详解Redis的LUA脚本、管道 (Pipelining)、事务事务 (Transactions)

1. 管道 (Pipelining)

  • 网络延迟 (Round-Trip Time - RTT) 瓶颈。

    • 在传统模式下,客户端发送一个命令 -> 等待 Redis 服务器处理并返回结果 -> 再发送下一个命令。如果客户端需要执行大量命令(例如设置或获取多个键),每个命令之间的网络往返时间(RTT)会成为主要的性能瓶颈,即使每个命令本身执行很快。

  • 原理:

    • 客户端一次性打包多个命令发送给 Redis 服务器。

    • Redis 服务器按顺序连续执行收到的所有命令。

    • 服务器执行完所有命令后,一次性将所有结果打包返回给客户端。

  • 关键特性:

    • 性能优化: 核心目标是极大减少网络 RTT 次数。将 N 次 RTT 减少为 1 次 RTT(发送请求包 + 接收响应包),显著提升吞吐量,尤其适合批量写入或读取。

    • 非原子性: 管道中的命令虽然是一起发送和返回的,但在执行过程中,命令之间并不是原子的。Redis 会按顺序依次执行每个命令。其他客户端的命令可能穿插在管道中某个命令执行的前后。

    • 无回滚: 如果管道中某个命令执行出错(例如语法错误、对错误类型操作),它不会影响其他命令的执行,也不会导致整个管道回滚。客户端在收到所有结果后需要自行检查每个命令的返回结果。

  • 使用方式:

    • 客户端库通常提供管道支持(如 Python 的 redis-py 使用 pipeline() 方法)。

    • 在 Redis CLI 中,可以手动将多个命令写在一行(用分号 ; 分隔)或使用 echo 和 nc 组合。

  • 适用场景:

    • 需要执行大量独立、无依赖关系的命令,且对原子性没有要求。

    • 批量设置(MSET)、批量获取(MGET 虽然原生支持,但复杂键名时仍需管道)、批量删除(DEL key1 key2 ...)。

    • 数据导入/导出。

    • 需要最大化吞吐量的场景(如实时计数器批量更新)。

  • 优点:

    • 大幅提升吞吐量,减少网络延迟影响。

    • 使用相对简单。

  • 缺点:

    • 不保证原子性: 命令可能被其他客户端插入。

    • 不提供隔离性: 管道执行过程中,其他客户端可以修改数据。

    • 错误处理: 需要客户端在收到响应后逐一检查每个命令的结果。

2. 事务 (Transactions)

  • 简单命令序列的原子性执行需求。

    • 确保一组命令在 EXEC 时作为一个整体连续执行(执行原子性),但运行时错误不会回滚已执行的命令。

  • 核心命令:

    • MULTI: 标记事务开始。之后的命令不会立即执行,而是被放入一个队列

    • EXEC: 执行事务队列中的所有命令。Redis 会按顺序、连续地、原子地执行队列中的所有命令。在执行 EXEC 期间,服务器不会被其他客户端的命令打断。

    • DISCARD: 放弃事务,清空队列。

    • WATCH key [key ...]: 关键机制! 在 MULTI 之前执行。监视一个或多个键。如果在 WATCH 之后、EXEC 之前,有任何被监视的键被其他客户端修改,那么当该客户端执行 EXEC 时,整个事务将被取消(返回 nil)。这是 Redis 实现 CAS(Compare-and-Set)乐观锁的基础。

  • 关键特性:

    • 原子性: EXEC 命令触发时,队列中的所有命令作为一个整体执行,不会被其他命令打断。

    • 隔离性: 通过 WATCH 实现乐观锁,可检测并发修改,但事务本身无隔离性保证。

    • 无回滚: 这是 Redis 事务的一个重要特点! 如果在事务执行过程中(EXEC 之后)某个命令运行时出错(例如对字符串执行 HINCRBY),只有出错的命令不会生效,而队列中其他命令依然会被执行! Redis 不会回滚已经执行成功的命令。事务的错误通常发生在入队时(命令语法错误,Redis 会拒绝入队)或运行时(数据类型错误)。

  • 执行流程:

    • WATCH key (可选,用于乐观锁)

    • MULTI

    • 发送要执行的命令 (这些命令被放入队列,返回 QUEUED)

    • EXEC 或 DISCARD

      • 如果执行 EXEC

        • 检查 WATCH 的键是否被修改过?是 -> 放弃执行,返回 (nil)

        • 否 -> 按顺序原子执行所有队列命令,返回所有命令的结果数组。

  • 适用场景:

    • 需要保证一组命令原子执行的简单场景(例如:转账 A-100; B+100)。

    • 结合 WATCH 实现乐观锁,处理简单的并发竞争(例如:库存扣减、抢票)。

  • 优点:

    • 提供命令序列的原子性保证。

    • WATCH 提供了基本的并发控制手段。

  • 缺点:

    • 运行时错误不回滚: 事务执行中部分命令失败,其他命令仍然生效。需要客户端精心设计命令或依赖 WATCH 重试。

    • 无法获取中间结果: 在事务中(MULTI 之后),无法直接获取之前命令的执行结果来决定后续操作(所有命令在 EXEC 时一次性执行)。命令是静态入队的。

    • 性能: 虽然 MULTI/EXEC 本身开销不大,但 WATCH 失败重试可能导致性能下降。EXEC 执行期间会阻塞其他命令。

    • 复杂性: 需要理解 WATCH 的机制和错误处理逻辑。

