美文网首页
Redis分布式锁

Redis分布式锁

作者: 圣村的希望 | 来源:发表于2019-11-18 19:58 被阅读0次

         在实际开发中,经常会用到redis来实现redis锁,来应对共享资源的并发访问。经常用的就是setnx+expire,释放锁用lua脚本来实现

    //获取锁,value唯一,这样便于释放锁
    set key value ex expireTime NX
    
    //释放锁,比较value来确认谁持有锁,谁就来释放锁
    if redis.call("get", keys[1]) == argv[1] then
        return redis.call("del", keys[1])
    else
        return 0
    end
    

          这种实现方式的关键点:

    • setnx和expire要在一个事务中执行,否则setnx成功,链接断开了expire没执行,就会出现死锁
    • value要唯一,这样在释放锁的时候就可以验证,谁持有锁谁就来释放锁
    • 释放锁用lua脚本,是为了保证命令在一个事务中执行

         具体java代码实现:

    public class RedisService {
    
        @Resource(name="redisTemplate")
        protected ValueOperations<String, String> valueOperations;
    
        public void setValue(String key, String value){
            valueOperations.set(key, value, TimeUnit.MILLISECONDS);
        }
    
        public String getValue(String key){
            return valueOperations.get(key);
        }
    
        public Boolean lock(String key, String value, long lockTime) {
            return valueOperations.setIfAbsent(key, value, lockTime, TimeUnit.MILLISECONDS);
        }
    
        public Long releaseLock(String key, String value) {
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> stringRedisScript = new DefaultRedisScript<>();
            ((DefaultRedisScript<Long>) stringRedisScript).setResultType(Long.class);
            ((DefaultRedisScript<Long>) stringRedisScript).setScriptText(luaScript);
            Long result = valueOperations.getOperations().execute(stringRedisScript, Collections.singletonList(key), value);
            return result;
        }
    
    }
    

         这样实现的优点就是很简单高效,在单机、主从、哨兵和集群环境一般情况都可以,但是在master发生主从切换的时候,就会出现锁丢失的情况,情况如下:

    • 客户端在master节点上拿到了锁
    • 但是这个时候master宕机了,并且发生了故障转移,slave自动升级为master节点
    • 这个节点没有及时从master同步到slave,这个时候就发生了锁丢失,主从复制异步

         基于上面的问题,又出现了Redlock,它的实现思路如下:

    1. 获取当前时间
    2. 依次尝试从奇数个实例(一般不小于3个),用相同的key和唯一的value获取锁。向redis请求获取锁时,客户端设置一个请求超时时间,这个超时时间远小于锁的有效时间。这样可以避免在redis宕机,客户端还在等待获取锁。如果没有在规定时间内从redis服务器获取锁,客户端立即尝试去另外一个redis服务器获取锁。
    3. 当且仅当从大多数的redis节点都获取到锁,并且锁的使用时间(当前时间减去步骤1中的时间)小于锁的有效时间,这样锁才算获取成功,锁的真正有效时间是有效时间减去获取锁使用时间。
    4. 锁没有获取成功,就释放部分获取到redis实例锁的客户端释放锁。防止这轮没获取锁成功,导致下一轮也没有获取到锁。

          Redlock具体实现:

    public class RedisRedlock {
        
        public void lock() {
            Config config1 = new Config();
            config1.useSingleServer().setAddress("redis://127.0.0.1:6379")
                    .setPassword("123456").setDatabase(0);
            RedissonClient redissonClient1 = Redisson.create(config1);
    
            Config config2 = new Config();
            config2.useSingleServer().setAddress("redis://127.0.0.1:6380")
                    .setPassword("123456").setDatabase(0);
            RedissonClient redissonClient2 = Redisson.create(config2);
    
            Config config3 = new Config();
            config3.useSingleServer().setAddress("redis://127.0.0.1:6371")
                    .setPassword("123456").setDatabase(0);
            RedissonClient redissonClient3 = Redisson.create(config3);
    
            String resourceName = "RLOCK";
    
            RLock lock1 = redissonClient1.getLock(resourceName);
            RLock lock2 = redissonClient2.getLock(resourceName);
            RLock lock3 = redissonClient3.getLock(resourceName);
            RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
            boolean isLock;
            try {
                // 50ms拿不到锁,就认为获取锁失败,10s是锁失效时间。
                isLock = redLock.tryLock(50, 10000, TimeUnit.MILLISECONDS);
                System.out.println("isLock = "+isLock);
                if (isLock) {
                    //business work
                }
            } catch (Exception e) {
    
            } finally {
                //释放锁
                redLock.unlock();
            }
        }
    }
    

         Redlock的实现,他对实例环境有点苛刻,它需要完全相互独立,不存在主从复制并且也不是存在集群协调机制。奇数个redis实例,奇数个sentinel集群,奇数个cluster集群。Redlock的实现仍然存在锁的有效时间失效的问题,也仍然存在主从复制不及时,故障主从转移的问题,只不过是它把问题出现的概率最小化了,除非是同时出现大多数节点同时宕机,同时出现主从不同步的问题,这样就会出现获取锁冲突问题。

          所以Redlock相较于前面的实现,它是把获取锁冲突的问题出现的概率降低了,但是仍然没有避免出现这个问题,同时Redlock带来的问题是,部署环境要求就要复杂点。所以在实际的选用方案就是在两者之间的权衡取舍。

    相关文章

      网友评论

          本文标题:Redis分布式锁

          本文链接:https://www.haomeiwen.com/subject/wwabictx.html