阶段七模块一 分布式锁实现商品秒杀
2021/4/8 10:08:40
本文主要是介绍阶段七模块一 分布式锁实现商品秒杀,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
内容输出来源:拉钩教育Java就业训练营
锁:我们在多线程中接触过,作用就是让当前的资源不会被其他线程访问!
在zookeeper中使用传统的锁引发的 “羊群效应” :1000个人创建节点,只有一个人能成功,999人需要等待!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tu2wtPme-1617846797778)(分布式锁实现商品秒杀.assets/zookeeper详解.jpg)]
避免“羊群效应”,zookeeper采用分布式锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzA1EjZU-1617846797780)(分布式锁实现商品秒杀.assets/zookeeper详解-1617780648149.jpg)]
- 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序
- 判断自己是不是/lock下最小的节点
- 是,获得锁(创建节点)
- 否,对前面小我一级的节点进行监听
- 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知(比你年轻的死了,你成为最年轻的了)
- 重复步骤2
实现步骤
1 初始化数据库
-- 商品表 create table product( id int primary key auto_increment, -- 商品编号 product_name varchar(20) not null, -- 商品名称 stock int not null, -- 库存 version int not null -- 版本 ) insert into product (product_name,stock,version) values('锦鲤-清空购物车-大奖',5,0)
-- 订单表 create table `order`( id varchar(100) primary key, -- 订单编号 pid int not null, -- 商品编号 userid int not null -- 用户编号 )
2 搭建工程
工程构造
实体类Order和Product自行创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqgTpRT3-1617846797781)(分布式锁实现商品秒杀.assets/微信截图_20210407161310.png)]
<packaging>war</packaging> <properties> <spring.version>5.2.7.RELEASE</spring.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- Mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- 数据库 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <!-- junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!-- maven内嵌的tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <!-- 目前apache只提供了tomcat6和tomcat7两个插件 --> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <port>8001</port> <path>/</path> </configuration> <executions> <execution> <!-- 打包完成后,运行服务 --> <phase>package</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 后台的日志输出:针对开发者--> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> </configuration>
spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 1.扫描包下的注解 --> <context:component-scan base-package="controller,service,mapper"/> <!-- 2.创建数据连接池对象 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://192.168.204.131:3306/zkproduct? serverTimezone=GMT" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="username" value="root" /> <property name="password" value="123123" /> <property name="maxActive" value="10" /> <property name="minIdle" value="5" /> </bean> <!-- 3.创建SqlSessionFactory,并引入数据源对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property> </bean> <!-- 4.告诉spring容器,数据库语句代码在哪个文件中--> <!-- mapper.xDao接口对应resources/mapper/xDao.xml--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="mapper"></property> </bean> <!-- 5.将数据源关联到事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 6.开启事务 --> <tx:annotation-driven/> </beans>
webxml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
OrderMapper
@Mapper @Component public interface OrderMapper { // 生成订单 @Insert("insert into `order` (id,pid,userid) values (#{id},#{pid},#{userid})") int insert(Order order); }
ProducttionMapper
@Mapper @Component public interface ProductMapper { // 查询商品(目的查库存) @Select("select * from product where id = #{id}") Product getProduct(@Param("id") int id); // 减库存 @Update("update product set stock = stock-1 where id = #{id}") int reduceStock(@Param("id") int id); }
ProductService
public interface ProductService { //减库存 void reduceStock(int id) throws Exception; }
ProductServiceImpl
@Service public class ProductServiceImpl implements ProductService { @Autowired ProductMapper productMapper; @Autowired OrderMapper orderMapper; public void reduceStock(int id) throws Exception { // 获取库存(根据商品id查询商品) Product product = productMapper.getProduct(id); // 模拟网络延迟 Thread.sleep(1000); if(product.getStock() <= 0) throw new RuntimeException("已抢光!"); // 2.减库存 int i = productMapper.reduceStock(id); if(i == 1){ Order order = new Order(); order.setId(UUID.randomUUID().toString()); order.setPid(id); order.setUserId(101); orderMapper.insert(order); }else throw new RuntimeException("减库存失败,请重试!"); } }
controller
@Controller public class ProductAction { @Autowired private ProductService productService; @GetMapping("/prodeuct/reduce") @ResponseBody public Object reduce(int id) throws Exception { productService.reduceStock(id); return "ok"; } }
3 启动测试
-
启动两次工程,端口号分别8001和8002
-
使用nginx做负载均衡
upstream sga{ server 192.168.163.128:8001; server 192.168.163.128:8002; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://sga; root html; index index.html index.htm; }
-
使用 JMeter 模拟1秒内发出10个http请求
4. apahce提供的zookeeper客户端
基于zookeeper原生态的客户端类实现分布式是非常麻烦的,我们使用apahce提供了一个zookeeper客户端来实现
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.2.0</version> </dependency>
5. 在控制层中加入分布式锁的逻辑代码
@Controller public class ProductAction { @Autowired private ProductService productService; private static String connectString = "192.168.204.141:2181,192.168.204.142:2181,192.168.204.143:2181"; @GetMapping("/product/reduce") @ResponseBody public Object reduce( int id) throws Exception { // 重试策略 (1000毫秒试1次,最多试3次) RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //1.创建curator工具对象 CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy); client.start(); //2.根据工具对象创建“内部互斥锁” InterProcessMutex lock = new InterProcessMutex(client, "/product_"+id); try { //3.加锁 lock.acquire(); productService.reduceStock(id); }catch(Exception e){ if(e instanceof RuntimeException){ throw e; } }finally{ //4.释放锁 lock.release(); } return "ok"; } }
k = new InterProcessMutex(client, “/product_”+id);
try {
//3.加锁
lock.acquire();
productService.reduceStock(id);
}catch(Exception e){
if(e instanceof RuntimeException){
throw e;
}
}finally{
//4.释放锁
lock.release();
}
return “ok”;
}
}
这篇关于阶段七模块一 分布式锁实现商品秒杀的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24鸿蒙原生开发手记:03-元服务开发全流程(开发元服务,只需要看这一篇文章)
- 2024-11-24细说敏捷:敏捷四会之每日站会
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解