Pipeline 批量发命令,Lua 脚本也能批量操作,但只有 Lua 是原子的。
为什么?答案藏在 Redis 的单线程心脏里。
在使用 Redis 时,你可能遇到过这样的困惑:
- 我用
Pipeline一口气发了 100 个命令,效率很高; - 但我听说
Pipeline不是原子的,中间可能被其他客户端插队; - 而写个 Lua 脚本,哪怕包含 10 条 Redis 命令,却能保证完全原子执行。
这到底是为什么?难道 Redis 对 Lua 有"特殊照顾"?
今天,我们就从 Redis 内部执行模型 出发,彻底讲清楚:Pipeline 和 Lua 脚本在 Redis 眼中,根本就不是一类东西。
一、Pipeline:只是"网络打包",不是"执行合并"
很多人误以为 Pipeline 是一种"批量命令",其实它根本不是 Redis 的功能 ,而是客户端的优化技巧。
客户端视角:
dart
// 普通方式:3 次网络往返
redis.set("a", "1");
redis.set("b", "2");
redis.set("c", "3");
// Pipeline:1 次网络往返
pipeline.set("a", "1");
pipeline.set("b", "2");
pipeline.set("c", "3");
pipeline.sync();
看起来很高效,对吧?但关键在于:Redis 并不知道你在用 Pipeline。
Redis 服务端视角:
当 Redis 收到 TCP 流中的数据:
css
SET a 1\r\nSET b 2\r\nSET c 3\r\n
它会逐行解析 ,然后依次执行三个独立的 SET 命令。
也就是说,在 Redis 内部,这三个命令和你分开三次发送没有任何区别 ------它们会被一个一个放进命令队列,再一个一个执行。
问题来了:命令之间会发生什么?
Redis 的主循环大致如下(简化版):
scss
while (1) {
// 1. 处理网络 I/O(读取所有客户端数据)
aeProcessEvents();
// 2. 执行命令队列中的命令
while (hasCommand()) {
executeCommand(dequeueCommand());
// ← 执行完一条命令后,回到循环顶部!
}
}
注意:每执行完一条命令,Redis 就会回到事件循环顶部,去处理:
- 其他客户端的新请求;
- 定时任务(如 key 过期);
- 主从同步等。
所以,你的 SET a 1 和 SET b 2 之间,完全可能插入别人发来的 DEL x!
✅ Pipeline 只省了网络 RTT,没改变命令的执行语义。
二、Lua 脚本:Redis 的"独占模式"
相比之下,Lua 脚本在 Redis 中享有"VIP 待遇"。
当你执行:
less
EVAL "redis.call('SET', KEYS[1], '1'); redis.call('SET', KEYS[2], '2')" 2 a b
Redis 不会把它拆成两个 SET 命令。而是:
- 整个脚本被视为一个不可分割的单元;
- 执行期间,Redis 会暂停处理任何新命令;
- 脚本内部的所有
redis.call()都在当前上下文中直接执行,不经过命令队列。
技术实现:lua_caller 标志位
在 Redis 源码中,有一个关键变量:
arduino
struct redisServer {
client *lua_caller; // 当前正在执行 Lua 脚本的客户端
};
在事件循环的关键位置(如 beforeSleep、processCommand),Redis 会检查:
arduino
if (server.lua_caller != NULL) {
// 正在执行 Lua 脚本,跳过处理新命令!
return;
}
这意味着:只要 Lua 脚本没跑完,其他客户端的命令一律排队等待。
✅ Lua 脚本 = 占用 Redis 主线程的"临界区"
三、一张图看懂本质区别
css
普通命令 / Pipeline 命令:
[SET a 1] → 执行 → 回到事件循环 → [其他客户端命令] → [SET b 2] → 执行
Lua 脚本:
[Lua 开始] → 执行 SET a 1 → 执行 SET b 2 → [Lua 结束] → 其他命令才能进来
Pipeline 的命令是"散装"的,Lua 脚本是"封装好的原子包"。
四、常见误区澄清
| 误区 | 正解 |
|---|---|
| "Pipeline 是事务" | ❌ Pipeline 没有任何事务语义,只是网络优化 |
| "Lua 脚本快是因为本地执行" | ❌ Lua 在 Redis 服务端执行,快是因为原子+无网络开销 |
| "Cluster 下 Pipeline 也能跨节点原子" | ❌ Cluster 下 Pipeline 必须同 slot,且仍非原子 |
五、如何选择?
| 场景 | 推荐方案 |
|---|---|
| 需要原子性(如扣款、限流) | ✅ Lua 脚本 |
| 只需提升吞吐(如批量导入) | ✅ Pipeline |
| 简单批量读写(同 key) | ✅ MGET / MSET(内置原子命令) |
💡 记住:
要速度 → Pipeline;
要一致 → Lua。
结语
Redis 的简洁之美,正体现在它的单线程模型 上。
Pipeline 和 Lua 脚本看似都能"批量操作",但一个只是网络层的巧思 ,另一个则是执行层的保障。