Redis应用--分布式锁

目录

一、什么是分布式锁

二、分布式锁的基础实现

三、引入过期时间

四、引入校验id

五、引入 watch dog(看门狗)

六、引入 Redlock 算法


一、什么是分布式锁

在一个分布式系统中,也会涉及到多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于"线程安全"的问题。

而 java 的 synchronized 或者 C++ 的 std::mutex, 这样的锁都是只能在当前进程中生效, 在分布式的这种多个进程多个主机的场景下就无能为力了.
这时候就需要用到分布式锁了。 利用Redis来做分布式锁本质上就是使用了一个公共的服务器来记录加锁状态,这个公共的服务可以是Redis也可以是其它的组件。

二、分布式锁的基础实现

思路非常简单,本质上就是通过一个键值对来标识锁的状态。

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

很显然上述场景是存在“线程安全”问题的,需要使用锁来控制,否则会出现超卖现象。

此时我们可以在上述架构中引入Redis作为分布式锁的管理器。

此时,如果买票服务器1尝试买票就需要先访问Redis服务器,在Redis设置一个键值对,比如key就设置为车次,value随便设置个值。如果这个操作设置成功,则说明当前没有节点对该车次加锁,就可以进行数据库的读写操作,操作完成之后,再把Redis上刚才这个键值对删除掉。

如果在买票服务器1操作数据库的过程中,买票服务器2也想买票,也会尝试给Redis上写一个键值对,key同样是车次,但此时发现Redis上已经存在这个key了,则认为有其他服务器正在持有锁,此时买票服务器2就需要等待或者暂时放弃。

三、引入过期时间

在上述过程中,服务器1加锁之后,开始处理买票逻辑时如果意外宕机了, 就会导致解锁操作(删除key的过程)无法执行,就可能会引起其他服务器始终无法获取到锁的情况。

为了解决这个问题,我们可以在设置key的同时引入过期时间,即这个锁最多持有多久,就应该被释放。(可以使用 set ex nx 的方式在设置锁的同时把过期时间设置进去,这里的过期时间只能用一个命令的方式设置)

四、引入校验id

对于Redis的加锁键值对,其他的节点也是可以删除的。比如服务器1写入一个键值对,服务器2完全可以将这个键值对给删除掉的。

为了解决上述问题我们可以引入一个校验id

比如可以把设置的键值对的值,不再是简单的设为一个 1 ,而是设成服务器的编号,形如 “001”:“服务器1”.

这样就可以在删除key(解锁)的时候,先校验当前删除 key 的服务器是否是当初加锁的那个服务器,如果是,才能删除。

伪代码表示如下:

String key = [要加锁的资源 id];
String serverId = [服务器的编号];
// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");
// 执⾏各种业务逻辑, ⽐如修改数据库数据. 
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配. 
if (redis.get(key) == serverId) {
 redis.del(key);
}

五、引入 watch dog(看门狗)

上述方案仍然存在⼀个重要问题. 当我们设置了 key 过期时间之后 (比如 10s), 仍然存在⼀定的可能性,当任务还没执行完, key 就先过期了. 这就导致锁提前失效.
我们可以思考一下,如果我们选择把这个过期时间设置的足够长的话,很明显,这个足够长是无止境的,也就是说无论我们设置的再长,也无法保证就完全没有提前失效的情况,而且如果设置的太长,如果对应的服务器挂了,其他服务器也不能及时的获取到锁,因此相比于设置一个固定的时间来说,不如动态的调整时间。
所谓的 watch dog 本质上是加锁的服务器的一个单独的线程,通过这个线程来对过期时间进行延长。
举个例子来说:
初始情况下设置过期时间为10s,同时设置看门狗线程每3s检测一次。
那么当3s时间到了的时候,看门狗就会判定当前任务是否完成
     如果任务已经完成,则通过lua脚本或者Redis事务的方式释放锁
     如果任务未完成则把过期时间重写为10s
这样就不担心锁提前失效的问题了,而且另一方面如果该服务器挂了,看门狗也就随之挂了,此时这个key就会迅速过期让其他服务器可以获取到锁。

六、引入 Redlock 算法

实践中的 Redis ⼀般是以集群的方式部署的 (至少是主从的形式, 而不是单机). 那么就可能出现以下比较极端的大冤种情况:
服务器1 向 master 节点进行加锁操作. 这个写入 key 的过程刚刚完成, master 挂了; slave 节
点升级成了新的 master 节点. 但是由于刚才写入的这个 key 尚未来得及同步给 slave 呢, 此时
就相当于 服务器1 的加锁操作形同虚设了,
为了解决这个问题,Redis作者提出了Redlock算法
我们引入一组 Redis 节点. 其中每⼀组 Redis 节点都包含⼀个主节点和若干从节点. 并且组和组之间存储的数据都是⼀致的, 相互之间是 "备份" 关系.加锁的时候, 按照⼀定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". 比如50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败

如果给某个节点加锁失败就立即尝试下一个节点,当加锁成功的节点超过了总节点数的一半,才视为加锁成功,这样即便有节点挂了,也不影响锁的正确性。

同理, 释放锁的时候, 也需要把所有节点都进行解锁操作. (即使是之前超时的节点, 也要尝试解锁, 尽量保证逻辑严密)

简而言之, Redlock 算法的核心就是, 加锁操作不能只写给⼀个 Redis 节点, 而要写个多个!! 分布式系统中任何⼀个节点都是不可靠的. 最终的加锁成功结论是 "少数服从多数的".
由于⼀个分布式系统不至于大部分节点都同时出现故障, 因此这样的可靠性要比单个节点来说靠谱不少.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值