redis的并发安全问题:redis的事务VSLua脚本

redis为什么会发生并发安全问题?

在redis中,处理的数据都在内存中,数据操作效率极高,单线程的情况下,qps轻松破10w。反而在使用多线程时,为了保证线程安全,采用了一些同步机制,以及多线程的上下文切换,却对性能造成了一定的影响。
如此看来,在单线程模式下,redis的性能比较高,且可以避免多线程情况下的线程安全问题。但是在redis使用过程中,线程安全问题依旧存在,此话怎讲呢?
线程安全,是站在reids的角度来说的,redis使用单线程模型,是不存在线程安全问题的,以为他只有一个线程,不存在多线程间数据的共享,俗话说没有共享就没有伤害。
而线程不安全,是站在客户端的角度说的,redis是只有一个线程在工作,但是客户单端却是有成千上万个的,对于客户端来说,redis是被共享的资源,所以对于客户端来说依旧存在线程安全问题,
举例:
1.商品库存开始为10,此时客户端A和客户端B,同时下单扣减库存。
2.两个客户端从redis获取到当前库存数为10。
3.两个客户端在本地将库存数减1,然后写回redis。
4,此时redis中存库数为9,正常情况下应该是8,造成超卖问题。
在这里插入图片描述
————————————————
版权声明:本文为CSDN博主「wind_huise」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45701550/article/details/126324924

读库存和写库存操作,在redis中是单线程执行的,是原子性的,但是整个扣减库存的操作却不是原子性的,这也是出现线程不安全的根本原因

对于解决这个问题,redis提供了一些复合命令,将多个操作合并成一个操作命令,此时这个复合命令就变成一个原子操作,也就不会再出现上述的线程安全问题了。如果需要实现简单的原子性操作,则可以使用Redis的单个指令,例如SET、GET、DEL等来实现;如果需要实现复杂数据结构的操作,则可以考虑使用Redis事务、Lua脚本来实现。

Redis的事务模型

在Redis中,事务是通过MULTI、EXEC、WATCH、UNWATCH等指令来实现的。在MULTI指令开始执行事务之前,客户端发送的每个指令都不会立即执行,而是被缓存到一个队列中,直到EXEC指令被执行时,所有缓存的指令才一起被执行。这种模型被称为乐观锁,因为在执行事务期间,Redis并不会对被监控的数据进行加锁,而是在执行EXEC指令时进行条件检查,如果检查失败,则事务执行失败。这种设计可以减少锁的竞争,提高Redis的并发性能。

流程如下:
1、开启事务:MULTI
客户端要使用一个命令显式地表示一个事务的开启。在Redis中,这个命令就是MULTI。
2、指令入队(暂时不执行)
客户端把事务中本身要执行的具体操作(例如增删改数据)发送给服务器端。这些操作就是Redis本身提供的数据读写命令,例如GET、SET等。不过,这些命令虽然被客户端发送到了服务器端,但Redis实例只是把这些命令暂存到一个命令队列中,并不会立即执行。
3、执行指令:EXEC
客户端向服务器端发送提交事务的命令,让数据库实际执行第二步中发送的具体操作。Redis 提供的EXEC命令就是执行事务提交的。当服务器端收到EXEC命令后,才会实际执行命令队列中的所有命令

Redis对于事务操作的支持比较局限,最大的不足是不支持回滚。执行事务时,Redis将这些命令依次执行,如果有一条命令执行失败,就会导致整个事务的失败,但不能回滚已经执行成功的命令

在Redis的事务模型中,由于不会对被监控的数据进行加锁,因此如果事务执行失败,只能够通过执行一系列的逆操作来恢复数据状态,而无法直接回滚事务。这是因为对于一些操作,例如INCRBY、RPUSH、SUNION、ZUNIONSTORE等,Redis并没有提供对应的逆操作。因此,如果使用Redis来实现复杂的事务操作,可能需要自己实现回滚逻辑,这会增加开发和维护的复杂度。

Lua脚本

基本原理为使脚本相当于一个redis命令,可以结合redis原有命令,自定义脚本逻辑

如何在redis中使用lua脚本,可以参考https://redis.io/commands/eval/

Lua脚本之所以可以保证线程安全,是因为我们可以把多个操作写成一个 lua 脚本,使其具备原子性,作为一个整体执行。再由于 redis 是单线程模型,不同线程的 lua 脚本是依次执行的。也就是说,只有一个线程原子性的多个操作执行完,下一个线程才可以执行。实际上也是保证了在 redis 内部不同线程操作的串行执行,从而能够解决并发安全问题。
【简单来说,就是lua脚本包括业务的多个操作,使得整个业务成为一个整体,执行时相当于把整个lua脚本当成一个redis指令】

Lua脚本的优点

1.lua脚本是作为一个整体执行的,所以中间不会被其他命令插入,无需担心并发;

2.lua脚本把多条命令一次性打包,而代码实现的事务需要向Redis发送多次请求,所以可以有效减少网络开销;

3.lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用。

redis事务和Lua两者相同点

