redis-事务(MULTI、EXEC、DISCARD、WATCH与lua脚本、包含lua脚本的简单介绍、乐观锁抢购案例的实现)
https://juejin.cn/post/6891158857708797959
首先Redis事务在实际的场景应用上也占着比较重要的地位,例如在秒杀场景中,我们就可以利用Redis事务中的watch命令监听key,实现乐观锁,保证不会出现冲突,也防止商品超卖。
另外就是Redis事务也是面试过程中面试官着重照顾的基础知识对象,假设面试官问你实现Redis事务有哪些方式?事务发生错误时Redis是怎么处理的?Redis事务支持回滚吗等等这些问题,你是否能脱口而出回答上来呢?如果你对这方便的基础知识有所欠缺,那是不是就栽跟头了呢?
两种实现方式
redis命令 MULTI、EXEC、DISCARD、WATCH
multi开启事务
exec是执行
disccard是放弃事务返回
重点讲下watch
WATCH 命令用于在事务开始之前监视任意数量的键,当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。
看例子:
- 首先我们在一个Redis客户端一上使用 WATCH 命令监控两个key,分别为name和sex,然后开启事务,在事务中修改name的值,
- 在客户端一执行 EXEC 命令之前,我们另外开一个客户端二,在客户端二中我们修改sex的值为man
接着我们回到客户端一执行 EXEC 命令
# 客户端一
127.0.0.1:6379> get name
"dashu"
127.0.0.1:6379> get sex
"male"
127.0.0.1:6379> WATCH name sex
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name saycode
QUEUED
127.0.0.1:6379> EXEC
(nil) # 事务失败
127.0.0.1:6379> get sex
"man"
127.0.0.1:6379> get name
"dashu"#--------- 这是一条分割线 ---------## 客户端二
127.0.0.1:6379> get sex
"male"
127.0.0.1:6379> set sex man
OK
从上面执行的结果可以看到,客户端一中的事务失败了,事务中所修改的name的值也不成功。主要原因是:调用 EXEC 命令执行事务时,被监控的sex 被客户端二修改了,所以客户端一的事务不再执行
watch命令的原理 看 https://juejin.cn/post/6891158857708797959
Lua脚本
除了上面介绍的命令模式可以实现Redis事务外,其实还有一种非常重要的方式:Lua脚本。
为什么要夸Lua脚本呢?我们来看看Lua脚本有什么优势:
原子操作:Redis确保脚本执行期间,其它任何脚本或者命令都无法执行。也就是说,在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延。因此使用脚本要更简单,速度更快
复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。
香吗?真香!反正用过的都说好。可以看到相比命令模式还是优势还蛮大的。
那么Lua脚本要怎么用呢?下面跟大家介绍几个常见的常用的命令:
EVAL
EVAL 可以理解为是lua脚本的解释器,它的语法格式如下:
EVAL script numkeys key [key ...] arg [arg ...]
- script:一段 Lua 脚本或 Lua 脚本文件所在路径及文件名。
- numkeys:Lua 脚本对应参数数量
- key [key …]:Lua 中通过全局变量 KEYS 数组存储的传入参数
- arg [arg …]:Lua 中通过全局变量 ARGV 数组存储的传入附加参数
官方腔有点重对吧,没事,咱们来看个例子:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
eval的第一个参数是脚本的内容,第二个参数是脚本里面KEYS数组的长度(不包括ARGV参数的个数),这里是两个;紧接着就会有两个参数,用于传递个KEYS数组;后面剩下的参数全部传递给ARGV数组,相当于命令行参数。
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 username age jack 20
1) "username"
2) "age"
3) "jack"
4) "20"
redis.call() / redis.call()
如果我们想在lua脚本中调用redis的命令该如何操作?其实我们可以在脚本中使用 redis.call() 或 redis.pcall() 直接调用。两者用法类似,只是在遇到错误时,返回错误的提示方式不同。
举个例子:
127.0.0.1:6379> get name
"saycode"
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'dashu')" 1 name
OK
127.0.0.1:6379> get name
"dashu"
127.0.0.1:6379> eval "return redis.call('get','name')" 0
"dashu"
127.0.0.1:6379>
SCRIPT LOAD 和 EVALSHA
SCRIPT LOAD:提前载入 Lua 脚本,返回对应脚本的 SHA1 摘要
EVALSHA:执行脚本,与EVAL相似,只不过它的参数为脚本的 SHA1 摘要
SCRIPT LOAD 和 EVALSHA 经常配合使用。我们看个例子:
127.0.0.1:6379> SCRIPT LOAD "return redis.call('set',KEYS[1],'30')"
"6445747e70ce11ad0b9717d78e8ff16fb0faed46"
127.0.0.1:6379> evalsha 6445747e70ce11ad0b9717d78e8ff16fb0faed46 1 age
OK
127.0.0.1:6379> get age
"30"
127.0.0.1:6379>
更多命令可以参看Redis Script 官方文档
有了上面的知识,我们就可以使用lua脚本来灵活的使用redis的事务,这里举几个简单的例子:
场景1:使用redis限制30分钟内一个IP只允许访问5次
思路:每次想把当前的时间插入到redis的list中,然后判断list长度是否达到5次,如果大于5次,那么取出队首的元素,和当前时间进行判断,如果在30分钟之内,则返回-1,其它情况返回1。我们来看一下具体实现:
eval "redis.call('rpush', KEYS[1],ARGV[1]);if (redis.call('llen',KEYS[1]) >tonumber(ARGV[2])) then if tonumber(ARGV[1])-redis.call('lpop', KEYS[1])<tonumber(ARGV[3]) then return -1 else return 1 end else return 1 end" 1 'test_127.0.0.1' 1451460590 5 1800
Lua脚本 对于实现Redis事务确实是一种不错的选择,相信未来会有越来越多的开发者倾向于使用脚本来实现事务。不过我们在使用的时候也要注意以下两点:
- 注意Redis版本。脚本功能是 Redis 2.6 才引入的。
- 由于脚本执行的原子性,所以我们不要在脚本中执行过长开销的程序,否则会验证影响其它请求的执行。
Redis事务是否支持回滚
Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制(rollback)。
也就是说:当在事务过程中发生错误时,Redis事务失败时并不进行回滚(roll back),而是继续执行余下的命令。官方给出的理由是这样子的:
从实用性的角度来说,Redis失败的命令是由编程错误造成的(例如错误的语法,命令用在了错误类型的命令),而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
保证Redis性能。因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速
事务中发生错误Redis如何表现
实际上,事务的错误我们可以总结两种情况:
一种是:事务在执行 EXEC 之前,入队的命令可能会出错。比如命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
对于发生在 EXEC 执行之前的错误,客户端的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。看例子:
127.0.0.1:6379> get name
"saycode"
127.0.0.1:6379> get sex
"man"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name dashu
QUEUED
127.0.0.1:6379> sett sex woman
(error) ERR unknown command `sett`, with args beginning with: `sex`, `woman`,
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
"saycode"
127.0.0.1:6379> get sex
"man"
还有一种是:命令可能在 EXEC 调用之后失败。比如事务中的命令可能处理了错误类型的键,例如将列表命令用在了字符串键上面
至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
127.0.0.1:6379> get name
"dashu"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name saycode
QUEUED
127.0.0.1:6379> lpop name
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get name
"saycode"
127.0.0.1:6379>
我们可以看到:即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。
Redis事务的实战应用——乐观锁抢购
了解完Redis事务的基础,最后我们来写个Demo来实现乐观锁,业务场景是商品抢购,伪代码如下:
public function actionBuy(){$userId = mt_rand(1,99999999);$goods = $this->goods;$redis = Yii::$app->redis;$lock = "Huawei p40";try {$inventory['num'] = $redis->get('goodNums');if($inventory['num']<=0){throw new \Exception('活动结束');}$redis->watch($lock);$redis->multi();//todo:这里还需要重新判断下库存,否则会出现超发,高并发情况下$inventory['num']肯定会出现同时读取一个值;为了方便测试,没写db操作//redis事务是将命令放入队列中,无法取goodNums来判断库存是否结束,此处使用数据库来判断库存合理//业务处理 减库存,创建订单$redis->decr('goodNums');$redis->sadd('order',$userId);$redis->exec();Common::addLog('shop.log',$userId.' 抢购成功');}catch (\Exception $e){$redis->discard();Common::addLog('shop.log',$e->getMessage());throw new \Exception('抢购失败');}die('success');
}
相关文章:
redis-事务(MULTI、EXEC、DISCARD、WATCH与lua脚本、包含lua脚本的简单介绍、乐观锁抢购案例的实现)
https://juejin.cn/post/6891158857708797959 首先Redis事务在实际的场景应用上也占着比较重要的地位,例如在秒杀场景中,我们就可以利用Redis事务中的watch命令监听key,实现乐观锁,保证不会出现冲突,也防止商品超卖。 …...
【Linux】su、su-、sudo、sudo -i、sudo su - 命令有什么区别?分别适用什么场景?
目录 su su- sudo sudo -i sudo su - /etc/sudoers su 该命令将启动非登录shell,即虽然以该用户身份启动shell,但使用的是原始用户的环境设置。普通用户账户运行 su 命令切换到另一用户账户,需提供要切换的账户的密码。root用户&…...
CCLinkIE转ModbusTCP借网关之力打破组态王与三菱PLC通讯隔阂
在某自动化生产线项目中,客户采用了三菱PLC作为现场控制核心,该PLC支持CCLinkIE现场总线协议。同时,客户希望使用组态王上位机软件进行生产过程的监控与管理,然而组态王上位机更擅长与ModbusTCP协议设备进行通讯。为了解决这一协议…...
MyBatis-Plus 通过 ID 更新数据为NULL总结
在使用 MyBatis-Plus 通过 ID 更新数据时,若需将字段值设为 null,可参考以下解决方案: 方法一:使用 TableField 注解 在实体类字段上添加注解,指定更新策略为忽略非空检查: public class User {TableFie…...
Linux网络编程第一课:深入浅出TCP/IP协议簇与网络寻址系统
知识点1【网络发展简史】 **网络节点:**路由器和交换机组成 交换机的作用:拓展网络接口 路由:网络通信路径 1、分组交换 分组的目的: 数据量大,不能一次型传输,只能分批次传输,这里的每一批…...
深入解析布尔注入:原理、实战与防御
目录 一、布尔注入的原理与核心逻辑 二、布尔注入的实战步骤 三、关键函数与绕过技巧 四、实战案例:获取数据库名称 五、防御策略与最佳实践 六、总结 一、布尔注入的原理与核心逻辑 布尔注入(Boolean-Based Blind SQL Injection)是一种…...
GESP2023年12月认证C++七级( 第三部分编程题(2)纸牌游戏)
参考程序: #include <iostream> #include <cstring> // for memset #include <vector> using namespace std;const int max_n 1005; int n; int a[max_n], b[max_n], c[max_n]; // a[]: 得分系数;b[]: 换牌惩罚;c[]: …...
不同的人机验证的机制
目录 Cloudflare Turnstile验证 reCAPTCHA V2 GeeTest CAPTCHA Arkose Labs 验证码(FunCaptcha) 图像(图片)验证码 亚马逊验证码 (AWS/WAF) Cloudflare Turnstile验证 Cloudflare Turnstile 验证码以隐形方式运行…...
HarmonyOS学习 实验九:@State和@Prop装饰器的使用方法
HarmonyOS应用开发:父子组件状态管理实验报告 引言 在HarmonyOS应用开发领域,组件之间的状态管理是一个至关重要的概念。通过有效的状态管理,我们可以确保应用的数据流动清晰、可预测,从而提升应用的稳定性和可维护性。本次实验…...
基于瑞芯微RK3562 四核 ARM Cortex-A53 + 单核 ARM Cortex-M0——Linux应用开发手册
前 言 本文主要介绍TL3562-MiniEVM评估板的AMP(Asymmetric Multi-processing)开发案例,适用开发环境如下: Windows开发环境:Windows 7 64bit、Windows 10 64bit Linux开发环境:VMware16.2.5、Ubuntu20.04.6 64bit U-Boot:U-Boot-2017.09 Kernel:Linux-5.10.209 Lin…...
Java c线程等待ab线程执行完再执行
1、LockSupport AtomicInteger LockSupport.park() 函数表示挂起当前线程LockSupport.unpark© 函数表示解除线程c的阻塞状态AtomicInteger.decrementAndGet() 函数表示将该变量减一,并返回当前变量值(线程安全的原子类) 2、CountDownL…...
ubuntu20.04 Android14编译环境配置
ubuntu 更新和必要安装 sudo apt update sudo apt install git sudo apt install python2-minimal sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2 sudo upda…...
优化 Dockerfile 性能之实践(Practice of Optimizing Dockerfile Performance)
优化 Dockerfile 性能之实践 构建 Docker 镜像时,Dockerfile 的性能会显著影响构建过程的效率。经过优化的 Dockerfile 可以缩短构建时间、最小化镜像大小并提高整体容器性能。在本文中,我们将探讨优化 Dockerfile 性能的最佳实践。 尽量减少层数 影响…...
【Ai】MCP实战:手写 client 和 server [Python版本]
什么是mcp MCP 是一个开放协议,它为应用程序向 LLM 提供上下文的方式进行了标准化。你可以将 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 为设备连接各种外设和配件提供了标准化的方式一样,MCP 为 AI 模型连接各种数据源和工具提供了标准化的接口…...
解决 AWS RDS MySQL mysqldump 导入sql SET @@GLOBAL 权限不足问题
在使用 mysqldump 导出数据库时,导出的 SQL 文件通常会包含一些 SET 语句,例如 SET MYSQLDUMP, SET SESSION, SET GLOBAL 等,这些语句用于设置会话或全局变量以确保数据一致性和兼容性。然而,在 AWS RDS MySQL 环境中,…...
Java与C在典型场景下的性能对比深度剖析
🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 【前言】 在计算机编程领域,Java和C语言都是举足轻重的编程语言。Java以其跨平台性、自动内存管理和丰富…...
多智能体 AI 游戏框架(开源程序):竞争、发展、适应
一、软件介绍 文末提供程序和源码下载 SamoAI 在人类和 AI 之间创建了一个无缝的多代理叙事层,实现了跨多个平台的自然协作。通过一致的身份保留和情境记忆,它允许通过一系列行动随着时间的推移而演变的交互,就像人际关系一样。 二、核心概念…...
从 BI 与 SQL2API 的差异,看数据技术的多元发展路径
在数据驱动的商业世界里,商业智能(BI)与 SQL2API 如同两颗闪耀的星星,各自散发着独特的光芒。BI 早已在企业中广泛应用,成为数据分析领域的中流砥柱;而 SQL2API 作为新兴技术,虽潜力巨大&#x…...
java实现二叉树的前序、中序、后序遍历(递归和非递归方式)以及层级遍历
java实现二叉树的前序、中序、后序遍历以及层级遍历 一、二叉树节点定义二、递归方式1.前序遍历2.中序遍历3.后序遍历 三、非递归方式1.前序遍历2.中序遍历3.后序遍历4.层级遍历5.分层打印 四、测试用例 一、二叉树节点定义 class TreeNode {int val;TreeNode left;TreeNode r…...
Solr admin 更新文档
<add><doc><field name"id">1904451090351546368</field><field name"companyName" update"set">测试科技有限公司</field></doc> </add>...
【Netty篇】EventLoopGroup 与 EventLoop 详解
目录 开场白:话说 Netty 江湖第一段:EventLoopGroup——“包工头”的角色第二段:EventLoop——“身怀绝技的工人”第三段:EventLoop 如何处理 I/O 事件、普通任务和定时任务第四段:Handler 执行中如何换人?…...
操作系统之shell实现(上)
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习C语言的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
考研数据结构之图的遍历:深度优先搜索(DFS)与广度优先搜索(BFS)(包含真题及解析)
考研数据结构之图的遍历:深度优先搜索(DFS)与广度优先搜索(BFS) 图的遍历是图论中的核心操作之一,主要包括深度优先搜索(DFS)和广度优先搜索(BFS)。本文将详…...
数据结构与算法——链表OJ题详解(2)
文章目录 一、前言二、OJ续享2.1相交链表2.2环形链表12.2环形链表2 三、总结 一、前言 哦了兄弟们,咱们上次在详解链表OJ题的时候,有一部分OJ题呢up并没有整理完,这一个星期呢,up也是在不断的学习并且沉淀着,也是终于…...
Linux 基础知识详解
Linux 基础知识详解 一、快照与克隆 1. 📸快照(Snapshot) 快照是虚拟机当前运行状态的一次“瞬间拷贝”,包括内存、磁盘、配置等信息。这使得管理员能够快速恢复到某个特定的时间点。 用途: 安全实验前保存状态&am…...
MySQL慢SQL优化方案详解:从诊断到根治的完整指南
MySQL慢SQL优化方案详解:从诊断到根治的完整指南 一、慢SQL的致命影响 当数据库响应时间超过500ms时,系统将面临三大灾难链式反应: 用户体验崩塌 页面加载超时率上升37%用户跳出率增加52%核心业务转化率下降29% 系统稳定性危机 连接池耗…...
centOs7配置有限网络
最简单快速的是使用nmtui命令,采用图形页面修改。 点击编辑连接并回车: 选中编辑然后回车: 千万记住DNS服务器就是子网掩码,不是常说的DNS域名。把地址,网关,子网掩码配置好。只要ip不冲突,网…...
C语言 —— 指尖跃迁 刻印永恒 - 文件操作
目录 1. 什么是文件 1.1 程序文件 1.2 数据文件 1.3 文件名 2. 二进制文件和文本文件 3. 文件的打开与关闭 3.1 流和标准流 3.2 文件指针 3.3 文件的打开与关闭 fopen fclose 4. 文件的顺序读写 4.1 fgetc和fputc fgetc fputc 4.2 fgets和fputs fgets fputs…...
网络安全与信息安全的区别及共通
在数字化时代,网络安全与信息安全已成为保障个人、企业乃至国家正常运转的重要防线。尽管二者紧密相关且常被混为一谈,但实则存在显著差异。当然,它们也有一些相同点,比如都以保障数字环境下的安全为核心目标,均需要通…...
【愚公系列】《Python网络爬虫从入门到精通》052-Scrapy 编写 Item Pipeline
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
