Redis 高并发分布式锁

微服务架构下的系统不可以使用 synchronized 加锁,可以使用 redis 的 setnx 指令来加锁。

Redis 加锁简单示例

@RestController
public class IndexTestController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //redis普通加锁
    @RequestMapping("/test_lock")
    public String  testLock(){
        String lock_key = "lock:test";
        
        //唯一id,不能用线程id,因为分布式有多台服务器,不同服务器的线程id可能相同
        String clientId = UUID.randomUUID().toString();

        //实现上锁和设置超时时间的原子性
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lock_key, clientId, 10, TimeUnit.SECONDS);
        if(!result){
            System.out.println("error_code");
            return "error_code";
        }
        
        try{
            //扣减库存
            int count = Integer.parseInt(stringRedisTemplate.opsForValue().get("count"));
            count = count - 1;
            if(count > 0){
                stringRedisTemplate.opsForValue().set("count", count + "");
                System.out.println("库存剩余:" + count + "个");
            }else{
                System.out.println("库存剩余不足");
            }

        }finally {
            //clientId 判断用来防止被其他线程误删锁
            if(clientId.equals(stringRedisTemplate.opsForValue().get(lock_key))){
            	//注意:clientId 的相等判断和删除锁的操作仍不满足原子性
                stringRedisTemplate.delete(lock_key);
            }
        }

        return "end";
    }
}

setnx 加锁可能产生的问题

如果finally 不进行 clientId.equals() 判断,直接删除 lock_key,可能删除了另外一个线程的锁。
在这里插入图片描述

如果 finally 进行 clientId.equals() 判断仍仍然可能删除了另外一个线程的锁

因为 clientId 的相等判断和删除锁的操作不满足原子性。

  • 线程 1 执行了 clientId.equals() 判断结果为 true;
  • 这时线程 1 还未来得及执行 delete 操作,线程 2 获得了锁;
  • 线程 1 重新获得资源,执行 delete 操作,这时 delete 的是线程 2 的锁。

Redisson 分布式锁

利用 Redisson 分布式锁可以避免上面的问题,Redisson 使用了 Lua 脚本来保证 redis 语句执行的原子性及支持事务,同时可以减少网络开销。

@RestController
public class IndexTestController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //redisson
    @RequestMapping("/test_lock")
    public String  testLock(){
        String lockKey = "lock:product_101";
        //获取锁对象
        RLock redissonLock = redisson.getLock(lockKey);
        //加分布式锁
        redissonLock.lock();   
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); 
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); /
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
            //解锁
            redissonLock.unlock();
        }

        return "end";
    }
}

Redisson 分布式锁原理

Redisson 还支持锁续命功能,后台线程发现线程还持有锁,会重置这个锁的过期时间,这种方式避免了锁过期了但是线程还没有结束的情况。

在这里插入图片描述

Redisson Redlock 分布式锁

Redis 主从架构锁失效问题

  1. 线程 1 加锁成功,主节点保存了加锁信息,但是还没有同步到从节点。
  2. 这时主节点宕机,从节点重新被选举为新的主节点,新的主节点中没有线程 1 加锁的记录。
  3. 线程 2 申请加锁成功,线程 1 加的锁失效。

在这里插入图片描述
解决办法
1、使用 Zookeeper,但是性能不如 Redis。
2、Redisson Redlock 可以解决这个问题,但仍存在一些问题。

Redis Redlock 分布式锁原理与存在的问题分析

Redis Redlock 分布式锁原理
在这里插入图片描述

存在的问题
1、每个 redis 节点关联一个从节点,仍然可能发生主从架构锁失效的问题。
2、每个 redis 节点不关联从节点,没有主从架构,这时如果超过半数 redis 节点宕机会导致 redis 加锁失败;为了解决这个问题增加 redis 节点数量,每次加锁需要更多的节点返回加锁成功信息,影响性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值