美文网首页
分布式锁实现

分布式锁实现

作者: 飞翃荷兰人 | 来源:发表于2020-09-25 00:28 被阅读0次

    1 基本的实现方式

    分布式锁的主要实现方式主要有以下几种:mysql,zookeeper和redis。下面依次介绍这三种组件实现分布式锁的方式。

    2 分布式锁的几个问题

    在使用分布式锁的时候,需要考虑如下几个问题:假如我们在多线程下拿锁(多线程和多进程是一样的):

    • 假如设置了锁的有效期,在有效期内线程没有执行完怎么办。
    • 假如不设置锁的有效期,线程异常挂掉,所有线程都拿不到锁了怎么办。
      带着这样的问题,看下面的实现。

    3 mysql实现分布式锁

    先select,如果没有则,insert一个key,insert成功则拿到锁,insert不成功则拿不到锁。如果第一次select到了,那么用select for update操作施加一个x锁,for update拿到数据则拿锁成功。
    锁的有效期就是线程的有效期,线程如果中断了或者异常退出了,一般都会捕获异常,回滚事务,释放锁。

    4 zookeeper实现分布式锁

    zookeeper实现分布式锁是利用的zookeeper可以创建临时节点的机制,同时利用了zk的watcher机制,如果不太熟悉zk的可以看我的zk文章,一个常见的使用zk作为分布式锁的方法为:

    • 线程在zk上创建临时节点,如果创建成功,则持有节点,并拿到锁;如果创建失败,则监听这个节点。
    • 线程退出,正常退出则删除节点,异常中断则,当SESSIONEXPIRED(可设置)之后,zk会把这个会话建立的临时数据移除。同时,所有等待这个节点的线程会收到通知。
    • 收到通知的线程都去拿锁,创建临时节点,但是只有一个可以创建成功。拿到锁。

    细心的读者可能发现了问题,所有的线程都去拿锁,这就是惊群效应,那么如何避免呢?zk还提供了一种创建模式,可以创建顺序节点,那么该怎么做呢?

    • 所有线程创建顺序节点,并且watch本节点的前一个节点。
    • 如果本节点的前一个节点被删除,则拿到锁。
    • 本线程做完工作则delete节点,异常退出则等待超时。

    zk的实现机制很完美,有什么问题呢?
    zk的事务提交流程比较长,在集群下需要非leader转发到leader,leader发起投票,过半提交后leadercommit事务,follower 提交事务。整个流程比较长,有些高并发场景下可能不太适用,而且新增了一个zk组件,不便维护。

    3 redis实现分布式锁

    redis的分布式锁实现,要依赖于原子操作和setnx命令。setnx拿到锁并同时设置超时时间的原子操作命令为:

    SET key value [EX seconds] [PX milliseconds] [NX|XX]
    实例:SET resource-name any-string NX EX max-lock-time
    

    这个命令解决了

    假如不设置锁的有效期,线程异常挂掉,所有线程都拿不到锁了怎么办。

    的问题。但是续期怎么做呢。
    有两种做法:
    第一种:

    • 第一种是每次set key的时候set一个唯一id(可以用时间+mac+线程号)。
    • 每次释放的时候看是不是自己的锁,放置过期了被别人拿到了,又把别人的锁释放了。但是这一步需要原子操作,可以用lua脚本:
    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                    "return nil;" +
                "end; " +
                // 将该客户端对应的锁的 hash 结构的 value 值递减为 0 后再进行删除
                // 然后再向通道名为 redisson_lock__channel publish 一条 UNLOCK_MESSAGE 信息
                "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                "if (counter > 0) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                    "return 0; " +
                "else " +
                    "redis.call('del', KEYS[1]); " +
                    "redis.call('publish', KEYS[2], ARGV[1]); " +
                    "return 1; "+
                "end; " +
                "return nil;",
    

    第二种,使用看门狗:

    • set key的时候需要set进线程id
    • Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒 (internalLockLeaseTime / 3) 检查一下,如果客户端 1 还持有锁 key(判断客户端是否还持有 key,其实就是遍历 EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间。
    • 锁释放也需要lua脚本。

    相关文章

      网友评论

          本文标题:分布式锁实现

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