真正的 Redis 分布式锁,就该是这样实现的

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了真正的 Redis 分布式锁,就该是这样实现的相关的知识,希望对你有一定的参考价值。

参考技术A


众所周知,redis 分布式锁使用 SET 指令可以实现,但是仅仅使用该命令就行了吗?是否还需要考虑 CAP 理论。

要是有上面说的那么简单就好喽,我们平时在开发中用到的分布式锁方案可能比较简单,这个取决于业务的复杂程度以及并发量。

下面我们来说说在高并发场景中,该如何正确使用分布式锁。

在正式讲解分布式锁之前,先来看下将要围绕展开来讲的几个问题:

在同一时刻,只能有一个线程去读写一个【共享资源】,也就是高并发的场景下,通常为了保证数据的正确,需要控制同一时刻只允许一个线程访问。

此时就需要使用分布式锁了。

简而言之,分布式锁就是用来控制同一时刻,只有一个线程可以访问被保护的资源。

可以使用 SETNX key value 命令实现互斥的特性。

解释下:如果 key 不存在,则设置 value 给这个 key ,否则啥都不做。

该命令的返回值有如下两种情况:

举例如下:

成功的情况:

> SETNX lock:101 1 (integer) 1 # 获取 101 锁 成功

失败的情况:

> SETNX lock:101 2 (integer) 0 # 后面申请锁 获取失败

可见,成功的就可以开始使用「共享资源」了。

使用结束后,要及时释放锁,给后面申请获得资源的机会。

释放锁比较简单,使用 DEL 命令删除这个 key 就可以了。如下:

> DEL lock:101 (integer) 1

分布式锁简单使用方案如下:

这看起来不是挺简单的吗,能有什么问题?往下听我分析。

首先该方案存在一个锁无法被释放的问题,场景如下:

可见,这个锁就会一直被占用,导致其它客户端也拿不到这个锁了。

设置举例如下:

> SETNX lock:101 1 // 获取锁 (integer) 1

> EXPIRE lock:101 60 // 60s 过期删除 (integer) 1

可见,60 秒后后该锁就好释放掉,其他客户就可以申请使用了。

由上面举例可知: 加锁 设置过期时间 是两个操作命令,并不是原子操作。

试想一下,可能存在这么个情况:

比如执行第一条命了成功,第二条命令还没来得及执行就出现了异常,导致设置 过期时间」失败,这样锁也是无法释放。

SET keyName value NX PX 30000

这样一看,似乎没啥毛病。不,仔细一看,写的还是不够严谨。想下,有没可能释放的不是自己加的锁。

思考中……

说下什么场景下, 释放的锁不是自己的

所以,有个关键点需要注意的是:只能释放自己申请的锁。

总之,解铃还须系铃人

伪代码如下:

// 判断 value 与 锁的唯一标识

此时,我们可以考虑通过 Lua 脚本来实现,这样判断和删除的过程就是原子操作了。

// 获取锁的 value 值与 ARGV[1] 比较,匹配成功则执行 del

使用上面的脚本,为每个锁分配一个随机字符串“签名”,只有当删除锁的客户端的“签名”与锁的 value 匹配的时候,才会去删除它。

遇到问题不要慌,先从官方文档入手:redis.io/topics/dist…

到目前为止,以上修改后(优化后)的方案算相比较完善的了,业界大部分使用的也都是该方案。

当然这个锁的过期时间不能瞎写,通常是根据多次测试后的结果来做选择,比如压测多轮之后,平均执行时间在 300 ms。

那么我们的锁 过期时间就应该放大到平均执行时间的 3~4 倍。

有些小伙伴可能会问:为啥是放大 3~4 倍

这叫凡事留一手:考虑到锁的操作逻辑中如果有网络 IO 操作等,线上的网络不会总是稳定的,此时需要留点时间来缓冲。

加锁的时候设置一个过期时间,同时客户端开启一个「守护线程」,定时去检测这个锁的失效时间。

如果快要过期,但是业务逻辑还没执行完成,自动对这个锁进行续期,重新设置过期时间。

可以先谷歌一下,相信谷歌大哥会告诉你有这么一个库把这些工作都封装好了,你只管用就是了,它叫 Redisson

在使用分布式锁的时候,其实就是采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。

这个方案可以说很 OK 了,能想到这些的优化点已经击败一大批程序猿了。

对于追求极致的程序员来说,你们可能会考虑到:

这里就不展开讨论了。有兴趣的可以在评论区一起讨论交流哈。

以上是关于真正的 Redis 分布式锁,就该是这样实现的的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# 巧用Redis,实现分布式锁

正确的Redis分布式锁实现(Distributed lock)

用 MySQL 实现分布式锁,你听过吗?

这样实现Redis分布式锁最简单

如何使用redis实现分布式锁功能?

Redis分布式锁实现简单秒杀功能