Redis 进阶 03:Pipeline、事务与 Lua

实验基线:Redis 7.4.11,先完成环境准备。除标注为宿主机终端的代码外,Redis 命令在该课指定的 redis-cli 中执行;不复制输出结果作为命令。

学习目标#

分清 Pipeline、事务与 Lua 的能力边界,复现 WATCH 冲突,原子地建立有有效期的计数器。

原理:吞吐与正确性分别设计#

Pipeline 把多条命令批量发送,减少网络往返;它不是事务,客户端要检查每条响应。MULTI/EXEC 将一组命令排队执行,执行阶段不会被其他客户端命令插入,但 Redis 事务没有数据库式回滚。运行时某条命令出错,其他排队命令仍可能成功。

WATCH 是乐观并发控制。如果被监视的键在 EXEC 前被其他客户端修改或过期,EXEC 返回空响应,需要重新读取并有界重试。不要无限重试。

Lua 在 Redis 中原子执行,但会阻塞其他命令,因此脚本应短小、避免无界循环,且显式传入所有键。Cluster 中脚本涉及的键必须在同一槽,下一课学习该约束。

实验:事务执行#

在主实验容器 redis-learning 的 redis-cli 中:

UNLINK learn:atomic:count
MULTI
INCR learn:atomic:count
EXPIRE learn:atomic:count 60
EXEC
GET learn:atomic:count

MULTI 返回 OK,中间两条命令返回 QUEUED;EXEC 的两条响应都是 1,GET 返回 1。计数与过期设置在执行阶段连续完成。

实验:WATCH 冲突#

打开两个宿主机终端,分别执行 docker exec -it redis-learning redis-cli。

终端 A:

SET learn:atomic:balance 100
WATCH learn:atomic:balance
GET learn:atomic:balance

终端 B:

INCRBY learn:atomic:balance 10

回到 A:

MULTI
SET learn:atomic:balance 90
EXEC
GET learn:atomic:balance

EXEC 返回 nil,最后余额为 110。A 的基于旧值计算的更新没有提交。重新读取后可再次 WATCH,并在有限次数内重试;连接断开后不要继续假设原监视状态有效。

实验:Lua 原子计数与首次设置 TTL#

先清理,再在 redis-cli 输入一行脚本:

UNLINK learn:atomic:window
EVAL "local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]); end; return n" 1 learn:atomic:window 60
EVAL "local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]); end; return n" 1 learn:atomic:window 60
TTL learn:atomic:window

预期 1、2,剩余 TTL 不大于 60,第二次不会刷新到期时间。numkeys=1 表示只有一个键,60 属于 ARGV。这是从首次请求开始的 60 秒窗口,不是按整分钟对齐的窗口。

Pipeline 如何练习#

使用官方客户端的 Pipeline API 同时发送 100 次 INCR,并逐条检查结果,与串行模式测量总耗时。redis-cli 交互逐行输入不构成对网络 Pipeline 的有效性能比较。不要用 MULTI 代替 Pipeline 然后宣称测到了同一种行为。

常见错误#

  • Redis 事务某个运行时错误后希望全部回滚:无此保证。
  • Lua 内用动态拼接产生未在 KEYS 声明的键:不利于 Cluster 正确路由。
  • 脚本成功后数据库写入失败:Redis 原子性不跨越数据库。
  • Lua 非原子地调用外部 HTTP:Redis 脚本不能这样执行网络请求。

练习与参考答案#

把窗口计数换成每次增加 2,需要同时改变“首次创建”判断,不能只把 INCR 换成 INCRBY:

UNLINK learn:atomic:step
EVAL "local exists=redis.call('EXISTS',KEYS[1]); local n=redis.call('INCRBY',KEYS[1],2); if exists==0 then redis.call('EXPIRE',KEYS[1],ARGV[1]); end; return n" 1 learn:atomic:step 60

预期返回 2,TTL 存在。脚本针对受控整数键,不支持把任意已有类型自动修复。

清理#

UNWATCH
UNLINK learn:atomic:count learn:atomic:balance learn:atomic:window learn:atomic:step

官方参考#

Pipeline · 事务 · EVAL

学习导航#

上一课:Redis 进阶 02:RDB、AOF 与备份恢复 · 学习目录 · 下一课:Redis 进阶 04:缓存故障与数据一致性

Redis 进阶 03:Pipeline、事务与 Lua
作者:LcooYa
CC BY-NC-SA 4.0
相关文章
随机文章