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

redis事务与Lua脚本

1. Redis 原生事务

Redis 的事务通过 MULTIEXECDISCARDWATCH 等命令实现。

  • 工作机制

    1. MULTI:开启一个事务,后续的命令都会被放入一个队列中,而不会立即执行。

    2. 输入命令:将多个命令按顺序加入队列。

    3. EXEC:一次性、按顺序地执行队列中的所有命令。

    4. WATCH:在 MULTI 之前执行,用于监控一个或多个键,如果在 EXEC 执行前这些键被其他客户端修改,则整个事务会失败(返回 nil)。

  • 特点

    • 原子性EXEC 命令触发时,事务中的所有命令会作为一个独立的、连续的操作序列被执行。在 EXEC 命令执行期间,Redis 服务器不会处理其他客户端的任何命令。这保证了事务的原子性。

    • 没有回滚:这是 Redis 事务最著名的特点。如果事务中的某个命令执行失败(例如,对字符串执行了 HGET 操作),后续的命令仍然会继续执行,并且之前已经执行的命令也不会回滚。Redis 认为这种错误通常是编程错误,应该在开发阶段被发现,而不是通过复杂的回滚机制来处理。

    • 隔离性:通过 WATCH 实现乐观锁,可以保证在事务执行时,被监控的键没有被其他客户端修改,从而提供隔离性。

2. Lua 脚本

Redis 从 2.6 版本开始内置了对 Lua 脚本的支持。

  • 工作机制

    • 使用 EVAL 或 SCRIPT LOAD + EVALSHA 来执行一段 Lua 脚本。

    • 脚本中可以包含多个 Redis 命令和复杂的逻辑(如条件判断、循环等)。

  • 特点

    • 真正的原子性整个 Lua 脚本在执行时会被当作一个单命令。脚本在执行过程中,不会被其他任何命令或脚本打断。这是比 MULTI/EXEC 更严格的原子性。

    • 减少网络开销:可以将多个操作打包在一个脚本中,一次发送,一次返回,显著减少网络延迟。

    • 复杂性:可以在脚本中实现复杂的业务逻辑,这是简单的事务命令队列无法做到的。

    • 可复用性:通过 SCRIPT LOAD 和 EVALSHA,可以预加载脚本并多次调用,避免重复传输脚本内容。


对比总结:为什么 Lua 脚本通常是更好的选择?

特性Redis 事务Lua 脚本
原子性命令级别EXEC 期间原子,但某个命令失败不影响后续。脚本级别:整个脚本作为一个原子单位执行,要么全成功,要么全不执行。
错误处理部分失败,无回滚。如果脚本语法错误或 redis.call() 出错,整个脚本都不会执行。
复杂性只能将命令简单排队,无法加入逻辑(如 if-else)。支持复杂逻辑,可以包含条件、循环、局部变量等。
网络开销需要发送 MULTI,N个命令,EXEC,共 N+2 次网络往返。一次网络往返,将脚本和参数一次性发送。
性能事务中的命令在排队时不会被解析,在 EXEC 时一次性解析和执行。脚本在传输后被缓存,执行效率非常高。
WATCH 乐观锁支持,是保证 CAS 操作的核心。不支持 在脚本内直接使用 WATCH,但脚本的原子性本身就解决了大部分竞态条件问题。
阻塞风险事务中的命令应都是快速操作。编写糟糕的脚本(如包含长循环或死循环)会长时间阻塞整个 Redis 服务器,是危险的。

