关于后端:面试官如何用-Redis-实现分布式锁

248次阅读

共计 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 实现分布式锁的 长处

  1. 性能高效(这是抉择缓存实现分布式锁最外围的出发点)。
  2. 实现不便。很多研发工程师抉择应用 Redis 来实现分布式锁,很大成分上是因为 Redis 提供了 setnx 办法,实现分布式锁很不便。
  3. 防止单点故障(因为 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 脚本就能够了。

正文完
 0