目录


在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用synchornized、 ReentrantLock等。

但是在后端集群部署的系统中,程序在不同的JVM虚拟机中运行, 且因为synchronized或ReentrantLock都只能保证同一个JVM进程 中保证有效,所以这时就需要使用分布式锁了。
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的 一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享 了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。




- CREATE TABLE `t_order` (
- `id` varchar(255) CHARACTER SET utf8 COLLATE
- utf8_general_ci NOT NULL,
- `order_status` int(1) NULL DEFAULT NULL
- COMMENT '订单状态 1 待支付 2已支付',
- `receiver_name` varchar(255) CHARACTER SET
- utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
- COMMENT '收货人名字',
- `receiver_mobile` varchar(255) CHARACTER SET
- utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
- COMMENT '收货人手机',
- `order_amount` decimal(10, 2) NULL DEFAULT
- NULL COMMENT '订单价格',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
- = utf8_general_ci ROW_FORMAT = Dynamic;
- CREATE TABLE `product` (
- `id` int(11) NOT NULL,
- `product_name` varchar(255) CHARACTER SET
- utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
- COMMENT '商品名字',
- `price` decimal(10, 2) NULL DEFAULT NULL
- COMMENT '商品价格',
- `count` bigint(50) UNSIGNED NULL DEFAULT NULL
- COMMENT '库存',
- `product_desc` varchar(255) CHARACTER SET
- utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
- COMMENT '商品描述',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
- = utf8_general_ci ROW_FORMAT = Dynamic;
- -- ----------------------------
- -- Records of product
- -- ----------------------------
- INSERT INTO `product` VALUES (1001,'拯救者',100.00, 5,'好用实惠', 1);
- CREATE TABLE `order_item` (
- `id` varchar(255) CHARACTER SET utf8 COLLATE
- utf8_general_ci NOT NULL,
- `order_id` varchar(36) CHARACTER SET utf8
- COLLATE utf8_general_ci NULL DEFAULT NULL
- COMMENT '订单ID',
- `produce_id` int(11) NULL DEFAULT NULL
- COMMENT '商品ID',
- `purchase_price` decimal(10, 2) NULL DEFAULT
- NULL COMMENT '购买价格',
- `purchase_num` int(11) NULL DEFAULT NULL
- COMMENT '购买数量',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
- = utf8_general_ci ROW_FORMAT = Dynamic;

- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-boot-starterartifactId>
- <version>3.4.2version>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <scope>runtimescope>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- <exclusions>
- <exclusion>
- <groupId>org.junit.vintagegroupId>
- <artifactId>junit-vintage-engineartifactId>
- exclusion>
- exclusions>
- dependency>
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-generatorartifactId>
- <version>3.5.2version>
- dependency>
-
- <dependency>
- <groupId>org.apache.velocitygroupId>
- <artifactId>velocity-engine-coreartifactId>
- <version>2.0version>
- dependency>
- dependencies>
- spring:
- application:
- name: lock
- datasource:
- url:
- jdbc:mysql://192.168.66.100:3306/distribute?serverTimezone=UTC
- username: root
- password01: 123456
- driver-class-name: com.mysql.cj.jdbc.Driver
- server:
- port: 9091
- @Slf4j
- @MapperScan("com.itbaizhan.lock.mapper")
- @SpringBootApplication
- public class LockdemoApplication {
- public static void main(String[] args) {
- SpringApplication.run(LockdemoApplication.class, args);
- log.info("************** 分布式锁 **************");
- }
- }
使用Mybaits Plus生成订单表、商品表、订单商品关联表的相关代码。
- package com.itbaizhan.lock.utils;
- import com.baomidou.mybatisplus.generator.FastAutoGenerator;
- import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
- import java.util.Arrays;
- import java.util.List;
- public class CodeGenerator {
- public static void main(String[] args) {
- FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
- .globalConfig(builder -> {
- builder.author("itbaizhan")// 设置作者
- .commentDate("MMdd") // 注释日期格式
- .outputDir(System.getProperty("user.dir")+ "/src/main/java/") // 指定输出目录
- .fileOverride(); //覆盖文件
- })
- // 包配置
- .packageConfig(builder -> {
- builder.parent("com.itbaizhan.lock") // 包名前缀
- .entity("entity")//实体类包名
- .mapper("mapper")//mapper接口包名
- .service("service"); //service包名
- })
- .strategyConfig(builder -> {
- List
strings = Arrays.asList("t_order"); - // 设置需要生成的表名
- builder.addInclude(strings)
- // 开始实体类配置
- .entityBuilder()
- // 开启lombok模型
- .enableLombok()
- //表名下划线转驼峰
- .naming(NamingStrategy.underline_to_camel)
- //列名下划线转驼峰
- .columnNaming(NamingStrategy.underline_to_camel);
- })
- .execute();
- }
- }
- public interface ITOrderService extends IService
{ - /**
- * 创建订单
- * @return
- */
- String createOrder(Integer productId,Integer count);
- }
- package com.itbaizhan.lock.service.impl;
- import com.itbaizhan.lock.entity.OrderItem;
- import com.itbaizhan.lock.entity.Product;
- import com.itbaizhan.lock.entity.TOrder;
- import com.itbaizhan.lock.mapper.OrderItemMapper;
- import com.itbaizhan.lock.mapper.ProductMapper;
- import com.itbaizhan.lock.mapper.TOrderMapper;
- import com.itbaizhan.lock.service.ITOrderService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import javax.annotation.Resource;
- import java.math.BigDecimal;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- *
- * 服务实现类
- *
- *
- * @author itbaizhan
- * @since 05-25
- */
- @Service
- public class TOrderServiceImpl extends ServiceImpl
implements ITOrderService { - @Resource
- OrderItemMapper orderItemMapper;
- @Resource
- ProductMapper productMapper;
- /**
- * 创建订单
- * @return
- */
- @Transactional(rollbackFor = Exception.class)
- @Override
- public String createOrder(Integer productId,Integer count) {
- // 1、根据商品id查询商品信息
- Product product = productMapper.selectById(productId);
- // 2、判断商品是否存在
- if (product == null){
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if( count > product.getCount() ){
- throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
- }
- // 4、计算库存
- Integer leftCount = product.getCount() - count;
- // 5、更新库存
- product.setCount(leftCount);
- productMapper.updateById(product);
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- }
- }
- @RestController
- @RequestMapping("/order")
- public class OrderController {
- @Autowired
- private ITOrderService iOrderService;
- /**
- * 创建订单
- * @param productId 商品id
- * @param count 商品数量
- * @return
- */
- @PostMapping("/create")
- public String createOrder(Integer productId,Integer count){
- return iOrderService.createOrder(productId,count);
- }
- }





下载Nginx windows服务,官网http://nginx.org/en/download.html

编辑nginx.conf文件添加负载均衡的配置。
- upstream test{
- server localhost:9090 ;
- server localhost:9091 ;
- }
-
- server {
- listen 80;
- server_name localhost;
-
- location / {
- proxy_pass http://test;
- }
- }
在nginx目录下面

添加线程组 -> HTTP请求 -> 察看结果树 -> 聚合报告





Spring进行了统一的抽象,形成了 PlatformTransactionManager事务管理器接口 , 事务的 提交、回滚等操作 全部交给它来实现。
三个接口功能一句话总的来说事务管理器基于事务基础信息在操作 事务时候对事务状态进行更新。

public synchronized String createOrder(Integer produceId, Integer purchaseNum) { }
- @Autowired
- private PlatformTransactionManager platformTransactionManager;
- @Autowired
- private TransactionDefinition transactionDefinition;
TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
- /**
- * 创建订单
- *
- * @param produceId 商品id
- * @param purchaseNum 购买数量
- * @return
- */
- @Override
- public synchronized String createOrder(Integer produceId, Integer purchaseNum) {
- TransactionStatus transaction = platformTransactionManager.getTransaction(trans
- actionDefinition);
- // 1、根据商品id获取商品信息
- Product product = productMapper.selectById(produceId);
- // 2、判断商品是否存在
- if (product == null) {
- platformTransactionManager.rollback(transaction);
- throw new RuntimeException("购买商品不存在");
- }
- log.info(Thread.currentThread().getName() + "库存数量" + product.getCount());
- // 3、校验库存
- if (purchaseNum > product.getCount()) {
- platformTransactionManager.rollback(transaction);
- throw new RuntimeException("库存不足");
- }
- // 4、更新库存操作
- int count = product.getCount() - purchaseNum;
- product.setCount(count);
- productMapper.updateById(product);
- // 5、创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//订单状态
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781058");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum)));//订单价格
- baseMapper.insert(order);
- // 6、创建订单和商品关联
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());//订单id
- orderItem.setProduceId(product.getId());// 商品id
- orderItem.setPurchasePrice(product.getPrice());// 购买价格
- orderItem.setPurchaseNum(purchaseNum);// 购买数量
- orderItemMapper.insert(orderItem);
- //提交事务
- platformTransactionManager.commit(transaction);
- return order.getId();
- }

分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一 致性(Consistency)、可用性(Availability)和分区容错性 (Partition tolerance),最多只能同时满足两项。”所以,很多系 统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的 场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只 需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯 一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。

使用Redis来实现分布式锁的效率最高,加锁速度最快,因为Redis 几乎都是纯内存操作,而基于数据库的方案和基于Zookeeper的方 案都会涉及到磁盘文件IO,效率相对低下。一般使用Redis来实现分 布式锁都是利用Redis的 SETNX key value 这个命令,只有当key不存在时 才会执行成功,如果key已经存在则命令执行失败。

Zookeeper一般用作配置中心,其实现分布式锁的原理和Redis类 似,我们在Zookeeper中创建临时顺序节点,利用节点不能重复创建的特性来保证排他性。


顾名思义,就是比较悲观的锁,总是假设最坏的情况,每次去拿数 据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁, 这样别人想拿这个数据就会阻塞直到它拿到锁。
开启2个命令行界面


- mybatis-plus:
- mapper-locations: classpath:mapper/*.xml
- public interface ProductMapper extends BaseMapper
{ - Product findById(@Param("id")Integer id);
- }
- <select id="findById" parameterType="int" resultType="com.itbaizhan.lock.entity.Product">
- select * from product where id = #{id} for update
- select>
- @Transactional(rollbackFor = Exception.class)
- @Override
- public String createOrder(Integer productId, Integer count) {
- // 1、根据商品id查询商品信息
- Product product = productMapper.findById(productId);
- // 2、判断商品是否存在
- if (product == null) {
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if (count > product.getCount()) {
- throw new RuntimeException("商品" +productId + "仅剩" + product.getCount() + "件,无法购买");
- }
- // 4、计算库存
- Integer leftCount = product.getCount() - count;
- // 5、更新库存
- product.setCount(leftCount);
- productMapper.updateById(product);
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- }


总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有 去更新这个数据,可以使用版本号机制和CAS算法实现。

- <update id="decreaseStockForVersion" parameterType="int" >
- UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
- update>
- /**
- * 创建订单 乐观锁
- *
- * @return
- */
- @Transactional(rollbackFor = Exception.class)
- @Override
- public String createOrder(Integer productId, Integer count) throws Exception {
- int retryCount = 0;
- int update = 0;
- // 1、根据商品id查询商品信息
- Product product = productMapper.selectById(productId);
- // 2、判断商品是否存在
- if (product == null) {
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if (count > product.getCount()) {
- throw new Exception("库存不够");
- }
- // 乐观锁更新库存
- // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
- // 最多重试3次
- while(retryCount < 3 && update == 0){
- update = this.reduceStock(product.getId(),count);
- retryCount++;
- }
- if (update == 0){
- throw new Exception("库存不够");
- }
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- }
- /**
- * 减库存
- *
- * 由于默认的事务隔离级别是可重复读,produce.findById()
- * 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
- */
- @Transactional(rollbackFor = Exception.class)
- public int reduceStock(int gid,int count) {
- int result = 0;
- //1、查询商品库存
- Product product = productMapper.selectById(gid);
- //2、判断库存是否充足
- if (product.getCount() >= count) {
- //3、减库存
- // 乐观锁更新库存
- result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
- }
- return result;
- }
互斥:确保只有一个线程获得锁
- # 添加锁 利用setnx的互斥性
- 127.0.0.1:6379> setnx lock thread1
1、手动释放锁
2、超时释放:获取锁时设置一个超时时间
- #释放锁 删除即可
- 127.0.0.1:6379> del lock
超时释放
- 127.0.0.1:6379> setnx lock tread1
- 127.0.0.1:6379> expire lock 5
- 127.0.0.1:6379> ttl lock
两步合成一步
- help set
- SET key value [EX seconds] [PX milliseconds] [NX|XX]
- summary: Set the string value of a key
- since: 1.0.0
- group: string
- 127.0.0.1:6379> get k1
- (nil)
- 127.0.0.1:6379> set lock k1 ex 5 nx
- OK
- 127.0.0.1:6379> set lock k1 ex 5 nx
- nil



-
-
org.springframework.boot -
spring-boot-starter-data-redis -
- spring:
- redis:
- host: localhost
- port: 6379
- @Override
- public String createOrderRedis(Integer productId, Integer count) throws Exception {
- log.info("*************** 进入方法 **********");
- String key = "lock:";
- String value = UUID.randomUUID().toString();
- // 获取分布式锁
- Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
- // 判断是否获取锁成功
- if (!result){
- log.info("我进入了锁");
- return "不允许重复下单";
- }
- try {
- // 1、根据商品id查询商品信息
- Product product = productMapper.selectById(productId);
- // 2、判断商品是否存在 if (product == null) {
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if (count > product.getCount()) {
- throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
- }
- // 4、计算库存
- Integer leftCount = product.getCount() - count;
- // 5、更新库存
- product.setCount(leftCount);
- productMapper.updateById(product);
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- // 释放锁
- stringRedisTemplate.delete(key+productId);
- }
- return "创建失败";
- }


- private static final String KEY_PREFIX = "lock:";
- private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");
- //1、获取线程标识
- String threadId = ID_PREFIX + Thread.currentThread().getId();
- // 2、获得锁 setnx key value time type
- Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);
- // 获取锁标识
- String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
- // 判断标识是否一致
- if (s.equals(threadId)){
- // 释放锁
- stringRedisTemplate.delete(KEY_PREFIX + produceId);
- }




Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。
-
org.redisson -
redisson-spring-boot-starter -
3.17.2
- spring:
- redis:
- host: localhost
- port: 6379
- package com.itbaizhan.lock.utils;
- import lombok.extern.slf4j.Slf4j;
- import org.redisson.api.RLock;
- import org.redisson.api.RedissonClient;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import java.util.concurrent.TimeUnit;
- @Component
- @Slf4j
- public class DistributedRedisLock {
- @Autowired
- private RedissonClient redissonClient;
- // 加锁
- public Boolean lock(String lockName) {
- if (redissonClient == null) {
- log.info("DistributedRedisLock redissonClient is null");
- return false;
- }
- try {
- RLock lock = redissonClient.getLock(lockName);
- // 锁15秒后自动释放,防止死锁
- lock.lock(15, TimeUnit.SECONDS);
- log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
- // 加锁成功
- return true;
- } catch (Exception e) {
- log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
- return false;
- }
- }
- // 释放锁
- public Boolean unlock(String lockName) {
- if (redissonClient == null) {
- log.info("DistributedRedisLock redissonClient is null");
- return false;
- }
- try {
- RLock lock = redissonClient.getLock(lockName);
- lock.unlock();
- log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
- // 释放锁成功
- return true;
- } catch (Exception e) {
- log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
- return false;
- }
- }
- }
- /**
- * Redis锁实现
- *
- * @param productId
- * @param count
- * @return
- * @throws Exception
- */
- @Override
- public String createOrderRedis(Integer productId, Integer count) throws Exception {
- //获取锁对象
- if (distributedRedisLock.lock(String.valueOf(productId))) {
- try {
- // 1、根据商品id查询商品信息
- Product product = productMapper.selectById(productId);
- // 2、判断商品是否存在
- if (product == null) {
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if (count > product.getCount())
- {
- throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
- }
- // 4、计算库存
- Integer leftCount = product.getCount() - count;
- // 5、更新库存
- product.setCount(leftCount);
- productMapper.updateById(product);
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- distributedRedisLock.unlock(String.valueOf(productId));
- }
- }
- return "创建失败";
- }



这种排队取水模型,就是一种锁的模型。


- [zk: localhost:2181(CONNECTED) 1] create -e -s
- /test 123


当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。

接着,客户端 Client1 会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获得锁。

这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。

客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它 排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存 在,也就是说 client2 抢锁失败进入等待状态。

此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。

同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。

如果是任务完成,Client1 会显式调用删除 lock1 的指令。

如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除的。

lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2 立刻收到通知,也会查找 locks 下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。

同理,Client2 获得锁之后,Client3 也对它虎视眈眈:


Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。

- <dependency>
- <groupId>org.apache.curatorgroupId>
- <artifactId>curator-frameworkartifactId>
- <version>5.2.0version>
- dependency>
- <dependency>
- <groupId>org.apache.curatorgroupId>
- <artifactId>curator-recipesartifactId>
- <version>5.2.0version>
- dependency>
- <dependency>
- <groupId>org.apache.curatorgroupId>
- <artifactId>curator-clientartifactId>
- <version>5.2.0version>
- dependency>
- @Configuration
- public class ZookeeperConfig {
- @Bean
- public CuratorFramework zookeeperClient() {
- CuratorFramework client = CuratorFrameworkFactory.builder()
- .connectString("127.0.0.1:2181")
- .sessionTimeoutMs(5000)
- .connectionTimeoutMs(5000)
- .retryPolicy(new ExponentialBackoffRetry(1000, 3))
- //.namespace("test")
- .build();
- client.start();
- return client;
- }
- }
使用InterProcessMutex的acquire和release方法,来获取和释放锁。
- @Autowired
- CuratorFramework client;
- @Override
- public String createOrderZookeeper(Integer productId, Integer count) throws Exception
- {
- // client cruator中zk客户端对象 path 抢锁路径同一个锁path需要一致
- InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
- //第一个属性:定时的时间数字
- //第二个属性:定义时间的单位
- if (lock.acquire(3, TimeUnit.SECONDS))
- {
- try {
- // 1、根据商品id查询商品信息
- Product product = productMapper.selectById(productId);
- // 2、判断商品是否存在
- if (product == null) {
- throw new RuntimeException("购买商品不存在:" + productId + "不存在");
- }
- // 3、校验库存
- if (count > product.getCount())
- {
- throw new RuntimeException("商品" + productId + "仅剩" +
- product.getCount() + "件,无法购买");
- }
- // 4、计算库存
- Integer leftCount = product.getCount() - count;
- // 5、更新库存
- product.setCount(leftCount);
- productMapper.updateById(product);
- // 6、 创建订单
- TOrder order = new TOrder();
- order.setOrderStatus(1);//待处理
- order.setReceiverName("张三");
- order.setReceiverMobile("18587781068");
- order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
- baseMapper.insert(order);
- // 7、 创建订单和商品关系数据
- OrderItem orderItem = new OrderItem();
- orderItem.setOrderId(order.getId());
- orderItem.setProduceId(product.getId());
- orderItem.setPurchasePrice(product.getPrice());
- orderItem.setPurchaseNum(count);
- orderItemMapper.insert(orderItem);
- return order.getId();
- } finally {
- lock.release();
- }
- }
- return "创建失败";
- }

优点:简单,使用方便,不需要引入 Redis、Zookeeper 等中间件。
缺点:1、不适合高并发的场景 2、db 操作性能较差
优点:1、性能好,适合高并发场景 2、较轻量级 3、有较好的框架支持,如 Redisson
缺点:1、过期时间不好控制 2、需要考虑锁被别的线程误删场景
优点:1、有较好的性能和可靠性 2、有封装较好的框架,如 Curator
缺点:1、性能不如 Redis 实现的分布式锁 2、比较重的分布式锁。
1、从性能角度:Redis > Zookeeper >= 数据库
2、从实现的复杂性角度:Zookeeper > Redis > 数据库
3、从可靠性角度:Zookeeper > Redis > 数据库