之前我们使用的定时任务都是只部署在了单台机器上,为了解决单点的问题,为了保证一个任务,只被一台机器执行,就需要考虑锁的问题,于是就花时间研究了这个问题。到底怎样实现一个分布式锁呢?
锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个可靠完善的分布式锁需要考虑的细节比较多,我们就来看看如何写一个正确的分布式锁。
单机版分布式锁 SETNX
所以我们直接基于 redis 的 setNX (SET if Not eXists)命令,实现一个简单的锁。直接上伪码
锁的获取:
SET resource_name my_random_value NX PX 30000
锁的释放:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
几个细节需要注意:
首先在获取锁的时候我们需要设置设置超时时间。设置超时时间是为了,防止客户端崩溃,或者网络出现问题以后锁一直被持有。真个系统就死锁了。
使用 setNX 命令,保证查询和写入两个步骤是原子的
在锁释放的时候我们判断了KEYS[1]) == ARGV[1],在这里 KEYS[1]是从redis里面取出来的value,ARGV[1]是上文生成的my_random_value。之所以进行以上的判断,是为了保证锁被锁的持有者释放。我们假设不进行这一步校验:
造成这个问题的关键,在于客户端B持有的锁,被客户端A释放了。
锁的释放必须使用lua脚本,保证操作的原子性。锁的释放包含了get,判断,del三个步骤。如果不能保证三个步骤的原子性,分布式锁就会有并发问题。
注意了以上细节,一个单redis节点的分布式锁就达成了。
在这个分布式锁中还是存在一个单点的redis。也许你会说,Redis是 master-slave的架构,发生故障的时候切换到slave就好,但是Redis的复制是异步的。
这样由于Master的宕机,造成了同时多人持有锁。如果你的系统可用接受短时时间内,有多人持有锁。这个简单的方案就能解决问题。
但是如果解决这个问题。Redis的官方提供了一个Redlock的解决方案。
RedLock 的实现
为了解决,Redis单点的问题。Redis的作者提出了RedLock的解决方案。方案非常的巧妙和简洁。
RedLock的核心思想就是,同时使用多个Redis Master来冗余,且这些节点都是完全的独立的,也不需要对这些节点之间的数据进行同步。
假设我们有N个Redis节点,N应该是一个大于2的奇数。RedLock的实现步骤:
对于释放锁的实现就很简单了。想所有的Redis节点发起释放的操作,无论之前是否获取锁成功。
同时需要注意几个细节:
重试获取锁的间隔时间应当是一个随机范围而非一个固定时间。这样可以防止,多客户端同时一起向Redis集群发送获取锁的操作,避免同时竞争。同时获取相同数量锁的情况。(虽然概率很低)
如果某master节点故障之后,回复的时间间隔应当大于锁的有效时间。
所以如果恢复的时间将大于锁的有效时间,就可以避免以上情况发生。同时如果性能要求不高,甚至可以开启Redis的持久化选项。
总结
了解了Redis分布式的实现以后,其实觉得大多数的分布式系统其实原理很简单,但是为了保证分布式系统的可靠性需要注意很多的细节,琐碎异常。
RedLock算法实现的分布式锁就是简单高效,思路相当巧妙。
但是RedLock就一定安全么?我还会写一篇文章来讨论这个问题。敬请大家期待。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持VEVB武林网。
新闻热点
疑难解答