3. Lua 脚本 (Lua Scripting)

  •  复杂原子操作、需要中间逻辑判断、事务的局限性。

    • 事务无法在命令执行过程中根据中间结果做动态决策。

    • 事务的错误回滚行为不符合某些预期。

    • 需要执行更复杂的逻辑,这些逻辑无法简单地拆分成一组 Redis 命令序列。

  • 原理:

    • Redis 内嵌了 Lua 5.1 解释器。

    • 客户端将一段 Lua 脚本发送给 Redis 服务器(使用 EVAL 或 EVALSHA)。

    • Redis 服务器在单个线程中原子性地执行整个 Lua 脚本

    • 脚本执行期间,服务器不会执行任何其他命令或脚本(完全隔离)。

    • 脚本可以访问和操作 Redis 数据,可以包含复杂的逻辑(条件判断、循环、计算等)。

    • 脚本最后返回一个结果给客户端。

  • 关键特性:

    • 原子性: 核心优势! 整个脚本的执行是原子的、隔离的。脚本执行过程中不会有其他命令或脚本执行,脚本看到的数据视图在执行开始时是确定的。

    • 灵活性: 可以使用 Lua 语言的全部特性(变量、条件、循环、函数、表等)实现复杂的业务逻辑。

    • 可获取中间结果: 脚本内部可以执行多个 Redis 命令(通过 redis.call() 或 redis.pcall()),并能立即获取这些命令的返回值,用于后续的逻辑判断和计算。这是超越事务的关键点。

    • redis.call(): 执行 Redis 命令,如果命令出错会抛出 Lua 错误,导致整个脚本停止执行(类似事务中的运行时错误)。

    • redis.pcall(): 执行 Redis 命令,如果命令出错会捕获错误并以 Lua 表的形式返回错误信息,不会中断脚本执行,脚本可以处理这个错误。

    • 复用性: 脚本可以被缓存(使用 SCRIPT LOAD 返回 SHA1 摘要),后续通过 EVALSHA 用摘要执行,减少网络传输。

  • 使用方式:

    • EVAL "lua_script" numkeys key [key ...] arg [arg ...]

      • lua_script: Lua 脚本字符串。

      • numkeys: 后面跟着的键名的个数。

      • key [key ...]: 脚本中用到的 Redis 键名,通过 KEYS[1]KEYS[2] 访问。

      • arg [arg ...]: 传递给脚本的附加参数,通过 ARGV[1]ARGV[2] 访问。

    • SCRIPT LOAD "lua_script": 加载脚本并返回 SHA1 摘要。

    • EVALSHA sha1 numkeys key [key ...] arg [arg ...]: 使用 SHA1 摘要执行缓存的脚本。

    • SCRIPT EXISTS sha1 [sha1 ...]: 检查脚本是否已缓存。

    • SCRIPT FLUSH: 清空脚本缓存。

    • SCRIPT KILL: 终止当前正在执行的脚本(仅当脚本未执行任何写操作时有效)。

  • 适用场景:

    • 需要真正原子性的复杂操作: 例如:检查库存、扣减库存、记录订单;实现分布式锁(Redlock 或更复杂的锁);实现自定义的原子计数器/限流器。

    • 需要基于中间结果做决策: 例如:如果 HGET 的值大于 X,则执行 ZADD 和 PUBLISH

    • 封装复杂操作: 将多个 Redis 命令和业务逻辑封装成一个原子操作,简化客户端代码。

    • 保证计算与操作原子性: 在脚本中进行计算并基于计算结果更新 Redis。

  • 优点:

    • 真正的原子性和隔离性: 脚本是执行 Redis 命令的最小单元。

    • 强大的灵活性: 几乎可以实现任何复杂的原子逻辑。

    • 可获取中间结果: 脚本内部可基于之前命令的返回值进行决策。

    • 复用性: EVALSHA 减少网络开销。

    • 性能: 脚本在服务器端执行,避免了多次网络 RTT(虽然单次 EVAL 请求可能比单个命令大,但比多次 RTT 好)。脚本执行很快(Redis 单线程高效)。

  • 缺点/注意事项:

    • 脚本编写: 需要学习 Lua 语法和 Redis Lua API (redis.callredis.pcall)。

    • 调试: Redis Lua 脚本调试相对困难。

    • 阻塞风险: 非常重要! 一个运行缓慢的 Lua 脚本会阻塞整个 Redis 服务器,导致所有其他客户端请求超时。必须确保脚本是高效的、执行时间可预测的。避免长循环、避免执行大量耗时的命令、避免使用 KEYS 命令。

    • 可复制性: Lua 脚本在复制和持久化时有一些特殊规则(通常脚本本身和效果都会被复制)。

    • 资源管理: 脚本缓存需要管理(虽然 Redis 会管理,但 SCRIPT FLUSH 需谨慎)。

    • 错误处理: 需要理解 redis.call 和 redis.pcall 的错误处理差异。脚本中的逻辑错误可能导致整个操作失败(原子性保证)。

