Redis核心技术与实战【学习笔记】 - 20.Redis原子操作及并发访问

概述

使用 Redis 时,不可避免地会遇到并发访问的问题,比如说如果多个用户同时下单,就会对缓存在 Redis 中的商品库存并发更新。一旦有了并发写操作,数据就会被修改,如果我们没有对并发写请求做好控制,就可能导致数据被改错,影响业务的正常使用(例如,库存数据错误,导致下单异常)。

为了保证并发访问的正确性,Redis 提供了两种方法,分别是加锁和原子操作。当一个客户端获得锁后,就会一直持有这把锁,直到客户端完成数据更新,才释放这把锁。

但是用锁会有两个问题:

  • 一个是,如果加锁操作多,会降低系统的并发访问性能。
  • 第二个是,Redis 客户端需要加锁时,需要用到分布式锁,而分布式锁实现复杂,需要用额外的存储系统来提供枷锁解锁操作。

原子操作是另一种提供并发访问控制的方法。原子操作是指执行过程保持原子性的操作,而且原子操作执行时并不需要加锁,实现了无锁操作。这样一来,既能保证并发控制,还能减少对系统并发性能的影响。


1.并发访问中需要对什么进行控制?

并发访问控制是指对多个客户端访问操作同一份数据的过程进行控制,以保证任何一个客户端发送的操作在 Redis 实例上执行具有互斥性。

并发访问控制对应的操作主要是数据修改操作。当客户端需要修改数据时,基本流程分成两步:

  1. 客户端先把数据读取到本地,在本地进行修改
  2. 客户端修改完数据后,再写回 Redis。

这个流程叫做“读取 - 修改 - 写回”操作(Read-Modify-Write,简称为 RMW 操作)。当有多个客户端对同一份数据执行 RMW 操作的话,我们就需要让 RMW 操作 涉及的代码以原子性方式执行。访问同一份数据的 RMW 操作 的代码,就叫做临界区代码。

不过,当有多个客户端并发执行临界区代码时,就会存在一些潜在问题。例如,客户端要对商品库存执行扣减 1 的操作,伪代码如下:

current = GET(id)
current--
SET(id, current)
  1. 可以看到,客户先根据商品 id,从 Redis 中读取商品当前的库存值 current(对应 Read)
  2. 然后,客户端对库存值减 1(对应 Modify)
  3. 再把库存值写回 Redis (对应 Write)

如果,我们对临界区代码的执行没有控制机制,就会出现数据更新错误。在刚才的例子中,假设现在有两个客户端 A 和 B,同时执行刚才的临界区代码,就会出现错误。
在这里插入图片描述

  1. 在客户端 A 在 t1 时读取库存值 10 并扣减 1
  2. 在 t2 时,客户端 A 还没有把扣减值后的库存值 9 写回 Redis。此时,客户端 B 读取到库存值 10,也扣减了 1, B 记录的库存值也为 9 了。
  3. 等到 T3 时刻, A 往 Redis 写回了库存值 9。
  4. T4 时刻,B 也写回了库存值 9。

如果按正确的逻辑处理,客户端 A 和 B 对库存值各做了一次扣减,库存值应该为 8.所以,这里的库存值明显更新错了。

出现这个现象的原因是,临界区代码中的客户端读取、更新、再写回涉及了三个操作,而这三个操作执行时并不具有互斥性,多个客户端基于相同的初始值进行修改,而不是基于前一个客户端修改后的值再修改。

前面,我们已经解释过,虽然加锁保证了互斥性,但是加锁也会导致系统并发性能降低。接下来,我们了解下 Redis 中的原子操作。

2.Redis 的两种原子操作方法

Redis 的原子操作采用了两种方法:

  1. 把多操作在 Redis 中实现成一个操作,也就是单命令操作。
  2. 把多个操作写到 Lua 脚本中,以原子性方式执行单个脚本。

单命令操作

Redis 使用单线程来串行处理客户端的请求命令,所以,当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的。

当然,Redis 的快照生成、AOF 重写这些操作可以使用后台线程或子进程执行,也就是和主线程的操作并行执行。不过他们都是只读操作,不会修改数据,所以,不需要对它们进行并发控制。

虽然 Redis 的单个命令可以原子性地执行,但是在实际应用中,数据修改时可能包含多个操作,至少包括读数据、数据增减、写回数据三个操作,这显然不是单个命令。

Redis 提供了 INCR/DECR 命令,把这三个操作变为一个原子操作了。INCR/DECR 命令可以对数据进行增值 / 减值操作,而它们本身就是单个命令操作,Redis 在执行它们时,本身就具有互斥性。

