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