Redis实现分布式锁

实现分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

  • 「互斥性」: 任意时刻,只有一个客户端能持有锁。
  • 「锁超时释放」:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
  • 「可重入性」:一个线程如果获取了锁之后,可以再次对其请求加锁。
  • 「高性能和高可用」:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
  • 「安全性」:锁只能被持有的客户端删除,不能被其他客户端删除

核心思想

借助redis 的SET key value[EX seconds][PX milliseconds][NX|XX]命令的原子性实现分布式锁获取

  • NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
  • EX seconds :设定key的过期时间,时间单位是秒。
  • PX milliseconds: 设定key的过期时间,单位为毫秒
  • XX: 仅当key存在时设置值

📌1. set命令要用set key value px milliseconds nx
2. value要具有唯一性
3. 释放锁时要验证value值,不能误解锁

代码实现

        // 当前处理业务ID
        String orderId = "orderId-123456";
        // 当前请求唯一ID
        String requestId = UUID.randomUUID().toString(true) + Thread.currentThread().getId();
        // 锁自动过期时间, 100s避免死锁
        long expire = 100L;

        // 当key不存在时才会set,成功返回true
        boolean lock = Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(orderId, requestId, Duration.ofSeconds(expire))
        );
        if (lock) {
            try {
                // 拿到锁
                log.info("执行业务逻辑");
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // 释放锁
                // 非原子性处理(不可靠):判断是不是当前线程加的锁,是才释放。
//                if (requestId.equals(String.valueOf(redisTemplate.opsForValue().get(orderId)))){
//                    // 如果此时锁已到期自动失效,可能会误删别人的锁
//                    redisTemplate.delete(orderId);
//                }

                // 原子性处理方法(可靠):使用lua脚本
                String lua = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;";
                Long res = redisTemplate.execute(
                        new DefaultRedisScript<>(lua, Long.class), Collections.singletonList(orderId), requestId
                );
                log.info(Long.valueOf(1).equals(res)? "解锁成功": "解锁失败");
            }
        }

注意上述分布式锁实现仍然存在问题:假设线程a获取锁成功,一直在执行临界区的代码。但是100s过去后,它还没执行完,锁已自动过期销毁。此时线程b请求过来,就可以成功获得锁,也开始执行临界区的代码

基于Redisson框架实现

该框架就可以解决上述提到的问题,只要线程一加锁成功,redisson就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。

核心内容

设置多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁

  1. 按顺序向N个master节点请求加锁
  2. 根据设置的超时时间来判断,是不是要跳过该master节点1
  3. 如果大于等于N/2+1个节点加锁成功,并且获取N个锁使用的总时间小于锁的有效期,即可认定加锁成功
  4. 如果加锁失败,则全部解锁

相关案例

Redlock:Redis分布式锁最牛逼的实现 (qq.com)


  1. 客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值