比如说,在刚才的库存扣减例子中,客户端可以使用下面的代码,也不用担心出现库存值扣减错误的问题。

DECR id

所以,如果我们执行的 RMW 操作对数据增减值的话, Redis 提供的原子操作 INCR 和 DECR 可以直接绑我们进行并发控制。

Lua 脚本

如果,我们要执行的操作不是简单地增减数据,而是有更新复杂的判断逻辑或者其他操作,那么 Redis 的单命令操作已经无法保证多个操作的互斥执行了。这个时候,就需要用到 Lua 脚本

Redis 会把整个 Lua 脚本 作为一个整体执行,在执行过程中不会被其他命令打断,从而保证了 Lua 脚本 中操作的原子性。如果,我们有多个操作要执行,但是又无法使用 INCR/DECR 这种命令操作来实现,就可以把这些要执行的命令编写到一个 Lua 脚本 中。然后,我们可以使用 Redis 的 EVAL 命令来执行脚本。这样一来,这些操作在执行的时就具有了互斥性。

我们可以把 访问次数加 1、判断访问次数是否为 1,以及设置过期时间这三个操作写入一个 Lua 脚本,如下所示:

local current
current = redis.call("incr", KEYS[1])
if tonumber(current) == 1 then
	redis.call("expire", KEYS[1], 60)
end
return current

假设编写的脚本的名称为 lua.script,我们就接着使用 Redis 客户端,带上 eval 选项,来执行该脚本。脚本所需要的参数通过以下命令中的 key 和 arg 进行传递。

redis-cli --eval lua.script [key key2 ...] , [arg arg2 ...]

另外还可以在 Redis 客户端内,执行脚本:eval lua.script key-num [key key2 ...] [arg arg2 ...]

  • [key key2 ...]: 表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
  • [arg arg2 ...]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

例如上面的 Lua 脚本的实际执行如下(其中, KEYS[1] 为 lua1):

chenjian@DESKTOP-Q24SEP3:~$ vim lua.script
chenjian@DESKTOP-Q24SEP3:~$ ./redis-6.2.12/src/redis-cli --eval lua.script lua1 ,
(integer) 1
chenjian@DESKTOP-Q24SEP3:~$ ./redis-6.2.12/src/redis-cli --eval lua.script lua1 ,
(integer) 2

这样一来,访问次数加 1、判断访问次数是否为 1,以及设置过期时间这三个操作就可以原子性的执行了。即使客户端有多个线程同时执行这个脚本,Redis 也会依次串行的执行脚本代码,避免并发操作带来的数据错误。

不过需要注意的是,如果把很多操作都放在 Lua 脚本中原子执行,会导致 Redis 执行脚本的时间增加,同样也会降低 Redis 。所以,给你一个小建议:在编写 Lua 脚本时,你要避免把不需要做并发控制的操作写入 Lua 脚本中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/380126.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络】计算机网络复习资料(期末)

