关于redis中的分布式锁

目录

分布式锁的基础实现

引入过期时间

引入校验id

引入lua脚本

引入看门狗

redlock算法


分布式锁的基础实现

多个线程并发执行的时候,执行的先后顺序是不确定的,需要保证程序在任意执行顺序下,执行逻辑都是ok的。

在分布式系统中,每个服务器都是独立的进程,因此,之前的锁,就难以对现在分布式系统中的多个进程之间产生制约。分布式系统中,多个进程之间的执行顺序也是不确定的。

像这样的情况就需要引入分布式锁来解决上述问题。

举个例⼦: 考虑买票的场景, 现在⻋站提供了若⼲个⻋次, 每个⻋次的票数都是固定的。现在存在多个服务器节点, 都可能需要处理这个买票的逻辑: 先查询指定⻋次的余票, 如果余票 > 0, 则设置余票值 -= 1。

显然上述的场景是存在 "线程安全" 问题的, 需要使⽤锁来控制。否则就可能出现 "超卖" 的情况。

此时如何进⾏加锁呢? 我们可以在上述架构中引⼊⼀个 Redis ,作为分布式锁的管理器。给其他的服务器提供"加锁"这样的服务。(redis是一种典型的可以用来实现分布式锁的方案,但不是唯一一种)

买票服务器,在进行买票的过程中,就需要先加锁(往redis上设置一个特殊的key-value)。完成上述买票操作之后,再把这个key-value删除掉。

其他服务器买票的时候,也要去redis上设置key-value,如果发现key-value已经存在,就认为"加锁失败"。(放弃/阻塞,就看具体的实现策略了)

上述操作就可以保证,第一个服务器执行"查询 ->更新"过程中,第二个服务器不会执行"查询"。

引入过期时间

如果服务器直接掉电,进程异常终止,这样的情况会导致redis上设置的key无人删除,也就导致其他服务器无法获取到锁了。这种情况该如何处理?

可以给key设置过期时间,一旦时间到,key就会自动被删除掉了。

可以通过 set ex nx 命令来实现。

比如设置key的过期时间为1000ms,那么意味着即使出现极端情况,某个服务器挂了,没有正确释放锁,这个锁最多保持1000ms,也就会自动释放了。

引入校验id

所谓的加锁,就是给redis上设置一个key-value。

所谓的解锁,就是给redis上这个key-value删除掉。

是否可能出现服务器1执行了加锁,服务器2执行了解锁

有可能的。服务器有可能不小心执行到了解锁操作,因此就可能进一步给整个系统带来更严重的问题。

为了解决上述问题,就需要引入校验机制

1.给服务器编号,每个服务器有一个自己的身份标识

2.进行加锁的时候,设置key-value,key对应着要针对哪个资源加锁(比如车次),value就可以存储刚才服务器的编号。表示出当前这个锁是哪个服务器加上的。

3.后续在解锁的时候,就可以进行校验了。(解锁的时候,先查询一下这个锁对应的服务器编号。然后判定一下这个编号是否就是当前解锁的服务器的编号,如果是,才能真正执行del。如果不是,就失败)

引入lua脚本

在解锁的时候,先查询判定,再进行del。此处两步操作(不是原子的),就可能会出现问题。

一个服务器内部,也可能是多线程的。此时,就可能同一个服务器,两个线程都在执行上述解锁操作。

在线程A执行完DEL之后,B执行DEL之前。服务器2的线程C正好要执行加锁(set),此时由于A已经把锁释放了,C的加锁是能够成功的。因为在线程B查询判定的时候,redis服务器已经判定这次请求来源于一个服务器了。但是紧接着,线程B DEL 就到来了。就把刚刚服务器2的加锁操作给解锁了。

可以使用lua脚本来解决上述问题。

可以使用lua编写一些逻辑,把这个脚本上传到redis服务器上。然后就可以让客户端来控制redis执行上述脚本了。

redis执行脚本的过程,也是原子的。相当于执行一条命令一样。

使⽤ Lua 脚本完成上述解锁功能:

复制代码
if redis.call('get',KEYS[1]) == ARGV[1] then
   return redis.call('del',KEYS[1])
else
   return 0
end;

引入看门狗

在加锁的时候,key的过期时间设定多长合适?

如果设置的短,可能在你的业务还执行完,就释放锁了。如果设置的太长,也会导致"锁释放不及时"问题。

更好的方式,是"动态续约"。初始情况下,设置一个过期时间(比如1s)就提前在还剩300ms(不一定,可以灵活调整)的时候,如果当前任务还没执行完,就把过期时间再续1s。等到时间又快到了,任务还没执行完,就再续。

如果服务器,中途崩溃了,自然就没人负责续约了,此时,锁就能在较短的时间内被自动释放。

动态续约这样的行为往往需要一个专门的线程,负责续约这个事情。称为"看门狗"。

redlock算法

使用redis作为分布式锁,redis本身有没有可能挂了呢? 当然有可能。

要想保证"高可用"就需要通过这样一系列的"预案演习"。

进行加锁,就是把key设置到主节点上,如果主节点挂了,有哨兵自动的把从节点升级成主节点,进一步的保证刚才的锁仍然可用。

但是主节点和从节点之间的数据同步,是存在延时的。可能主节点收到了set请求,还没来得及同步给从节点,主节点就先挂了。即使从节点升级成了主节点。但是,刚才的加锁对应的数据,也是不存在的。

Redlock 算法:

引⼊⼀组 Redis 节点。其中每⼀组 Redis 节点都包含⼀个主节点和若⼲从节点. 并且组和组之间存

储的数据都是⼀致的,相互之间是 "备份" 关系。

此处加锁,就是按照一定的顺序,针对这些组redis都进行加锁操作。如果某个节点挂了,继续给下一个加锁即可。

如果写入key成功的节点个数超过总数的一半,就视为加锁成功。

同理,进行解锁的时候,也会把上述节点都设置一遍。

以上,关于redis的分布式锁,希望对你有所帮助。

相关推荐
MuYiLuck7 分钟前
【redis实战篇】第八天
数据库·redis·缓存
睡觉待开机8 分钟前
6. MySQL基本查询
数据库·mysql
后端码匠9 分钟前
Spark 单机模式部署与启动
大数据·分布式·spark
�FENG34 分钟前
Redis 安装配置和性能优化
redis·持久化
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
Dnui_King1 小时前
Kafka 入门指南与一键部署
分布式·kafka
大熊猫侯佩1 小时前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩1 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
Ares-Wang1 小时前
负载均衡LB》》HAproxy
运维·数据库·负载均衡