redis事务与Lua脚本

1. Redis 原生事务

Redis 的事务通过 MULTI, EXEC, DISCARD, WATCH 等命令实现。

  • 工作机制

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

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

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

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

  • 特点

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

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

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

2. Lua 脚本

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

  • 工作机制

    • 使用 EVALSCRIPT LOAD + EVALSHA 来执行一段 Lua 脚本。

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

  • 特点

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

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

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

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


对比总结:为什么 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 是必需且逻辑无法融入单个脚本时,才使用原生事务。

相关推荐
不剪发的Tony老师8 小时前
SQL Studio:一个基于浏览器的数据库查询工具
数据库·sql
王道长服务器 | 亚马逊云9 小时前
AWS + SeyouCMS:海外资源站的高性能部署实战
服务器·网络·数据库·云计算·软件构建·aws
紫金桥软件10 小时前
组态软件和实时数据库区别大吗?
数据库·物联网·软件工程·scada·监控组态软件
TDengine (老段)10 小时前
益和热力性能优化实践:从 SQL Server 到 TDengine 时序数据库,写入快 20 秒、查询提速 5 倍
大数据·数据库·物联网·性能优化·时序数据库·tdengine·1024程序员节
yolo_Yang11 小时前
【MySQL】mysqldump使用方法
数据库·mysql·oracle
玩转测试开发11 小时前
xshell设置跳板机登录内网服务器
运维·服务器·数据库
你不是我我11 小时前
【Java 开发日记】运行时有出现过什么异常?
数据库·oracle
切糕师学AI12 小时前
C# 使用 CSRedisCore指南
redis·c#·.net core
夏玉林的学习之路12 小时前
正则表达式
数据库·c++·qt·mysql·正则表达式