redsi的无锁原子操作以及分布式锁
创始人
2025-05-29 03:48:49
0

并发访问控制对应的操作主要是数据修改操作。当客户端需要修改数据时,基本流程分成两步:
客户端先把数据读取到本地,在本地进行修改;
客户端修改完数据后,再写回 Redis。
我们把这个流程叫做“读取 - 修改 - 写回”操作(Read-Modify-Write,简称为 RMW 操作)。当有多个客户端对同一份数据执行 RMW 操作的话,我们就需要让 RMW 操作涉及的代码以原子性方式执行。访问同一份数据的 RMW 操作代码,就叫做临界区代码。
为了实现并发控制要求的临界区代码互斥执行,Redis 的原子操作采用了两种方法:
把多个操作在 Redis 中实现成一个操作,也就是单命令操作;
把多个操作写到一个Lua 脚本中,以原子性方式执行单个 Lua 脚本。
如果我们执行的 RMW 操作是对数据进行增减值的话,Redis 提供的原子操作 INCR 和 DECR 可以直接帮助我们进行并发控制。
但是,如果我们要执行的操作不是简单地增减数据,而是有更加复杂的判断逻辑或者是其他操作,那么,Redis 的单命令操作已经无法保证多个操作的互斥执行了。所以,这个时候,我们需要使用第二个方法,也就是 Lua 脚本。
Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性。使用 Redis 的 EVAL 命令来执行脚本。

redis实现分布式锁
在分布式系统中,当有多个客户端需要争抢锁时,我们必须要保证,这把锁不能是某个客户端本地的锁。否则的话,其它客户端是无法访问这把锁的,当然也就不能获取这把锁了。
在分布式场景下,锁变量需要由一个共享存储系统来维护,只有这样,多个客户端才可以通过访问共享存储系统来访问锁变量。相应的,加锁和释放锁的操作就变成了读取、判断和设置共享存储系统中的锁变量值。
我们就可以得出实现分布式锁的两个要求:

要求一:分布式锁的加锁和释放锁的过程,涉及多个操作。所以,在实现分布式锁时,我们需要保证这些锁操作的原子性
要求二:共享存储系统保存了锁变量,如果共享存储系统发生故障或宕机,那么客户端也就无法进行锁操作了。在实现分布式锁时,我们需要考虑保证共享存储系统的可靠性,进而保证锁的可靠性

基于单个 Redis 节点实现分布式锁
作为分布式锁实现过程中的共享存储系统,Redis 可以使用键值对来保存锁变量,再接收和处理不同客户端发送的加锁和释放锁的操作请求

在这里插入图片描述
因为加锁包含了三个操作(读取锁变量、判断锁变量值以及把锁变量值设置为 1),而这三个操作在执行时需要保证原子性。所以Redis 可以用单命令操作实现加锁操作。

首先是SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。

对于释放锁操作来说,我们可以在执行完业务逻辑后,使用 DEL 命令删除锁变量。
总结来说,我们就可以用 SETNX 和 DEL 命令组合来实现加锁和释放锁操作:
/ 加锁
SETNX lock_key 1
// 业务逻辑
DO THINGS
// 释放锁
DEL lock_key

这样有两个风险:
一个是加锁后可能在中间出现异常,导致锁无法释放,其他客户不能执行业务。解决办法是加过期时间,EX/PX选项。
一个是客户端A的锁可以被客户端B误释放。解决办法是加锁的时候设置一个独特的标识,释放时要比较是否相等,相等才能释放。
// 加锁, unique_value作为客户端唯一性的标识
SET lock_key unique_value NX PX 10000

// 业务逻辑
DO THINGS

//释放锁 比较unique_value是否相等,避免误释放
if redis.call(“get”,KEYS[1]) == ARGV[1] then
​ return redis.call(“del”,KEYS[1])
else
​ return 0
end

基于多个 Redis 节点实现高可靠的分布式锁
如果只用了一个 Redis 实例来保存锁变量,如果这个 Redis 实例发生故障宕机了,那么锁变量就没有了。此时,客户端也无法进行锁操作了,这就会影响到业务的正常执行。所以,我们在实现分布式锁时,还需要保证锁的可靠性。
为了避免 Redis 实例故障而导致的锁无法工作的问题,Redis 的开发者 Antirez 提出了分布式锁算法 Redlock

Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。

