共计 2530 个字符,预计需要花费 7 分钟才能阅读完成。
目录
- 如何用 Redis 实现分布式锁?
- Redis 是如何解决集群状况下分布式锁的可靠性问题的?
如何用 Redis 实现分布式锁的?
分布式锁是用于分布式环境下并发管制的一种机制,用于管制某个资源在同一时刻只能被一个利用所应用。如下图所示:
[图片上传失败 …(image-257e52-1662170462486)]
Redis 自身能够被多个客户端共享拜访,正好就是一个共享存储系统,能够用来保留分布式锁,而且 Redis 的读写性能高,能够应答高并发的锁操作场景。
Redis 的 SET 命令有个 NX 参数能够实现「key 不存在才插入」,所以能够用它来实现分布式锁:
- 如果 key 不存在,则显示插入胜利,能够用来示意加锁胜利;
- 如果 key 存在,则会显示插入失败,能够用来示意加锁失败。
基于 Redis 节点实现分布式锁时,对于加锁操作,咱们须要满足三个条件。
- 加锁包含了读取锁变量、查看锁变量值和设置锁变量值三个操作,但须要以原子操作的形式实现,所以,咱们应用 SET 命令带上 NX 选项来实现加锁;
- 锁变量须要设置过期工夫,免得客户端拿到锁后产生异样,导致锁始终无奈开释,所以,咱们在 SET 命令执行时加上 EX/PX 选项,设置其过期工夫;
- 锁变量的值须要能辨别来自不同客户端的加锁操作,免得在开释锁时,呈现误开释操作,所以,咱们应用 SET 命令设置锁变量值时,每个客户端设置的值是一个惟一值,用于标识客户端;
满足这三个条件的分布式命令如下:
SET lock_key unique_value NX PX 10000
- lock_key 就是 key 键;
- unique_value 是客户端生成的惟一的标识,辨别来自不同客户端的锁操作;
- NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
- PX 10000 示意设置 lock_key 的过期工夫为 10s,这是为了防止客户端产生异样而无奈开释锁。
而解锁的过程就是将 lock_key 键删除(del lock_key),但不能乱删,要保障执行操作的客户端就是加锁的客户端。所以,解锁的时候,咱们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
能够看到,解锁是有两个操作,这时就须要 Lua 脚本来保障解锁的原子性,因为 Redis 在执行 Lua 脚本时,能够以原子性的形式执行,保障了锁开释操作的原子性。
// 开释锁时,先比拟 unique_value 是否相等,防止锁的误开释
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
这样一来,就通过应用 SET 命令和 Lua 脚本在 Redis 单节点上实现了分布式锁的加锁和解锁。
基于 Redis 实现分布式锁有什么优缺点?
基于 Redis 实现分布式锁的 长处:
- 性能高效(这是抉择缓存实现分布式锁最外围的出发点)。
- 实现不便。很多研发工程师抉择应用 Redis 来实现分布式锁,很大成分上是因为 Redis 提供了 setnx 办法,实现分布式锁很不便。
- 防止单点故障(因为 Redis 是跨集群部署的,天然就防止了单点故障)。
基于 Redis 实现分布式锁的 毛病:
- 超时工夫不好设置。如果锁的超时工夫设置过长,会影响性能,如果设置的超时工夫过短会爱护不到共享资源。比方在有些场景中,一个线程 A 获取到了锁之后,因为业务代码执行工夫可能比拟长,导致超过了锁的超时工夫,主动生效,留神 A 线程没执行完,后续线程 B 又意外的持有了锁,意味着能够操作共享资源,那么两个线程之间的共享资源就没方法进行爱护了。
- 那么如何正当设置超时工夫呢? 咱们能够基于续约的形式设置超时工夫:先给锁设置一个超时工夫,而后启动一个守护线程,让守护线程在一段时间后,从新设置这个锁的超时工夫。实现形式就是:写一个守护线程,而后去判断锁的状况,当锁快生效的时候,再次进行续约加锁,当主线程执行实现后,销毁续约锁即可,不过这种形式实现起来绝对简单。
- Redis 主从复制模式中的数据是异步复制的,这样导致分布式锁的不可靠性。如果在 Redis 主节点获取到锁后,在没有同步到其余节点时,Redis 主节点宕机了,此时新的 Redis 主节点仍然能够获取锁,所以多个应用服务就能够同时获取到锁。
Redis 如何解决集群状况下分布式锁的可靠性?
为了保障集群环境下分布式锁的可靠性,Redis 官网曾经设计了一个分布式锁算法 Redlock(红锁)。
它是基于多个 Redis 节点的分布式锁,即便有节点产生了故障,锁变量依然是存在的,客户端还是能够实现锁操作。
Redlock 算法的基本思路,是让客户端和多个独立的 Redis 节点顺次申请申请加锁,如果客户端可能和半数以上的节点胜利地实现加锁操作,那么咱们就认为,客户端胜利地取得分布式锁,否则加锁失败。
这样一来,即便有某个 Redis 节点产生故障,因为锁的数据在其余节点上也有保留,所以客户端依然能够失常地进行锁操作,锁的数据也不会失落。
Redlock 算法加锁三个过程:
- 第一步是,客户端获取以后工夫。
- 第二步是,客户端按程序顺次向 N 个 Redis 节点执行加锁操作:
- 加锁操作应用 SET 命令,带上 NX,EX/PX 选项,以及带上客户端的惟一标识。
- 如果某个 Redis 节点产生故障了,为了保障在这种状况下,Redlock 算法可能持续运行,咱们须要给「加锁操作」设置一个超时工夫(不是对「锁」设置超时工夫,而是对「加锁操作」设置超时工夫)。
- 第三步是,一旦客户端实现了和所有 Redis 节点的加锁操作,客户端就要计算整个加锁过程的总耗时(t1)。
加锁胜利要同时满足两个条件(简述:如果有超过半数的 Redis 节点胜利的获取到了锁,并且总耗时没有超过锁的无效工夫,那么就是加锁胜利):
- 条件一:客户端从超过半数(大于等于 N/2+1)的 Redis 节点上胜利获取到了锁;
- 条件二:客户端获取锁的总耗时(t1)没有超过锁的无效工夫。
加锁胜利后,客户端须要从新计算这把锁的无效工夫,计算的后果是「锁的最后无效工夫」减去「客户端为获取锁的总耗时(t1)」。
加锁失败后,客户端向所有 Redis 节点发动开释锁的操作,开释锁的操作和在单节点上开释锁的操作一样,只有执行开释锁的 Lua 脚本就能够了。