目录
分布式锁的基础实现
引入过期时间
引入校验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] thenreturn redis.call('del',KEYS[1])
elsereturn 0
end;
引入看门狗
在加锁的时候,key的过期时间设定多长合适?
如果设置的短,可能在你的业务还执行完,就释放锁了。如果设置的太长,也会导致"锁释放不及时"问题。
更好的方式,是“动态续约”。初始情况下,设置一个过期时间(比如1s)就提前在还剩300ms(不一定,可以灵活调整)的时候,如果当前任务还没执行完,就把过期时间再续1s。等到时间又快到了,任务还没执行完,就再续。
如果服务器,中途崩溃了,自然就没人负责续约了,此时,锁就能在较短的时间内被自动释放。
动态续约这样的行为往往需要一个专门的线程,负责续约这个事情。称为“看门狗”。
redlock算法
使用redis作为分布式锁,redis本身有没有可能挂了呢? 当然有可能。
要想保证“高可用”就需要通过这样一系列的“预案演习”。
进行加锁,就是把key设置到主节点上,如果主节点挂了,有哨兵自动的把从节点升级成主节点,进一步的保证刚才的锁仍然可用。
但是主节点和从节点之间的数据同步,是存在延时的。可能主节点收到了set请求,还没来得及同步给从节点,主节点就先挂了。即使从节点升级成了主节点。但是,刚才的加锁对应的数据,也是不存在的。
Redlock 算法:
引⼊⼀组 Redis 节点。其中每⼀组 Redis 节点都包含⼀个主节点和若⼲从节点. 并且组和组之间存
储的数据都是⼀致的,相互之间是 "备份" 关系。
此处加锁,就是按照一定的顺序,针对这些组redis都进行加锁操作。如果某个节点挂了,继续给下一个加锁即可。
如果写入key成功的节点个数超过总数的一半,就视为加锁成功。
同理,进行解锁的时候,也会把上述节点都设置一遍。
以上,关于redis的分布式锁,希望对你有所帮助。