共计 1699 个字符,预计需要花费 5 分钟才能阅读完成。
先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制。从实现角度来看,主要有两种方式:基于 Redis 的方式和基于 Zookeeper 的方式,下面分别简单介绍下这两种方式:
一、基于 redis 的分布式锁实现
1. 获取锁
redis 是一种 key-value 形式的 NOSQL 数据库,常用于作服务器的缓存。从 redis v2.6.12 开始,set 命令开始变成如下格式:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
除 key 和 value 外,EX 是超时时间,NX 表示只有在 key 不存在的时候才会设置 key 的值,而 XX 表示在 key 存在的时间才会设置 key 的值。NX 机制就是基于 redis 分布式锁的核心。能够解决以下问题:
1)节点 1 获取 key,并且设置超时时间后,还没来得及释放就挂掉了——这里 EX 超时时间会发挥作用,超时后自动释放锁。
2)刚获取到锁,还没来得及设置超时时间就挂了——这里设置 key 和设置超时时间是原子操作,如果出现这种情况,会返回 0,即获取不到锁。
2. 释放锁
为了解决非原子操作带来的问题,常采用 lua 脚本实现。lua 脚本的操作会被认为是原子性的,类似于事务。伪代码如下:
String luaScript =
“if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));
二、基于 zookeeper 的分布式锁实现
zookeeper 是一种分布式协调服务,其中每个节点称为 znode,并有自己独立的路径。znode 有四种类型:
持久节点:默认的节点类型。创建节点的客户端与 zookeeper 断开连接后,该节点依旧存在。
持久节点顺序节点:所谓顺序节点,就是在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号:
临时节点:和持久节点相反,当创建节点的客户端与 zookeeper 断开连接后,临时节点会被删除:
临时顺序节点:结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与 zookeeper 断开连接后,临时节点会被删除。
下面看看是怎样基于上面的四类节点实现分布式锁的。
1. 获取锁
1)在 Zookeeper 当中创建一个持久节, 当第一个客户端 Client1 想要获得锁时, 需要在这个节点下面创建一个临时顺序节点。
2)Client1 查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
3)如果再有一个客户端 Client2 前来获取锁,则在持久节点下面再创建一个临时顺序节点 Lock2。
4)Client2 查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。
于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态。
5)如果又有一个客户端 Client3 前来获取锁,则在持久节点下载再创建一个临时顺序节点 Lock3。
Client3 查找持久节点下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock3 是不是顺序最靠前的一个,结果同样发现节点 Lock3 并不是最小的。
于是,Client3 向排序仅比它靠前的节点 Lock2 注册 Watcher,用于监听 Lock2 节点是否存在。这意味着 Client3 同样抢锁失败,进入了等待状态。
2. 释放锁
释放锁就比较简单了,因为前面创建的临时顺序节点,所以在出现下面两种情况时,都会自动释放锁:
1)任务完成后,Client 会释放锁。
2)任务没完成,Client 就崩溃了,也会自动释放锁。