一,为什么要使用分布式锁? 

如果在并发时锁定代码的执行,java中用synchronized锁保证了线程的原子性和可见性

但java锁只在单机上有效,如果是多台服务器上的并发访问,则需要使用分布式锁,

例如:两台机器上同时各有一个进程查询同一件商品的库存,此时商品库存数为1,

数据库给两台机器返回的都是1,

然后这两台机器同时下单,两个订单就超出了商品的库存数,

所以此时要使用分布式锁

 

说明:刘宏缔的架构森林是一个专注架构的博客,

网站:https://blog.imgtouch.com本文: https://blog.imgtouch.com/index.php/2023/05/22/springboot-yong-redisredisson-shi-xian-fen-bu-shi-suo-redisson3111springboot22/

         对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

 

二,使用redisson

redisson是对redis用java语言的封装

1,redisson的官网:

https://redisson.org/

 

2,redisson的官方文档:

https://github.com/redisson/redisson/wiki

 

3,使用redisson做分布式锁和mysql悲观锁(for update)的区别:

   本质上没有区别,

   但redis性能更强

 

三,演示项目的相关信息

1,地址

https://github.com/liuhongdi/distributedlock

 

2,原理:

  在减库存之前,先加锁,

  在减库存完成后,解锁

  这样避免高并发时查询到相同的库存数而导致超卖情况

 

3,结构

 

redisson分布式锁官网 redisson分布式锁的优缺点_spring boot

 

4,数据表:

CREATE TABLE `goods` (
  `goods_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `goods_name` varchar(500) NOT NULL DEFAULT '' COMMENT 'name',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
  PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'

 

四,java代码的说明

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService {
 
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private GoodsMapper goodsMapper;
 
    /*
    *   加锁减库存
    * */
    @Override
    public boolean decrementProductStoreLock(int goodsId, int buyNum) {
        String key = "dec_store_lock_" + goodsId;
        //生成锁对象
        RLock lock = redissonClient.getLock(key);
        try {
            //2, TimeUnit.MINUTES
            lock.lock(2, TimeUnit.MINUTES);
            boolean upRes = updateGoodsStock(goodsId, buyNum);
            if (upRes == false) {
                return false;
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        } finally {
            //解锁
            if (lock.isHeldByCurrentThread()){
                System.out.println("----------------release lock");
                lock.unlock();
            }
        }
        return true;
    }
 
    /*
     *   减库存
     * */
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public boolean updateGoodsStock(int goodsId, int buyNum) {
        Goods goodsOne = goodsMapper.selectOneGoods(goodsId);
        System.out.println("-------------------------当前库存:"+goodsOne.getStock()+"-------购买数量:"+buyNum);
        if (goodsOne.getStock() < buyNum || goodsOne.getStock() <= 0) {
            System.out.println("------------------------fail:buy fail,return");
            return false;
        }
        int upStock = goodsOne.getStock()-buyNum;
        goodsOne.setStock(upStock);
        int upNum = goodsMapper.updateOneGoodsStock(goodsOne);
        System.out.println("-------------------------success:成交订单数量:"+upNum);
        return true;
    }
 
    /*
     *   不加锁减库存
     * */
    @Override
    public boolean decrementProductStoreNoLock(int goodsId, int buyNum) {
        return updateGoodsStock(goodsId, buyNum);
    }
}

 

java代码说明:

String key = "dec_store_lock_" + goodsId:  加锁时加入商品id,这样不影响其他商品

RLock是redisson中定义的可重入锁

 lock.lock(2, TimeUnit.MINUTES)   加锁,时长两分钟

lock.unlock(); 解锁

 

五,测试高并发时加锁的效果:

1,数据库的数据:

mysql> update goods set stock=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> select * from goods;
+----------+------------+-------+
| goods_id | goods_name | stock |
+----------+------------+-------+
|        3 | green cup2 |     3 |
+----------+------------+-------+
1 row in set (0.00 sec)

我们设置商品的库存数为3

 

2,不加锁的情况

#-c:请求并发数

#-n:请求总数

[root@localhost ~]# ab -c 20 -n 20 http://127.0.0.1:8080/lock/buynolock

查看代码的打印输出: 

-------------------------当前库存:3-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:2-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:1-------购买数量:1
-------------------------当前库存:1-------购买数量:1
-------------------------当前库存:1-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------success:成交订单数量:1
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------success:成交订单数量:1
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return

共成交5单,超出了库存数量

 

3,加锁的情况:

#-c:请求并发数

#-n:请求总数

[root@localhost ~]# ab -c 20 -n 20 http://127.0.0.1:8080/lock/buylock

查看代码的打印输出: 

-------------------------当前库存:3-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:2-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:1-------购买数量:1
-------------------------success:成交订单数量:1
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return
-------------------------当前库存:0-------购买数量:1
------------------------fail:buy fail,return

只成功了3个订单,说明分布式锁有效

 

六,查看spring boot的版本:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)