很好的实现了一致性、隔离性和持久性,但没有实现原子性,无论是redis事务,还是lua脚本,如果执行期间出现运行错误,之前的执行过的命令是不会回滚的。

lua 脚本实现的原子性是假的原子性

因为当多个指令执行时,lua脚本中的一条指令报错时,后面的指令执行失败了,但是前面的指令已经成功。且不会回滚

其中报错原因包括:
1、指令语法错误
2、语法是正确的,但是类型不对,比如对已经存在的string类型的key,执行hset等
3、服务器挂掉了,比如lua脚本执行了一半,但是服务器挂掉了

前面两者,我们可以通过仔细检查脚本逻辑,确保脚本中的所有命令都是正确的,并且按照预期的顺序执行、使用redis.pcall处理潜在的错误以及适时使用事务,可以编写出高效、可靠的Lua脚本,确保脚本的逻辑正确性和健壮性,以避免潜在的问题。

redis.pcall在命令执行失败时不会引发错误,而是返回一个包含错误信息的表。通过检查redis.pcall的返回值,可以在脚本中处理错误情况,从而避免脚本执行失败

Lua脚本回滚?
在Lua脚本中,我们可以使用Redis的WATCH指令,它允许我们监视一个或多个键的变化。当我们监视的键发生变化时,Redis会立刻中断正在执行的Lua脚本,使得整个Lua脚本操作回滚,这种方式可以实现Redis事务的回滚效果。

Lua脚本相对于Redis事务更实用的原因有以下几点:

1、原子性保证:使用Lua脚本可以将多个命令封装成一个原子操作,确保这些命令在执行期间不会被其他命令插入,从而保证操作的原子性。
2、减少网络开销:在Lua脚本中,多个命令可以一次性发送到Redis服务器,并由Redis执行,减少了网络开销。而Redis事务需要通过MULTI和EXEC命令来开启和提交事务,增加了网络往返的次数。
3、高性能:由于Lua脚本在Redis服务器端执行,避免了客户端与服务器之间的通信。这样可以减少通信延迟,并在服务器端以原生代码的方式执行,提高了执行效率。相比之下,Redis事务在客户端和服务器之间进行多次通信,可能降低执行效率。
4、复杂逻辑支持:Lua脚本提供了强大的编程能力,可以实现复杂的业务逻辑。通过脚本的编写,可以实现数据库操作的灵活性,从而适应更多的场景需求。Redis事务对于复杂逻辑的支持相对较弱,更适合简单的操作序列。

参考文章:
使用redis,怎么解决并发问题?
redis为什么不支持事务(redis不支持的数据结构)
Redis实例发生故障,而Redis使用的RDB机制,事务的原子性还能否得到保证?
Redis不支持事务的解决方案(redis事务为什么不支持回滚)
Redis实例发生故障,而Redis使用的RDB机制,事务的原子性还能否得到保证?

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

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

相关文章

20.3 HTML 表格

1. table表格 table标签是HTML中用来创建表格的元素. table标签通常包含以下子标签: - th标签: 表示表格的表头单元格(table header), 用于描述列的标题. - tr标签: 表示表格的行(table row). - td标签: 表示表格的单元格(table data), 通常位于tr标签内, 用于放置单元格中的…

C语言枚举与联合体详解

本篇文章带来枚举与联合体相关知识详细讲解! 如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作的动力之源,让我们一起加油,一起奔跑,让我们顶峰相见!!! 目录 一…

InnoDB引擎底层逻辑讲解——架构之内存架构

1.InnoDB引擎架构 下图为InnoDB架构图,左侧为内存结构,右侧为磁盘结构。 2.InnoDB内存架构讲解 2.1 Buffer Pool缓冲池 2.2 Change Buffer更改缓冲区 2.3 Adaptive Hash Index自适应hash索引 查看自适应hash索引是否开启: show variable…

Modbus TCP/IP之异常响应

文章目录 一、异常响应二、异常码分析2.1 异常码0x012.2 异常码0x022.3 异常码0x032.4 异常码0x062.5 异常码0x04、0x05等 一、异常响应 对于查询报文,存在以下四种处理反馈: 正常接收,正常处理,返回正常响应报文;因为…

部署问题集合(十八)Windows环境下使用两个Tomcat

下载Tomcat Tomcat镜像下载地址:https://mirrors.cnnic.cn/apache/tomcat/进入如下地址:zip的是压缩版,exe是安装版 修改第二个Tomcat配置文件 第一步:编辑conf/server.xml文件,修改三个端口,有些版本改…

【Rust日报】2023-07-28 使用 Cargo-PGO 优化 Rust 程序

使用 Cargo-PGO 优化 Rust 程序 去年,作者致力于改进用于构建 Rust 编译器的配置文件引导优化 (PGO) 工作流程。在这样做的过程中,虽然 PGO 对于 Rust 工作得很好,但它并不像希望的那样易于使用和发现。这促使我创建了 cars-pgo,这…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块2

37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&am…

【Vue3】递归组件

