• 分布式锁(二)Redis实现分布式锁 Diamond


    一、背景:

    前面了解了分布式锁,做了最简单的入门了解,分布式锁(一)--基础

    这里再了解使用最多的Redis分布式锁。

    二、Redis实现可靠性分布式锁的条件:

    互斥性。在任意时刻,只有一个客户端能持有锁。

    不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

    具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

    加锁和解锁必须是同一个客户端。

    三、加锁思路:

    set lockKey value NX PX 3000
    

    lockKey:表示Redis的key。
    value:value一定要是随机值。
    NX:如果不存在这个key,可以设置成功,如果成功,设置失败,Redis返回nil。
    PX:过期时间到了之后,这个key就会自动删除。
    timeout(3000):过期时间为3s。
    如果没有拿到锁,需要每隔500ms或者其他时间间隔尝试获取锁。

    四、释放锁思路:

    一般可以用lua脚本删除,判断value一样才删除:

        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else
            return 0
        end
    

    1、为什么使用lua脚本:

    Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务。

    2、为什么使用随机值呢?

    如果客户端A获取到了锁,但是阻塞了很长时间才执行完,此时可能已经自动释放锁了。
    此时客户端B已经获取到了这个锁,要是直接删除key的话会有问题,所以用随机值加上面的lua脚本来释放锁。
    当客户端A执行完成去删除锁的时候,会判断value值才能够去删除,保证不会误删锁。

    五、代码实现:

        public static final String LOCK_SUCCESS = "OK";//加锁成功
        
        public static final String SET_IF_NOT_EXIST = "NX";
        
        public static final String SET_WITH_EXPIRE_TIME = "PX";
        
        public static final Long RELEASE_SUCCESS = 1L;
        
        public class RedisUtils {
        
            @Autowired
            JedisPool jedisPool;
        
            /**
             * 尝试获取分布式锁
             */
            public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
                Jedis jedis = jedisPool.getResource();
                String result = jedis.set(lockKey, requestId, RedisConstant.SET_IF_NOT_EXIST, RedisConstant.SET_WITH_EXPIRE_TIME, expireTime);
                if (StringUtils.equals(result, RedisConstant.LOCK_SUCCESS))
                    return true;
                return false;
            }
        
            /**
             * 释放分布式锁
             */
            public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        
                if (RedisConstant.RELEASE_SUCCESS.equals(result)) {
                    return true;
                }
                return false;
            }
        }
    

    这里可以通过while设置进行尝试获取锁,如果失败,sleep一段时间,直到达到最大过期时间。

    KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。

    六、存在的问题:

    key超时之后业务并没有执行完毕但是会自动释放锁,这样就会导致并发问题。

    生产环境Redis是通常都是cluster部署,当master挂了之后,master数据同步到slave也是需要时间的,可能正好同步过程中挂了,这样也会出现并发问题。

    亦或者key刚写入master,master就挂了,数据直接丢了。

    七、Redisson实现分布式锁:

    1、背景:

    具体api如何使用,自行查询官方文档,GitHub地址

    2、加锁:看门狗

    加锁和解锁过程都是通过lua脚本实现。

    当加锁之后,Redisson会开启watchdog线程。

    每隔10s是否还被客户端持有,如果是,将过期时间重置为30s。

    3、解锁:

    同样的,解锁也是通过lua脚本实现。

    正常情况下,程序执行完成解锁。

    如果加锁的客户端直接挂了,那么Redisson框架启动的watchdog线程肯定也挂了,到了30s就会过期。

  • 相关阅读:
    Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 2
    Putting Apache Kafka To Use: A Practical Guide to Building a Stream Data Platform-part 1
    Apache Kafka之设计
    分布式内存文件系统Tachyon
    Tachyon:Spark生态系统中的分布式内存文件系统
    Python垃圾回收机制
    一行 Python 实现并行化 -- 日常多线程操作的新思路
    Hive SQL的编译过程
    vim调整粘贴时的文本缩进
    linux img文件 分区挂载
  • 原文地址:https://www.cnblogs.com/huigelaile/p/15834873.html
Copyright © 2020-2023  润新知