当前位置: 首页 > news >正文

Redis 的指令执行方式:Pipeline、事务与 Lua 脚本的对比

Pipeline

客户端将多条命令打包发送,服务器顺序执行并一次性返回所有结果。可以减少网络往返延迟(RTT)以提升吞吐量。

需要注意的是,Pipeline 中的命令按顺序执行,但中间可能被其他客户端的命令打断

典型场景:批量插入、查询或更新数据,也就是命令间没有什么依赖关系的情况。

比如下面这个例子,Pipeline 将 3 个 SET 打包发送,减少三次网络往返为一次。假设单次命令 RTT 为 1ms,3 次命令需 3ms;使用 Pipeline 仅需约 1ms。

SET key1 value1
SET key2 value2
SET key3 value3

事务

可以确保一组命令“原子”执行,确保一组命令执行过程中不被其他客户端打断如果事务中有命令失败,整个事务不会回滚,但后续命令不会执行

通常,它会结合 WATCH 来实现乐观锁

另外,Redis 中的原子性和 MySQL 中的原子性意义不同。MySQL 的原子性意味着,要么全部执行成功,要么就不执行,它会涉及回滚的操作。但 Redis 没有没有回滚的操作(为了性能,实现回滚需要维护事务日志等其他一些机制,会增加开销),它的原子性指,一组命令顺序执行,不会被其他命令打断。

还有,Redis 的事务并不会像 Pipeline 一样,将命令一起发送给 Redis,而是会一条一条发送。为什么?可以结合下面的过程,这样的话可以用来进行语法检查。

客户端发送 MULTI,开启事务。
客户端逐条发送命令(如 SET、INCR),每条命令立即传输到服务器。
服务器收到命令后,不执行,而是将其放入事务队列,并返回 QUEUED 响应。
客户端发送 EXEC,服务器原子性执行队列中的所有命令,并返回结果。
或者,客户端发送 DISCARD,清空队列,取消事务。

在实际使用中,比如在 Python 中的 redis-py 库,默认情况下,Pipeline 会以事务的方式运行,来优化命令发送。

pipe = r.pipeline()  # 创建 Pipeline
pipe.multi()  # 开始事务;可以不写
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.execute()  # 发送并执行事务

典型场景:需要保证数据一致性的操作,如转账、库存扣减。

WATCH accountA  -- 监控账户 A 的键,防止被其他客户端修改
MULTI
GET accountA -- 如果账户 A 的值发生改变,剩下的命令将不会执行
SET accountA $new_balanceA -- 更新账户 A 余额(减 100)
SET accountB $new_balanceB -- 更新账户 B 余额(加 100)
EXEC

Lua 脚本

Lua 脚本可以在一条命令中实现复杂逻辑,如条件判断、循环、错误控制。它和事务功能上有些相似,都是为了实现原子性。不过 Lua 脚本有两种选择,redis.call() 失败时脚本停止(前面已经执行完的命令不会受影响);redis.pcall() 捕获错误继续执行。

它比起事务有什么好处呢?

  • 比如事务中需要依赖于一个 GET 操作的结果,来决定后面的操作,事务无法实现,需要结合客户端的代码,加大了复杂性
  • 事务中的每条命令都会与 Redis 服务器进行网络交互,增加了客户端与服务器的交互

另外,为了避免每次都需要传输完整的 Lua 脚本给 Redis,Redis 还设立了一个缓冲区,来去存放 Lua 脚本的内容以及它的 SHA1 值(一种哈希算法,将 Lua 脚本内容映射为固定长度的唯一标识符)。之后只需传输对应的 SHA1 值即可执行对应的脚本。

总之,Lua 脚本会更灵活,事务能做的,Lua 都能做。所以能用 Lua 就用 Lua

典型场景:需要保证数据一致性的操作,如转账、库存扣减。和事务差不多。

local accountA = KEYS[1]    -- 账户 A 的键
local accountB = KEYS[2]    -- 账户 B 的键
local amount = tonumber(ARGV[1])  -- 转账金额(转换为数字)-- 获取账户 A 余额(不存在则默认为 0)
local balanceA = redis.call('GET', accountA) or 0
balanceA = tonumber(balanceA)-- 检查余额是否足够
if balanceA < amount thenreturn 0  -- 余额不足,返回 0
end-- 获取账户 B 余额(不存在则默认为 0)
local balanceB = redis.call('GET', accountB) or 0
balanceB = tonumber(balanceB)-- 执行转账
redis.call('SET', accountA, balanceA - amount)  -- 扣减账户 A
redis.call('SET', accountB, balanceB + amount)  -- 增加账户 Breturn 1  -- 转账成功,返回 1-- Redis 调用方式:
EVAL "local accountA = KEYS[1] local accountB = KEYS[2] local amount = tonumber(ARGV[1]) local balanceA = redis.call('GET', accountA) or 0 balanceA = tonumber(balanceA) if balanceA < amount then return 0 end local balanceB = redis.call('GET', accountB) or 0 balanceB = tonumber(balanceB) redis.call('SET', accountA, balanceA - amount) redis.call('SET', accountB, balanceB + amount) return 1" 2 accountA accountB 30

参考

  1. Redis 事务 vs Lua,区别以及如何选择
  2. 【Redis】- 事务和Lua脚本
  3. Redis 管道、事务、Lua 脚本对比
http://www.xdnf.cn/news/6067.html

相关文章:

  • HTTP:九.WEB机器人
  • 探索 HumanoidBench:类人机器人学习的新平台
  • 甘果桌面tv版下载-甘果桌面安卓电视版使用教程
  • OpenAI 34页最佳构建Agent实践
  • Python(23)Python异常处理完全指南:从防御到调试的工程实践
  • 使用 Vue 开发登录页面的完整指南
  • 解决 Spring Boot 多数据源环境下事务管理器冲突问题(非Neo4j请求标记了 @Transactional 尝试启动Neo4j的事务管理器)
  • 数据库原理及应用mysql版陈业斌实验四
  • 若依同步企业微信架构及ACTIVITI
  • docker部署springboot(eureka server)项目
  • 珈和科技遥感赋能农业保险创新 入选省级卫星应用示范标杆
  • 从零开始物理引擎(六)- 重构完成与MVP理解
  • Windows程序包管理器WinGet实战
  • 极狐GitLab 议题和史诗创建的速率限制如何设置?
  • 2025.04.18|【Map】地图绘图技巧全解
  • 【MySQL】初识数据库
  • 电脑 BIOS 操作指南(Computer BIOS Operation Guide)
  • Houdini python code:参数指定文件路径
  • 13.编码器的结构
  • 5.Rust+Axum:打造高效错误处理与响应转换机制
  • Wireshark 搜索组合速查表
  • HTML新标签与核心 API 实战
  • tomcat 的安装与启动
  • 具身智能机器人学习路线全解析
  • Muduo网络库实现 [十四] - HttpResponse模块
  • 【4.1.-4.20学习周报】
  • 信号的传输方式
  • JS实现RSA加密
  • Redis面试——日志
  • 《Java 泛型的作用与常见用法详解》