Redlock 算法的执行步骤:

第一步是,客户端获取当前时间。
第二步是,客户端按顺序依次向 N 个 Redis 实例执行加锁操作。
这里的加锁操作和在单实例上执行的加锁操作一样,使用 SET 命令,带上 NX,EX/PX 选项,以及带上客户端的唯一标识。当然,如果某个 Redis 实例发生故障了,为了保证在这种情况下,Redlock 算法能够继续运行,我们需要给加锁操作设置一个超时时间。
如果客户端在和一个 Redis 实例请求加锁时,一直到超时都没有成功,那么此时,客户端会和下一个 Redis 实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时间,一般也就是设置为几十毫秒。

第三步是,一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件一:客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁;
条件二:客户端获取锁的总耗时没有超过锁的有效时间。
在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。
当然,如果客户端在和所有实例执行完加锁操作后,没能同时满足这两个条件,那么,客户端向所有 Redis 节点发起释放锁的操作。

相关内容

热门资讯

图形视图框架QGraphics... QGraphicsView(图形视图) QGraphicsView提供了...
大数据学习(2) 大数据学习(2)0 数据仓库0.0 数据仓库基本概念0.1 数据仓库主要...
最新或2023(历届)小学生关... 小学生关于法制手抄报的图片模板  小学生关于法制手抄报图片1  小学生关于法制手抄报图片2  小学生...
【spring】javaCon... 目录一、xml形式二、javaConfig形式三、源码分析 一、xml形式 1.spring容器为...
最新或2023(历届)有关法制... 有关法制手抄报的图片模板  有关法制手抄报图片1  有关法制手抄报图片2  有关法制手抄报图片3  ...
最新或2023(历届)有关法制... 有关法制手抄报的图片参考  有关法制手抄报参考图片(1)  有关法制手抄报参考图片(2)  有关法制...
最新或2023(历届)初中生法... 初中生法制手抄报的图片参考  初中生法制手抄报参考图片(1)  初中生法制手抄报参考图片(2)  初...
最新或2023(历届)初中生法... 初中生法制手抄报的图片模板  初中生法制手抄报图片1  初中生法制手抄报图片2  初中生法制手抄报图...
2022湖北省赛 L 裸线段树 有人不会裸线段树有人没有pushdown调了两小时裸线段树早该414了L (codeforces.c...
最新或2023(历届)初中生法... 初中生法制手抄报的图片模板  初中生法制手抄报图片1  初中生法制手抄报图片2  初中生法制手抄报图...
Chapter2.3:线性表的... 该系列属于计算机基础系列中的《数据结构基础》子系列,参考书《数据结构考研复习指导》(王...
最新或2023(历届)初中生法... 初中生法制手抄报的图片参考  初中生法制手抄报参考图片(1)  初中生法制手抄报参考图片(2)  初...
最新或2023(历届)关于简单... 关于简单的法制教育手抄报的图片模板  关于简单的法制教育手抄报图片1  关于简单的法制教育手抄报图片...
最新或2023(历届)关于简单... 关于简单的法制教育手抄报的图片参考  关于简单的法制教育手抄报参考图片(1)  关于简单的法制教育手...
最新或2023(历届)中学生法... 中学生法制教育手抄报的图片模板  中学生法制教育手抄报图片1  中学生法制教育手抄报图片2  中学生...
多种方法跳出线程发包         明文发包CALL是分析一款游戏功能的主要突破口,但是很多游戏都是线程发...
GDKOI2023 D1T1 前言 考场上没想出来,收到来自题目标题的嘲讽 题目大意 求 ∑i=1ni!ik...
Java 造轮子例子 1.要规范地造好一个轮子,以下是一些步骤和建议: 确定你的轮子的功能和用...
最新或2023(历届)中学生法... 中学生法制教育手抄报的图片参考  中学生法制教育手抄报参考图片(1)  中学生法制教育手抄报参考图片...
KubeSphere 社区双周... KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 ...