关键区别和选择场景

  1. 需要条件逻辑时,必须使用 Lua 脚本

    • 例如:只有在库存大于 0 时才执行扣减和创建订单。这在事务中无法实现,因为事务只是命令队列,无法根据中间结果做判断。

  2. 需要更严格的原子性和一致性时,推荐 Lua 脚本

    • 由于 Lua 脚本的“全有或全无”特性,它更适合需要强一致性的场景。而原生事务的“部分失败”特性需要客户端进行更复杂的错误处理。

  3. 当需要 WATCH 监控多个键,且逻辑简单时,可以使用原生事务

    • 虽然 Lua 脚本本身是原子的,但有些场景需要读取一个键的值,然后根据这个值(可能被其他客户端改变)来决定是否修改另一个键。这种跨键的、依赖于外部状态的 CAS 操作,使用 WATCH + 事务仍然是标准做法。

    • 举例:你想在用户积分(key A)大于 100 时,给他发一条消息(修改 key B)。你需要先 WATCH 积分 key,然后 MULTI,在事务中检查积分并决定是否发消息。如果在 EXEC 前积分被其他客户端修改,事务会失败,你可以重试。如果这个逻辑写在 Lua 脚本里,脚本读取积分的那一刻值是确定的,但无法感知到在它执行前这个积分是否已经被其他客户端修改过(因为 WATCH 机制在脚本外)。

  4. 性能敏感和网络延迟高的场景,优先使用 Lua 脚本

    • 一次网络往返的优势非常明显。

结论

Lua 脚本在功能上是 Redis 事务的超集,它提供了更强大的原子性、更丰富的逻辑能力和更低的网络开销。因此,对于绝大多数原本计划使用事务的场景,用 Lua 脚本来实现是更好、更推荐的选择。

唯一需要保留使用原生事务的情况是:你需要使用 WATCH 来实现一个涉及多个键的、逻辑简单的、依赖于在事务执行前键值是否被改变的乐观锁控制。即便如此,也可以考虑是否能用 Lua 脚本将相关键的操作合并到一个脚本中,从而从根本上避免竞态条件。

简而言之:能用 Lua 脚本就用 Lua 脚本。只有在 WATCH 是必需且逻辑无法融入单个脚本时,才使用原生事务。

http://www.dtcms.com/a/552855.html

相关文章:

  • 【技术选型】前端框架:Vue vs React - 组合式API与Hooks的哲学之争
  • 网站建设网网站建设全网营销客户资源
  • Python 数据可视化:用 Matplotlib 绘制多维度对比图表
  • 【axf文件解析与J-Link通讯实战(五)】PySide6图形界面与数据可视化集成
  • Android 权限管理:适配 Android 14 运行时权限申请(含后台定位)
  • 涡阳网站优化wordpress进入后台空白
  • 【实战案例】火语言 RPA 采集小说站已完结书名(自动翻页判断),保存到Excel 全流程(附完整脚本)
  • 基于微信小程序的背单词系统x1o5sz72(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 力扣hot100-------11、盛最多水的容器(java版)
  • Visual Basic 菜单编辑器
  • 本地部署轻量级持续集成工具 Drone CI 并实现外部访问
  • gitlab-ci中cicd+helm实现devops自动化部署到k8s
  • 网站欢迎页面怎么做江门建站公司模板
  • 评论回复网站怎么做郑州百度搜索优化
  • Rust开发之使用derive宏自动实现Trait(Clone、Debug)
  • 15. setState的更新是异步的吗
  • Qwen2.5-VL开源,斩获多榜单冠军!
  • Prometheus和Grafana简介
  • 基于深度学习的医疗器械分类编码映射系统:设计篇
  • Rust开发之自定义错误类型(实现Error trait)
  • 【Java Web学习 | 第三篇】CSS(2) - 元素显示模式
  • 10月31日
  • Mybatis-Plus实现MySQL分表
  • 兵团住房和城乡建设局网站网站设计标杆企业
  • 快充新标杆:AVS 协议如何重塑手机充电体验
  • LIUNX 与手机安卓的文件互传 的常用方法
  • 第一届数证杯做题笔记(流量分析和手机取证)
  • 【IO多路转接】深入解析 poll:从接口到服务器实现
  • 【Spring Boot】Spring Boot解决循环依赖
  • 网站开发发展趋势2018网上建立网站赚钱