1. 递归组件mock数据 App.vue <template><div><Tree :data"data"></Tree></div> </template><script setup lang"ts"> import { reactive } from vue; import Tree from ./components/Tree.vue; interface Tr…

CentOS 8 上安装 Nginx

Nginx是一款高性能的开源Web服务器和反向代理服务器&#xff0c;以其轻量级和高效能而广受欢迎。在本教程中&#xff0c;我们将学习在 CentOS 8 操作系统上安装和配置 Nginx。 步骤 1&#xff1a;更新系统 在安装任何软件之前&#xff0c;让我们先更新系统的软件包列表和已安…

读发布!设计与部署稳定的分布式系统(第2版)笔记26_安全性上

1. 安全问题 1.1. 系统违规并不总是涉及数据获取&#xff0c;有时会出现植入假数据&#xff0c;例如假身份或假运输文件 1.2. 必须在整个开发过程中持续地把安全内建到系统里&#xff0c;而不是把安全像胡椒面那样在出锅前才撒到系统上 2. OWASP 2.1. Open Web Application…

Godot 4 源码分析 - 动态导入图片文件

用Godot 4尝试编一个电子书软件&#xff0c;初步效果已经出来&#xff0c;并且通过管道通信接口可以获取、设置属性、调用函数&#xff0c;貌似能处理各种事宜了。 其实不然&#xff0c;外因通过内因起作用&#xff0c;如果没把里面搞明白&#xff0c;功能没有开放出来&#x…

【SpringCloud Alibaba】(六)使用 Sentinel 实现服务限流与容错

今天&#xff0c;我们就使用 Sentinel 实现接口的限流&#xff0c;并使用 Feign 整合 Sentinel 实现服务容错的功能&#xff0c;让我们体验下微服务使用了服务容错功能的效果。 因为内容仅仅围绕着 SpringCloud Alibaba技术栈展开&#xff0c;所以&#xff0c;这里我们使用的服…

详解Mybatis之分页插件【PageHelper】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 一. 什么是分页&#xff1f;二. 为什么使用分页&#xff1f;三. 如何设计一个Page类&#xff08;分…

【玩转python系列】【小白必看】使用Python爬虫技术获取代理IP并保存到文件中

文章目录 前言导入依赖库打开文件准备写入数据循环爬取多个页面完整代码运行效果结束语 前言 这篇文章介绍了如何使用 Python 爬虫技术获取代理IP并保存到文件中。通过使用第三方库 requests 发送HTTP请求&#xff0c;并使用 lxml 库解析HTML&#xff0c;我们可以从多个网页上…

《Kali渗透基础》11. 无线渗透(一)

kali渗透 1&#xff1a;无线技术特点2&#xff1a;IEEE 802.11 标准2.1&#xff1a;无线网络分层2.2&#xff1a;IEEE2.3&#xff1a;日常使用标准2.3.1&#xff1a;802.112.3.2&#xff1a;802.11b2.3.3&#xff1a;802.11a2.3.4&#xff1a;802.11g2.3.5&#xff1a;802.11n …

python的包管理器pip安装经常失败的解决办法:修改pip镜像源

pip 常用的国内镜像源&#xff1a; https://pypi.tuna.tsinghua.edu.cn/simple/ // 清华 http://mirrors.aliyun.com/pypi/simple/ // 阿里云 https://pypi.mirrors.ustc.edu.cn/simple/ // 中国科技大学 http://pypi.hustunique.com/ // 华中理…

stm32通过ESP8266接入原子云

1. ESP8266模块需要烧录原子云固件&#xff0c;此原子云固件和正常的ESP8266固件相比添加了ATATKCLDSTA 和 ATATKCLDCLS 这两条指令&#xff1a; 2. 原子云账号注册及设备建立 设备管理-新增设备-ESP8266 新建设备后新建分组&#xff0c;将设备加入到此分组中&#xff1a; 至此…

【算法基础:贪心】6. 贪心

文章目录 区间问题905. 区间选点&#xff08;排序 贪心&#xff09;908. 最大不相交区间数量&#xff08;排序 贪心&#xff09;906. 区间分组&#xff08;排序 优先队列 贪心&#xff09;⭐907. 区间覆盖&#xff08;排序 贪心&#xff09; Huffman树148. 合并果子&#…

安卓:Picasso——加载网络图片的库

目录 一、Picasso介绍及其优势 二、Picasso的使用方法 1、添加依赖&#xff1a; 2、Picasso常用方法&#xff1a; 1、加载图像&#xff1a; 2、图像显示&#xff1a; 3、图像处理&#xff1a; 4、图像占位符和错误处理&#xff1a; 5、缓存控制&#xff1a; 6、清除缓…

关于前端框架vue2升级为vue3的相关说明

一些框架需要升级 当前&#xff08;202306&#xff09; Vue 的最新稳定版本是 v3.3.4。Vue 框架升级为最新的3.0版本&#xff0c;涉及的相关依赖变更有&#xff1a; 前提条件&#xff1a;已安装 16.0 或更高版本的Node.js&#xff08;摘&#xff09; 必须的变更&#xff1a;核…