【并发编程】基于 Redis 手写分布式锁

发布于:2025-05-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

一、基于 Redis 演示超卖现象

1.1 Redis 超卖现象

1.2 超卖现象解决方案

二、Redis 的乐观锁机制

2.1 原生客户端演示

2.2 业务代码实现

三、单机部署 Redis 实现分布式锁

3.1 分布式锁的演变和升级

3.2 setnx 实现分布式锁

3.2.1 递归调用实现分布式锁

3.2.2 循环实现分布式锁(避免栈溢出)

3.3 set nx ex/px

3.3.1 锁设置过期时间

3.3.2 Lua 脚本解决锁误删问题

3.4 set nx ex/px+Lua(解决锁重入和锁续期)

3.4.1 可重入锁实现的分析

3.4.2 分布式锁工厂实现

3.4.3 Redis 分布式锁实现类


一、基于 Redis 演示超卖现象

1.1 Redis 超卖现象

1. 编写业务逻辑实现

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void deduct(){
    //1.查询库存信息
    String stock = stringRedisTemplate.opsForValue().get("stock");
    //2.判断库存是否充足
    if(stock != null && stock.length() > 0){
        Integer st = Integer.valueOf(stock);
        if(st>0){
            //3.扣减库存
            stringRedisTemplate.opsForValue().set("stock",String.valueOf(st-1));
        }
    }
}

2. 通过 JMeter 进行并发压力测试

3. 查看 Redis 请求后实际的商品库存数量:发生超卖现象

1.2 超卖现象解决方案

  1. JVM 本地锁机制:本地锁可以解决一个资源内部的并发资源的问题

  2. Redis 提供的乐观锁机制(性能较低):watch multi exec

    1. watch:可以监控一个或多个 key 的值,如果在事务执行之前,key 的值发生变化,则取消事务的执行

    2. multi:开启事务

    3. exec:执行事务

  3. 分布式锁:分布式锁可以实现跨进程跨服务器的使用

    1. 基于 Redis 实现:setnx

    2. 基于 Zookeeper/etcd 实现

    3. 基于 MySQL 实现


二、Redis 的乐观锁机制

2.1 原生客户端演示

Redis中的WATCH命令用于实现乐观锁机制。它可以监视一个或多个键,在事务执行前,如果这些被监视的键没有被其他客户端修改,事务可以正常执行;但只要有一个被监视的键在事务执行前被修改了,事务在执行EXEC时就会失败,从而让应用程序可以重新尝试事务或者采取其他处理逻辑,以保证在高并发场景下数据的一致性。

在执行WATCH stock之后,stock键的值被客户端2修改了。WATCH命令会监视指定的键,当在事务执行前(从WATCH命令执行到EXEC命令执行之间),被监视的键发生了变化,那么事务在执行EXEC时就会失败,以保证数据的一致性和避免并发问题。

2.2 业务代码实现

public void deduct(){
    stringRedisTemplate.execute(new SessionCallback<Object>() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            //1.查询库存信息
            operations.watch("stock");
            String stock = operations.opsForValue().get("stock").toString();
            //2.判断库存是否充足
            if(stock != null && stock.length() != 0){
                Integer st = Integer.valueOf(stock);
                if(st>0){
                    //3.扣减库存
                    operations.multi();
                    operations.opsForValue().set("stock",String.valueOf(--st));
                    List exec = operations.exec();
                    if(exec == null || exec.size()==0){
                        //重试操作
                        deduct();
                    }
                    return exec;
                }
            }
            return null;
        };
    });

}

运行 JMeter 进行并发测试:吞吐量只有104请求/秒:(性能太差)

查看 Redis 商品库存数量:


三、单机部署 Redis 实现分布式锁

3.1 分布式锁的演变和升级

Redis 实现分布式锁的演变和升级:

  1. setnx:set if Not eXists -> setnx key value -> 问题:因为没有设置过期时间,所以可能存在死锁的问题(执行一半程序崩溃,后面的释放锁流程没有执行)。

  2. set nx ex/px:Redis 2.6+ set key value


网站公告

今日签到

点亮在社区的每一天
去签到