3. Redis 高级特性:事务、Pipeline、Lua脚本、发布订阅与Stream

在削峰与缓存一致性场景中,仅靠基础读写操作无法应对高并发下的原子性、批量吞吐与消息解耦需求。Redis 提供的事务、Pipeline、Lua 脚本、发布订阅及 Stream 五大高级特性,分别从原子执行、批量加速、逻辑下沉、实时通知与可靠消息五个维度补全了缓存系统的能力。本章逐一剖析其原理、适用边界与实战陷阱。

3.1 Redis 事务:乐观锁与原子队列

Redis 事务通过 MULTI、EXEC、DISCARD、WATCH 四个命令实现。与传统关系型数据库不同,Redis 事务不支持回滚,但保证队列中的命令连续、隔离执行。

命令作用说明
MULTI开启事务后续命令进入队列,不立即执行
EXEC执行事务原子化执行队列中所有命令
DISCARD取消事务清空队列,退出事务状态
WATCH乐观锁监视监视 key 变化,若被修改则事务拒绝执行
实战要点:WATCH 实现 CAS(Compare And Set)机制。在缓存一致性中,常用于“先比较版本号再更新”的防并发覆盖场景。若 WATCH 的 key 在 EXEC 前被其他客户端修改,EXEC 返回 nil,需重试。
// 使用 WATCH 实现乐观锁扣减库存
WATCH stock:1001
stock = GET stock:1001
if stock > 0
    MULTI
    DECR stock:1001
    EXEC
else
    DISCARD
避坑:Redis 事务在命令入队时不会检查语法错误以外的逻辑错误(如对 string 类型执行 LPUSH),错误命令会在 EXEC 时抛出异常,但已成功的命令不会回滚。因此事务内命令应保证类型安全。

3.2 Pipeline 管道:批量网络优化

Pipeline 将多条命令打包一次性发送给 Redis 服务端,再一次性读取所有响应。在削峰场景中,Pipeline 能显著降低 RTT(往返时延)对吞吐的影响。

  • 适用场景:批量写入缓存、批量删除缓存键、批量读取不依赖中间结果的数据。
  • 不适用场景:命令之间存在前后依赖(如先 SET 再 GET 该值),此时必须使用 Lua 脚本保证原子性。
  • 注意事项:Pipeline 打包的命令数不宜过大(建议 50~200 条/批),否则会阻塞客户端或服务端缓冲区。
// Java Jedis Pipeline 示例
Pipeline pipeline = jedis.pipelined();
for (String key : keysToDelete) {
    pipeline.del(key);
}
pipeline.sync(); // 一次性发送并接收结果
性能对比:在 1ms 网络延迟下,1000 次独立 SET 耗时约 1000ms;使用 Pipeline 打包 100 条/批,总耗时约 10~15ms,吞吐提升 60~100 倍。

3.3 Lua 脚本:原子逻辑下沉

Redis 内嵌 Lua 5.1 解释器,允许将多个命令封装为一个脚本原子执行。脚本执行期间不会插入其他命令,天然具备事务性,且支持复杂逻辑判断。

对比维度Redis 事务Lua 脚本
原子性是是
条件分支不支持支持 if/else
中间结果复用不支持支持变量暂存
回滚不支持不支持
网络往返多次(入队+执行)一次(EVAL)
-- Lua 脚本:扣减库存并记录扣减日志
local stock = redis.call('GET', KEYS[1])
if not stock or tonumber(stock) < tonumber(ARGV[1]) then
    return 0  -- 库存不足
end
redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('LPUSH', KEYS[2], ARGV[2])  -- 记录日志
return 1
削峰实战:将“库存扣减 + 限流计数 + 缓存一致性标记”合并为一个 Lua 脚本,一次网络调用完成所有操作,避免多次 RTT 和并发竞态。
Lua 脚本原子执行流程 客户端 EVAL 脚本 Redis 引擎 GET 库存 条件判断 DECRBY LPUSH 日志 原子性保证 整个脚本作为一个整体执行,中间不会被其他客户端命令打断

3.4 发布订阅(Pub/Sub):实时消息通知

Redis 发布订阅模式基于频道实现:发布者向频道发送消息,所有订阅该频道的消费者实时接收。在缓存一致性中,常用于缓存失效通知和配置动态刷新。

  • 核心命令:PUBLISH channel message、SUBSCRIBE channel、UNSUBSCRIBE channel。
  • 特点:消息即发即失,不持久化;消费者离线则丢失消息。
  • 适用场景:实时性要求高、允许少量丢失的广播通知(如缓存预热信号、限流阈值变更)。
  • 不适用场景:需要可靠投递、消息回溯的场景。
生产避坑:Pub/Sub 的消费者断开重连后,会丢失断开期间的所有消息。在削峰场景中,若依赖 Pub/Sub 做缓存一致性同步,必须配合主动查询兜底,或改用 Redis Stream。

3.5 Redis Stream:持久化消息队列

Redis 5.0 引入的 Stream 数据类型,提供了持久化、消费者组、消息确认(ACK)等完整消息队列能力,是 Pub/Sub 的升级替代方案。

特性Pub/SubStream
消息持久化否是(RDB/AOF)
消费者组不支持支持(XGROUP)
消息确认不支持支持(XACK)
消息回溯不支持支持(XRANGE)
阻塞读取支持支持(XREAD BLOCK)
# 创建消费者组
XGROUP CREATE mystream mygroup $ MKSTREAM

# 消费者读取未确认消息
XREADGROUP GROUP mygroup consumer1 BLOCK 5000 COUNT 10 STREAMS mystream >

# 处理完成后确认消息
XACK mystream mygroup 1634829200000-0
削峰实战:将秒杀请求先写入 Stream,后端 worker 以消费者组模式异步消费,实现请求削峰 + 可靠处理。配合 PEL(Pending Entries List)实现失败重试,确保缓存一致性操作不丢失。
Redis Stream 消费者组架构 生产者 Stream 消息持久化存储 消费者组 mygroup C1 C2 C3 PEL 待确认列表 未 ACK 消息自动重试 Stream 削峰优势 消息持久化不丢失 | 消费者组水平扩展 | PEL 机制保障至少一次处理

3.6 特性选型对比

特性原子性持久化批量加速消息可靠推荐场景
事务✓———乐观锁防并发覆盖
Pipeline——✓—批量读写、缓存预热
Lua 脚本✓—✓—复杂原子操作、限流扣减
Pub/Sub———低实时广播、配置通知
Stream—✓—高削峰队列、缓存同步
架构建议:在缓存一致性实战中,优先组合使用 Lua 脚本(保证原子性)+ Stream(保证消息可靠投递),替代传统的事务 + Pub/Sub 组合,以获得更强的数据一致性保障。