![c32531051c38391eac5bc88b8d374738.png](https://i-blog.csdnimg.cn/blog_migrate/005a895f75c0f809135c68c84aad2a14.jpeg)
前面我们介绍了下锁的基本概念,这篇文章主要介绍下Redis、Zookeeper、etcd怎么来实现分布式锁。
Redis
redis主要是通过setnx、get、getset、del命令来完成加锁,抢锁和释放锁的操作的,这里我用两个客户端来模拟下加锁的过程。
1、客户端1使用setnx获取到锁,并设置锁的当前时间。
执行命令
setnx lock 0
我们假定锁的当前时间是从0秒开始的,超时时间为3秒。可以看出结果返回为1,表明加锁成功,可以执行业务逻辑了。
![ae271fa03fcf6205733ac18d5fbbcc18.png](https://i-blog.csdnimg.cn/blog_migrate/3b4d2a7fa82c9a3d387bb26ed6a55fcb.jpeg)
2、第一秒钟客户端2使用setnx获取锁,执行命令
setnx lock 1
返回值为0,则加锁失败。
![b9f92077b61d82d5c2193563249a5ed9.png](https://i-blog.csdnimg.cn/blog_migrate/c82034ca918e6867643c27b2723c34f5.jpeg)
3、客户端2接收到加锁时间的回应后,会去查看锁是否超时。执行命令
get lock
返回值为加锁的时间,假设现在时间已经到第4秒了,客户端会使用返回的时间0加上超时时间3小于当前时间4,锁已经超时了。
![872a1ef07b2292082e3d68326e20eaca.png](https://i-blog.csdnimg.cn/blog_migrate/aff98b4041c02f6147c8063565d9694b.jpeg)
4、客户端2使用getset命令去获取锁。
getset lock 4
这时候返回的是前一次加锁的时间,如果这个时间是0,证明加锁成功,因为和前一次get的值相同。如果不同,说明锁已经被别人抢占了,加锁失败,继续重复步骤3和4。
![43caa3e36fe6a7084d7003c7157174df.png](https://i-blog.csdnimg.cn/blog_migrate/5bf6c5e6c7359e186402e143ca9b7595.jpeg)
5、业务执行完成后,判断下锁是否超时,没有超时,不用管了。如果没有超时,调用del命令释放锁即可。
![508dd366f58ecd0e14b1aedeb2ade8c4.png](https://i-blog.csdnimg.cn/blog_migrate/aa81f0d32446737c2bdf03d42fd24203.jpeg)
redis存在的问题
1、redis如果是单机的话是有单点问题的。redis集群因为是ap模型,是不能保证一致性的,官方提供了redlock算法来解决这个问题,但是至少需要3个master-slave节点才能完成,成本也较大。redlock相当于是来实现一致性协议的。
2、锁的超时时间设定问题,太长太短都不合适,太长了如果服务挂掉了会一直阻塞业务,太短了有可能业务还没执行完成就释放了。当然可以用官方提供redisson解决。
zookeeper
zookeeper是基于顺序临时节点和watch机制实现分布式锁的
1、创建持久化节点/lock。
create /lock 0
![b81f2d0c6d08a028e5cc15c32ecfd6d0.png](https://i-blog.csdnimg.cn/blog_migrate/647835c8fb247b8fe5a4491e76548760.jpeg)
2、server1来获取锁,在lock目录下创建有序的临时节点
create -s -e /lock/ 0
![767de3d9b30f8c3d91353e7e6d0f59fb.png](https://i-blog.csdnimg.cn/blog_migrate/d1c0608dbc3fb935ce1adb48aae9f515.jpeg)
3、server1查看lock下面的子节点,自己的序号是不是最小的,这时候只有一个节点肯定是最小的,则获取成功
4、server2这时候也来获取锁,创建有序的临时节点
create -s -e /lock/ 0
![6c7034f7b93ea74039d69ac3b2d5442f.png](https://i-blog.csdnimg.cn/blog_migrate/10c8c042a3237d0854311e08e57b2397.jpeg)
5、server2发现自己的节点序号不是最小,则获取失败,是最小则获取成功。并监听比自己小的前一个节点。
![e78c20ae54fe67b451fcd9a860ca43a2.png](https://i-blog.csdnimg.cn/blog_migrate/233c0a0433bab72891efd999b7ae937c.jpeg)
6、当上一个节点被删除的时候,会通知当前节点,这时候重复5步骤。
![d91028b67324e73b17176215e5756cd0.png](https://i-blog.csdnimg.cn/blog_migrate/bd4b20465e657d30082ead302dcdb058.jpeg)
zk总结
zk采用zab协议保证一致性,并且不用设置超时时间了,如果服务加锁挂掉了,临时节点也会自动删除。
当然也可用使用Apache开源的 curator框架, 自带了分布式锁解决方案,原理和上述差不多。
zk加锁释放锁都是通过动态创建节点、销毁节点来实现的,性能消耗较大。
http://curator.apache.org/
etcd
etcd调用可以通过restful API的方式进行,这些需要通过prevExist实现分布式锁,如果prevExist为true, 则这是一个更新请求,如果prevExist的值是false,则是一个创建请求。
1、server1获取锁,设置超时时间是3秒
http://127.0.0.1:2379/v2/keys/locks?value=xxx&ttl=3&prevExist=false
![065673fd28a59a21c38795c4570bded8.png](https://i-blog.csdnimg.cn/blog_migrate/e16b1ff4bc45f224dc367b87aa674acf.jpeg)
2、为了防止业务还没有执行完,锁释放,所以每隔1秒需求重新设置下值。这个就是锁续租约。
http://127.0.0.1:2379/v2/keys/locks?value=xxx&ttl=3&prevExist=true
![a613a6034a27aee4fb7b56146a4b218e.png](https://i-blog.csdnimg.cn/blog_migrate/353ec677c03a32fc7c3e56080359e0aa.jpeg)
3、server2这时候去获取锁,则会失败
http://127.0.0.1:2379/v2/keys/locks?value=xxx&ttl=3&prevExist=false
![93fec2165c14e8574fa000039ffaaeaf.png](https://i-blog.csdnimg.cn/blog_migrate/f5a3220ff46b21a04503f3b011ed1cc3.jpeg)
4、server2可以监听lock的变化。当lock目录有变化的时候就会接到通知,然后重复步骤3。
这里要注意watch事件不能太多。
http://127.0.0.1:2379/v2/keys/lock?wait=true
![c8a57da5bdc8fbde561cddff0025501c.png](https://i-blog.csdnimg.cn/blog_migrate/86ee7af4ed7ca36093f223987b6dfd18.jpeg)
etcd总结
上面这个是etcd2的实现,etcd3本身已经支持分布式锁了,key增加Revision了,客户端可以判定自己key对应的Revision是不是最小来获取锁,机制和zk获取最小值类似,但是etcd3可能还不够成熟。
etcd使用raft协议,相比paxos协议会容易理解些。
总结
以上就是redis、zk、etcd怎么实现分布式锁的过程了。
每个方案都有可行的,还是看公司整体情况选择使用哪种方案。
只要能保持读取和修改状态是原子性操作都能当锁使用,但是分布式锁需要明确的几个条件:获取锁的业务无论正常还是异常,都需要保证可以释放,否则会出现死锁的情况,还有就是锁释放后需要及时让等待锁的一方知道,方便再次获取锁。
欢迎关注我,后续会持续分享架构方面文章,谢谢