复习要点 一、填空题 1.计算机网络的两个重要基本特点 连通性、共享 2.计算机中的端口号类型 两类端口号 { 服务器端 { 熟知端口号(系统端口号)数值为0~1023 登记端口号,1024~49151 } 客户端使用的端口号:短…

Mac如何安装python?

一、问题 Mac如何安装python? 二、解决 1、系统自带python Mac系统均自带Python环境,用户在终端输入“python3”命令就可以运行,如图所示 2、官网下载 Download Python | Python.org (1)在Download下找到macOS &am…

Android Studio中打开文件管理器

文章目录 一、前言二、操作步骤 一、前言 在Android Studio中有时候需要查看手机的文件目录或者复制文件,但是有时候文件管理器找不到在哪,这里记录该操作流程 二、操作步骤 第一步: 第二步: 第三步:

Mysql——更新数据

注:文章参考: MySQL 更新数据 不同条件(批量)更新不同值_update批量更新同一列不同值-CSDN博客文章浏览阅读2w次,点赞20次,收藏70次。一般在更新时会遇到以下场景:1.全部更新;2.根据条件更新字段中的某部分…

flutter使用webview_flutter在安卓和ios上打开网页

webview_flutter仓库地址:webview_flutter | Flutter package github地址:https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter 要打开非https协议的网页,需要在安卓平台上添加权限:andro…

JAVA设计模式之原型模式详解

原型模式 1 原型模式介绍 定义: 原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。 西游记中的孙悟空 拔毛变小猴,孙悟空这种根据自己的形状复制出多个身外化身的技巧,在面向对象软件设计领…

Java图形化界面编程—— 基本组件和对话框 笔记

2.5 AWT中常用组件 2.5.1 基本组件 组件名功能ButtonButtonCanvas用于绘图的画布Checkbox复选框组件(也可当做单选框组件使用)CheckboxGroup选项组,用于将多个Checkbox 组件组合成一组, 一组 Checkbox 组件将只有一个可以 被选中…

Springboot+vue的社区养老服务平台(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频: Springbootvue的社区养老服务平台(有报告)。Javaee项目,springboot vue前后端分离项目 项目介绍: 本文设计了一个基于Springbootvue的前后端分离的社区养老服务平台,采用M(model&…

Vue 条件渲染 双向绑定

https://www.dedao.cn/ebook/reader?id5lZOKpMGr9mgdOvYa6Ej75XRo1NML3jx810k8ZVzb2nqPpDxBeJlK4AyQ8RPQv2z v-if实现条件渲染的功能。v-model实现双向数据传输。 v-model用来进行双向绑定,当输入框中的文字变化时,其会将变化同步到绑定的变量上&#…

华为机考入门python3--(9)牛客9-提取不重复的整数

分类:列表 知识点: 从右往左遍历每一个字符 my_str[::-1] 题目来自【牛客】 def reverse_unique(n): # 将输入的整数转换为字符串,这样可以从右向左遍历每一位 str_n str(n) # 创建一个空列表来保存不重复的数字 unique_digits []…

ChatGPT高效提问—prompt常见用法(续篇六)

ChatGPT高效提问—prompt常见用法(续篇六) 1.1 控制输出 ​ 控制输出是一种先进的自然语言处理技术,其能够在AI模型生成文本的过程中实现更高级别的控制。通过提供特定的输入,如模板、特定词语或约束性条件,从而精准…

【QT学习十四】 文件目录操作

目录 一、概述 二、详解 1. QFile QFile 类中的一些静态方法: 使用示例: 注意事项: 2. QDir 成员函数 使用实例: 注意事项: 3. QFileInfo 成员函数 使用实例 4. QTemporaryFile 成员函数 使用实例 注…

Redis(三)主从架构、Redis哨兵架构、Redis集群方案对比、Redis高可用集群搭建、Redis高可用集群之水平扩展

转自 极客时间 Redis主从架构 redis主从架构搭建,配置从节点步骤: 1、复制一份redis.conf文件2、将相关配置修改为如下值: port 6380 pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件 logfile "6380.log" …

C语言每日一题(50)二叉树的最大深度

力扣104 二叉树的最大深度 题目描述 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:3示例 2: …

嵌入式学习之Linux入门篇笔记——15,Linux编写第一个自己的命令

配套视频学习链接:http://【【北京迅为】嵌入式学习之Linux入门篇】 https://www.bilibili.com/video/BV1M7411m7wT/?p4&share_sourcecopy_web&vd_sourcea0ef2c4953d33a9260910aaea45eaec8 1.什么是命令? 命令就是可执行程序。 比如 ls -a…

学习Android的第八天

目录 Android ImageView 图像视图 ImageView 的基本使用 src属性和background属性的区别 范例 解决 anndroid:blackground 属性拉伸导致图片变形的方法 设置透明度的问题 范例 android:src 和 android:background 结合 范例 Java 代码中设置 blackground 和 src 属性…

k8s 部署java应用 基于ingress+jar包

k8 集群ingress的访问模式 先部署一个namespace 命名空间 vim namespace.yaml kind: Namespace apiVersion: v1 metadata:name: ingress-testlabels:env: ingress-test 在部署deployment deployment是pod层一层封装。可以实现多节点部署 资源分配 回滚部署等方式。 部署的…

融资项目——获取树形结构的数据

如下图所示,下列数据是一个树形结构数据,行业中包含若干子节点。表的设计如下图,设置了一个id为1的虚拟根节点。(本树形结构带虚拟根节点共三层) 实现逻辑: 延时展示方法,先展现第二层的信息&a…

C++自定义函数详解

个人主页:PingdiGuo_guo 收录专栏:C干货专栏 铁汁们新年好呀,今天我们来了解自定义函数。 文章目录 1.数学中的函数 2.什么是自定义函数 3.自定义函数如何使用? 4.值传递和引用传递(形参和实参区分) …

电脑服务器离线安装.net framework 3.5解决方案(错误:0x8024402c )(如何确定当前系统是否安装NET Framework 3.5)

问题环境: 日常服务的搭建或多或少都会有需要到NET Framework 3.5的微软程序运行框架,本次介绍几种不同的安装方式主要解决运行在Windows 2012 以上的操作系统的服务。 NET Framework 3.5 是什么? .NET Framework是微软公司推出的程序运行框架…