注册

Redis分布式锁



需求

分布式应⽤进⾏逻辑处理时经常会遇到并发问题。

互斥访问某个网络上的资源,需要有一个存在于网络上的锁服务器,负责锁的申请与回收。Redis 可以充当锁服务器的角色。首先,Redis 是单进程单线程的工作模式,所有前来申请锁资源的请求都被排队处理,能保证锁资源的同步访问。

适用原因:

  • Redis 可以被多个客户端共享访问,是·共享存储系统,可以用来保存分布 式锁

  • Redis 的读写性能高,可以应对高并发的锁操作场景。

实现

在分布式场景下,锁变量需要由一个共享存储系统来维护,这样,多个客户端可以通过访问共享存储系统来访问锁变量。

简单实现

模仿单机上的锁,使用锁变量即可在Redis上实现分布式锁。

我们可以在 Redis 服务器设置一个键值对,用以表示一把互斥锁,当申请锁的时候,要求申请方设置(SET)这个键值对,当释放锁的时候,要求释放方删除(DEL)这个键值对。

但最基本需要保证加锁解锁操作的原子性。同时为了保证锁在异常情况下能被释放,必须设置超时时间。

Redis 2.8 版本中作者加⼊了 set 指令的扩展参数,使得 setnx 和 expire 指令可以⼀起执⾏

加锁原子操作

加锁包含了三个操作(读取锁变量、判断锁变量值以及把锁变量值设置为 1),而这 三个操作在执行时需要保证原子性。

首先是 SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键 值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。

超时问题

Redis 的分布式锁不能解决超时问题,如果在加锁和释放锁之间的逻 辑执⾏的太长,超出了锁的超时限制,就无法保证互斥。

解决方案

最简单的就是避免Redis 分布式锁⽤于较⻓时间的任务。如果真的偶尔出现了,数据出现的⼩波错乱可能需要⼈⼯介⼊解决。

判断拥有者

为了防止锁变量被拥有者之外的客户端进程删除,需要能区分来自不同客户端的锁操作

set 指令的 value 参数设置为⼀个 随机数,释放锁时先匹配随机数是否⼀致,然后再删除 key,这是为 了确保当前线程占有的锁不会被其它线程释放,除⾮这个锁是过期了被服务器⾃动释放的。

Redis 给 SET 命令提供 了类似的选项 NX,用来实现“不存在即设置”。如果使用了 NX 选项,SET 命令只有在键 值对不存在时,才会进行设置,否则不做赋值操作。此外,SET 命令在执行时还可以带上 EX 或 PX 选项,用来设置键值对的过期时间。

可重入问题

可重⼊性是指线程在持有锁的情况下再次请求加锁,如果⼀个锁⽀持 同⼀个线程的多次加锁,那么这个锁就是可重⼊的。Redis 分布式锁如果要⽀持 可重⼊,需要对客户端的 set ⽅法进⾏包装,使⽤线程的 Threadlocal 变量存储当前持有锁的计数。

分布式拓展

单Redis实例并不能满足我们的高可用要求,一旦实例崩溃,就无法对外分布式锁服务。

但在集群环境下,这种只对主Redis实例使用上述方案是有缺陷 的,它不是绝对安全的。

一旦主节点挂掉,但锁变量没有及时同步,就会导致互斥被破坏。

Redlock

为了解决这个问题,Antirez 发明了 Redlock 算法

Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户 端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布 式锁了,否则加锁失败。

执行步骤:

  • 获取当前时间

  • 客户端按顺序依次向 N 个 Redis 实例执行加锁操作

  • 一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过 程的总耗时。客户端只有在满足下面的这两个条件时,才能认为是加锁成功

    • 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;

    • 客户端获取锁的总耗时没有超过锁的有效时间。


作者:不是二向箔
来源:https://juejin.cn/post/7038155529566289957

0 个评论

要回复文章请先登录注册