总结对比表

特性管道 (Pipeline)事务 (Transaction)Lua 脚本 (Lua Scripting)
核心目标优化性能 (减少 RTT)保证隔离性 (命令连续执行)原子性执行复杂逻辑
发送方式✅ 批量发送❌ 逐条发送(MULTI+命令)✅ 一次性发送(EVAL/EVALSHA
原子性❌ 不保证。命令逐个执行,可能被穿插。⚠️ 隔离性而非严格原子性。 EXEC 期间连续执行,但运行时错误不回滚。✅ 强原子性。 整个脚本执行不可中断,脚本内错误会中止执行(已执行效果保留)。
性能优化✅ 显著减少 RTT (主要优势)⚠️ 批量执行减少部分 RTT,但 WATCH 失败重试会降低效率。✅ 减少复杂逻辑的 RTT,但脚本执行本身可能阻塞服务器。
复杂逻辑❌ 只能执行简单命令序列。❌ 只能执行简单命令序列。✅ 支持条件、循环、变量、函数等复杂 Lua 逻辑。
错误处理每个命令独立成功/失败。⚠️ 入队错误导致事务放弃;运行时错误仅失败命令,其他执行。⚠️ 脚本或命令错误导致脚本中止,已执行命令效果保留。
阻塞性低。服务器按序执行,但命令间可穿插其他客户端命令。EXEC 执行期间阻塞其他命令。整个脚本执行期间完全阻塞服务器!
关键命令客户端实现(一次性发送/接收)。MULTIEXECDISCARDWATCHEVALEVALSHASCRIPT LOADSCRIPT FLUSH
典型场景大批量独立命令操作(无原子性要求)。需要连续执行且不被干扰的命令组 + 乐观锁 (WATCH)。需要原子性执行的复杂业务逻辑(如分布式锁、限流)。

如何选择?

  1. 需要高性能批量读写,且命令独立、无原子性要求? -> 管道

  2. 需要保证几个简单命令要么都成功要么都不成功,并且并发竞争不激烈或可以通过 WATCH 重试解决? -> 事务

  3. 需要实现复杂的业务逻辑、需要基于中间结果做决策、需要真正的原子性保证(即使包含逻辑判断和循环)、或者事务的运行时错误不回滚特性不符合需求? -> Lua 脚本 (务必保证脚本高效!)。

  4. 需要结合使用? 很常见!例如,使用管道发送多个 EVAL 或 EVALSHA 命令来批量执行多个(独立的)原子操作。

重要补充:Redis 7 函数 (Functions)

        Redis 7 引入了 Redis Functions,这是对 Lua 脚本的进一步封装和增强。它允许你将 Lua 脚本注册为命名的函数(FUNCTION LOAD),然后像调用内置命令一样调用它们(FCALLFCALL_RO)。这提供了更好的代码组织、复用性、访问控制和调试支持(通过 FUNCTION 命令族管理)。函数在底层仍然是 Lua 脚本执行,继承了其原子性、隔离性、阻塞风险等核心特性,但提供了更优雅和安全的使用方式。对于新项目,特别是需要复用复杂逻辑的场景,推荐优先考虑 Redis Functions。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SIP 协议中的定时器

SIP(Session Initiation Protocol) 是一种信令协议,广泛用于建立、维持和终止多媒体会话(如VoIP通话)。作为基于UDP等不可靠传输的协议,SIP 通过多个定时器机制来确保消息的可靠传输和状态机的正常运行。 …

【机器学习深度学习】偏置项(Bias)概念

目录 前言 一、先说结论:偏置项是“默认起点” 二、类比理解 类比 1:老师给学生的“基础分” 类比 2:预测房价时的“固定成本” 三、没有偏置项的模型,会有什么问题? 四、在神经网络中,偏置项是神经…

使用数组 海选女主角

问题描述 面试那天,刚好来了m * n个MM,站成一个m * n的队列,副导演Fe(OH)2为每个MM打了分数,分数都是32位有符号整数。 一开始我很纳闷:分数怎么还有负的?Fe(OH)2解释说,根据选拔规则&#xff…

从0开始学习R语言--Day29--社交网络分析

在探寻数据之间的关系时,由于数据类型的限制,很多时候我们可以从数据的现实角度出发去选择方法,而不是一昧地从头尝试不同方法去分类。假如我们用的是传染病在市面上的传播路径数据,亦或是病毒对于基因的感染模块,就可…

一款基于 React 的开源酷炫动画库

React Bits 是一个开源的交互式 React 组件库,包含一系列动画化、交互式且完全可定制的 React 组件,用于构建令人惊艳且难忘的用户界面,可帮助开发者在 React 应用中轻松实现各种动画效果。它提供了超过70种动画组件,分为文本动画…

深入理解前端理念bundleless

Bundleless 是一种新兴的前端开发趋势,它的核心思想是减少或完全去除传统的打包步骤,直接利用浏览器对现代 JavaScript 特性(尤其是 ES 模块)的原生支持。这一趋势背后的推动力包括现代浏览器的进步、开发者对更快开发反馈的需求以及更简单的开发流程。以下是对 bundleless…

马斯克YC技术核弹全拆解:Neuralink信号编译器架构·星舰着陆AI代码·AGI防御协议(附可复现算法核心/开源替代方案/中国技术对标路径)

一、Neuralink技术栈深度剖析 ▶ 神经信号编译架构(基于已公开专利US20220369936) 关键算法实现: # 运动意图解码核心(简化版) import numpy as np from sklearn.ensemble import RandomForestClassifierclass Neura…

【RK3568 嵌入式linux QT开发笔记】 二维码开源库 libqrencode 交叉静态编译和使用

本文参考文章:https://blog.csdn.net/qq_41630102/article/details/108306720 参考文章有些地方描述的有疏漏,导致笔者学习过程中,编译的.a文件无法在RK3568平台运行,故写本文做了修正,以下仅是自我学习的笔记&#xf…

git本地裸仓库的“激活”:在同一台 Linux 服务器上创建工作区

大家好!在之前的文章中,我们探讨了 Git 裸仓库(Bare Repository)的概念,它是没有工作目录,只包含 .git 目录内容的特殊仓库格式,非常适合作为中心化的代码集散地或备份。我们也了解了 git clone…

如何排查在docker中运行软件的故障:Docker故障排查可视化指南,三招锁定问题根源

很多刚接触Docker的朋友常觉得故障排查很神秘。其实只需关注CPU、内存、磁盘这三大资源指标!Linux终端虽强大但不够直观,下面教你用可视化工具轻松监控: 一、宿主机全局监控:FinalShell 掌控全局 连接宿主机 打开FinalShell&…

【论文笔记】【强化微调】T-GRPO:对视频数据进行强化微调

tulerfeng/Video-R1: Video-R1: Reinforcing Video Reasoning in MLLMs [🔥the first paper to explore R1 for video] 1. 引述 在强化微调中,像 GRPO、DAPO 这样的方法都是对文本或者图片进行微调思考,所以这类微调方法不对时序信息做处理&…

【Unity】动画系统

0 前言 早些时间学动画系统时的笔记,实际还没学完,后续计划会慢慢补全吧。 1 动画 通常来说动画都是动画师来做的,不过Unity也能实现简单的动画效果。PS:官方文档中,将动画称之为动画剪辑。 1.1 创建动画 首先在Unit…

C++二级指针的用法指向指针的指针(多级间接寻址)

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。 指针的指针就是将指针的地址存放在另一个指针里面。 通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个…

【格与代数系统】示例

【格与代数系统】格与代数系统汇总 例1 设是由诱导的代数系统,则其上的二元运算满足(ABCD) A. B. C. D. 代数系统满足交换律、幂等律、吸收律、结合律 例2 是(ABCD) A.有界格 有界格:有最大、最小元…

Stable Diffusion 项目实战落地:手机壁纸制作-第一篇 从零基础到生成艺术品的第一步!

大家好!欢迎来到《StableDiffusion实战-手机壁纸制作》系列的第一篇! 在这一篇文章里,我们将一起探索如何用StableDiffusion(SD)这款强大的工具,快速制作出炫酷的手机壁纸。 如果你对生成艺术、AI绘图感兴趣,那你一定不能错过! 你能做什么?你将做什么! 在之前的系…

WEB3开启 Hardhat 自动验证有什么意义

这是个非常好的问题,尤其是你在学习 Web3 后端开发时,理解为什么要启用 Hardhat 自动验证合约源码,会让你开发流程更完整、更专业。 ✅ 一句话总结: 开启 Hardhat 自动验证的意义是:让你的合约在区块链浏览器&#xff…

Qt窗⼝的学习(一)

Qt窗⼝是通过QMainWindow类来实现的。 QMainWindow是⼀个为⽤⼾提供主窗⼝程序的类,继承⾃QWidget类,并且提供了⼀个预定义的 布局。QMainWindow包含⼀个菜单栏(menubar)、多个⼯具栏(toolbars)、多个浮动窗⼝(铆 接部…

C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)

📚 本文主要总结了一些常见的C面试题,主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能,掌握这些内容,基本上就满足C的岗位技能(红色标记为重点内容),欢迎大家前来学习指正&…

git提交的脚本无执行权限怎么办

问题描述 自己写的小项目,没有在服务器安装 Jenkins 进行项目部署,为了图方便,在项目中编写了一个 deploy.sh 脚本文件用来执行项目部署。但是在服务器上 pull 下来之后发现脚本文件没有执行权限,通过 chmod 命令进行赋权&#x…

004.chromium编译进阶-启动时传入cookies

一、目标: 实现传入参数--set-cookies[{"domain":"https://baidu.com","name":"AAAA","value":"111"},{"domain":"https://baidu.com","name":"BBB","…