当前位置:网站首页>Redis实现分布式锁原理详解
Redis实现分布式锁原理详解
2022-07-06 09:20:00 【李孛欢】
分布式锁要求
在分布式系统中,当有多个客户端需要获取锁时,我们需要分布式锁。此时,锁是保存在一个共享存储系统中的,可以被多个客户端共享访问和获取。
分布式锁可以用一个变量来实现。客户端加锁和释放锁的操作逻辑:加锁时,先获取锁变量的值,再根据锁变量值是否为0来判断能否加锁成功;释放锁时需要把锁变量值设置为 0,表明客户端不再持有锁。在分布式场景下,锁变量需要由一个共享存储系统来维护,只有这样,多个客户端才可以通过访问共享存储系统来访问锁变量。相应的,加锁和释放锁的操作就变成了读取、判断和设置共享存储系统中的锁变量值。
从上述过程可以看出来,分布式锁有以下两个要求:
- 分布式锁的加锁和释放锁的过程,涉及多个操作。所以,在实现分布式锁时,我们需要保证这些锁操作的原子性;
- 共享存储系统保存了锁变量,如果共享存储系统发生故障或宕机,那么客户端也就无法进行锁操作了。在实现分布式锁时,我们需要考虑保证共享存储系统的可靠性,进而保证锁的可靠性
作为分布式锁实现过程中的共享存储系统,Redis 可以使用键值对来保存锁变量,再接收和处理不同客户端发送的加锁和释放锁的操作请求。我们用lock_key作为键,来设置锁变量的值。
首先我们来看加锁操作,包含了三个动作:读取锁变量,判断锁变量和将锁变量的值修改为1。而这三个操作在执行时需要保证原子性。那怎么保证原子性呢?
Redis提供的原子操作
为了实现并发控制要求的临界区代码互斥执行,Redis 的原子操作采用了两种方法:
- 把多个操作在 Redis 中实现成一个操作,也就是单命令操作;
- 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。
Redis 是使用单线程来串行处理客户端的请求操作命令的,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。当然,Redis 的快照生成、AOF 重写这些操作,可以使用后台线程或者是子进程执行,也就是和主线程的操作并行执行。不过,这些操作只是读取数据,不会修改数据,所以,我们并不需要对它们做并发控制。
可以看到redis的单个命令操作可以原子性地执行,但是在实际应用中,数据的修改可能需要多个操作,对于这种情况redis提供了INCR/DECR 命令,把这三个操作转变为一个原子操作了。INCR/DECR 命令可以对数据进行增值 / 减值操作,而且它们本身就是单个命令操作,Redis 在执行它们时,本身就具有互斥性。
但是,如果我们要执行的操作不是简单地增减数据,而是有更加复杂的判断逻辑或者是其他操作,那么,Redis 的单命令操作已经无法保证多个操作的互斥执行了。所以,这个时候,我们需要使用第二个方法,也就是 Lua 脚本。 Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入,因此在脚本运行过程中无需担心会出现竞态条件。我们可以使用 Redis 的 EVAL 命令来执行脚本。假设我们编写的脚本名称为 lua.script,我们可以使用 Redis 客户端,带上 eval 选项,来执行该脚本。脚本所需的参数将通过以下命令中的 keys 和 args 进行传递。
redis-cli --eval lua.script keys , args
Redis实现分布式锁
我们可以用SETNX 单命令设置键值对的值。具体来说,就是这个命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。使用redisTemplate操作SetNx会返回一个布尔值,根据布尔值判断是否获得了锁。 对于释放锁操作来说,我们可以在执行完业务逻辑后,使用 DEL 命令删除锁变量。SETNX就包括了获取锁变量值和判断的过程。
// 加锁
SETNX lock_key 1
// 业务逻辑
DO THINGS
// 释放锁
DEL lock_key
不过使用SETNX和DEL命令组合的方式实现分布式锁会出现以下问题:
- 某个客户端执行了SETNX却在处理业务逻辑时出现异常,未执行DEL操作,导致其他客户端无法获取锁。
- 某个客户端A执行了SETNX,而另一个客户端B在其处理业务逻辑时执行了DEL命令,然后客户端C又执行了SETNX获取了锁,与A一起操作共享数据,导致错误。
对于第一个问题,一个有效的解决方法是,给锁变量设置一个过期时间。 对于第二个问题,我们需要能区分来自不同客户端的锁操作。具体操作是,在加锁操作时,可以让每个客户端给锁变量设置一个唯一值,这里的唯一值就可以用来标识当前操作的客户端。在释放锁操作时,客户端需要判断,当前锁变量的值是否和自己的唯一标识相等,只有在相等的情况下,才能释放锁。这样一来,就不会出现误释放锁的问题了。具体实现如下:
// 加锁, unique_value作为客户端唯一性的标识
SET lock_key unique_value NX PX 10000
其中,unique_value 是客户端的唯一标识,可以用一个随机生成的字符串来表示,PX 10000 (ms 相当于EX 10 s)则表示 lock_key 会在 10s 后过期,以免客户端在这期间发生异常而无法释放锁。因为在加锁操作中,每个客户端都使用了一个唯一标识,所以在释放锁操作时,我们需要判断锁变量的值,是否等于执行释放锁操作的客户端的唯一标识。这个时候就要用到Lua脚本了,因为,放锁操作的逻辑也包含了读取锁变量、判断值、删除锁变量的多个操作。
分布式锁的可靠性保证
为了避免 Redis 实例故障而导致的锁无法工作的问题,Redis 的开发者 Antirez 提出了分布式锁算法 Redlock。Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。Redlock 算法可以分为三步:
- 客户端获取当前时间
- 客户端按顺序依次向 N 个 Redis 实例执行加锁操作,与上述加锁操作不同的是会给加锁操作设置一个超时时间,如果加锁操作超时就会和下一个redis实例进行加锁
- 一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
- 条件一:客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
- 条件二:客户端获取锁的总耗时没有超过锁的有效时间。
在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。释放锁的操作和在单例上释放锁一样。
边栏推荐
猜你喜欢
Questions and answers of "basic experiment" in the first semester of the 22nd academic year of Xi'an University of Electronic Science and technology
(超详细onenet TCP协议接入)arduino+esp8266-01s接入物联网平台,上传实时采集数据/TCP透传(以及lua脚本如何获取和编写)
1.C语言初阶练习题(1)
2.C语言矩阵乘法
Cloud native trend in 2022
fianl、finally、finalize三者的区别
IPv6 experiment
关于双亲委派机制和类加载的过程
ABA问题遇到过吗,详细说以下,如何避免ABA问题
Conceptual model design of the 2022 database of tyut Taiyuan University of Technology
随机推荐
Alibaba cloud microservices (II) distributed service configuration center and Nacos usage scenarios and implementation introduction
Arduino+ water level sensor +led display + buzzer alarm
西安电子科技大学22学年上学期《射频电路基础》试题及答案
3. C language uses algebraic cofactor to calculate determinant
西安电子科技大学22学年上学期《信号与系统》试题及答案
Wei Pai: the product is applauded, but why is the sales volume still frustrated
C语言入门指南
Set container
12 excel charts and arrays
4. Binary search
(ultra detailed onenet TCP protocol access) arduino+esp8266-01s access to the Internet of things platform, upload real-time data collection /tcp transparent transmission (and how to obtain and write L
MySQL中count(*)的实现方式
There is always one of the eight computer operations that you can't learn programming
fianl、finally、finalize三者的区别
Share a website to improve your Aesthetics
TYUT太原理工大学2022“mao gai”必背
5.MSDN的下载和使用
System design learning (I) design pastebin com (or Bit.ly)
Tyut Taiyuan University of technology 2022 "Mao Gai" must be recited
Relational algebra of tyut Taiyuan University of technology 2022 database