Database

redis pipeline

Pipeline

Redis

Pipeline

Pipeline 让客户端把多条命令打包一次性发给 Redis,不用等每条命令的响应就能继续发下一条,最后再一起收结果。核心价值是减少网络往返次数,性能提升非常明显。

正常执行 100 条命令,客户端要和 Redis 来回通信 100 次。用 Pipeline 的话,100 条命令一次性发过去,Redis 按顺序执行完,结果一次性返回,网络往返只有 1 次。

假设网络延迟 1ms,100 条命令正常执行光网络等待就要 100ms,用 Pipeline 只要 1ms。命令越多、网络延迟越高,Pipeline 的收益越大。

image.png

Pipeline 的好处:

1)节省网络往返时间,RTT 从 N 次降到 1 次

2)减少系统调用的上下文切换开销,100 条命令只需要 1 次用户态到内核态的切换

Pipeline vs 事务 vs Lua脚本

  • Pipeline 不保证原子性。10 条命令打包发过去,第 5 条执行失败了,前 4 条不会回滚,后 5 条照样执行。它只是个批量发送的优化手段,跟原子性没关系。

  • 事务用 MULTI/EXEC 包裹命令,能保证这批命令连续执行不被其他客户端的命令插队,但也不支持回滚。(redis的事务不具备原子性,事务的原子性指的是事务内的操作要么同时成功要么同时失败,而redis的事务即便执行失败也会继续执行,且没有回滚)

  • Lua 脚本是真正的原子执行,整个脚本作为一条命令跑,中间不会被打断。但 Lua 脚本也不支持失败回滚。

img

特性Pipeline事务Lua 脚本
原子性不保证命令连续执行,不被插队整体原子执行
失败回滚不支持不支持不支持
网络往返1 次2 次1 次
条件判断不支持不支持支持

想要原子性用 Lua 脚本,只想提升批量操作性能用 Pipeline。

使用限制

官方建议单次 Pipeline 最多打包 10000 条命令。命令太多会有两个问题:

1)客户端发完请求要等所有结果返回才能处理,命令太多等待时间太长

2)Redis 要把所有响应存在内存里,命令太多内存占用会飙升

实际生产中一般控制在 1000 条以内,够用了

底层原理

正常情况下客户端发一条命令,Redis 收到后执行,结果通过 TCP 返回,客户端收到响应后才发下一条。每条命令都要经历一次完整的请求-响应周期。

Pipeline 打破了这个模式。客户端不等响应,连续把命令写到发送缓冲区,一股脑推给 Redis。Redis 那边按顺序执行,把结果存到响应缓冲区,最后一起返回。

从系统调用角度看,100 条命令正常执行要 100 次 write 和 100 次 read,用 Pipeline 可能就 1 次 write 和 1 次 read。用户态和内核态的切换次数大幅减少,这也是性能提升的重要原因。

适用场景

1)批量数据导入:一次性写入几万条数据

2)批量查询:一次性查多个 key 的值

3)数据迁移:从一个 Redis 实例搬数据到另一个

4)统计任务:批量执行 INCR、HINCRBY 这类计数操作

不适合的场景:后面的命令依赖前面命令的结果,这种情况 Pipeline 搞不定,得用 Lua 脚本。

Pipeline和MGET/MSET这类批量命令有何不同

MSET、MGET 是 Redis 内置的原子性批处理命令,一条命令搞定多个 key 的读写;Pipeline 是客户端层面的批量发送机制,把一堆命令打包发出去,让 Redis 挨个执行再一次性返回结果。

两者最大的区别在于原子性和适用范围:

1)MSET/MGET 在 Redis 服务端是单条命令执行,天然原子,中间不会插入其他命令。但只能干"批量读"或"批量写"这一件事,想混着用 SET、INCR、LPUSH 就没戏了。

2)Pipeline 就灵活多了,什么命令都能往里塞,SET、GET、HSET、ZADD 随便组合。但它本质上是多条独立命令,Redis 只是帮你省了网络往返,执行过程中完全可能被其他客户端的命令插队。

3)网络开销上两者都是一次往返,但 MSET/MGET 在服务端只有一次命令解析开销,Pipeline 有 N 次。数据量不大的时候差别可以忽略,几万条命令往上 Pipeline 的解析成本就能感知到了。

各自的缺点/限制

Pipeline:

  • 首先就是命令数量如果太多,就会导致内存中临时存储了大量响应,可能会OOM
  • 即便中间命令出错也不会停止,只能从返回值中挑出哪些成功哪些失败
  • 在Redis Cluster模式下,Pipeline里的命令如歌涉及不同slot客户端得自己拆分对应节点

MSET/MGET

  • 首先就是他的批量操作也不宜太多
  • MSET 和 MGET 在 Cluster 模式下也有 slot 限制,所有 key 必须落在同一个 slot 才行。实际用法上,一般会用 hash tag 来强制路由

Q&A

Pipeline 和 Redis 事务有什么区别?什么时候该用事务?

Pipeline 只是把命令打包发送,执行过程中完全可能被其他客户端的命令插队;事务用 MULTI/EXEC 包裹,保证这批命令连续执行不会被打断。但 Redis 事务不支持回滚,某条命令出错了前面的操作不会撤销。需要"执行过程不被插队"的场景用事务,纯粹想提升吞吐量就用 Pipeline。两者也能组合用,Pipeline 里套 MULTI/EXEC。

Pipeline 打包太多命令会有什么风险?

内存撑爆和网络超时。客户端要把所有命令攒在内存里才能发送,Redis 也要把所有响应攒着才能返回,100 万条命令两边内存都扛不住。另外 TCP 有发送缓冲区大小限制,数据量太大可能触发分包,网络抖动时容易超时。一般控制在每批 1000 到 10000 条比较稳妥。

Cluster 模式下怎么优雅地用 Pipeline?

核心是按 slot 分组。先计算每个 key 的 slot,把同一个 slot 的命令归到一组,每组单独走 Pipeline 发到对应节点。Lettuce 客户端内置了这个逻辑,Jedis 需要自己手动分组或者用 JedisCluster 的批量 API。如果 key 本身有规律,也可以用 hash tag 强制路由到同一个 slot。

Pipeline 会阻塞 Redis 吗?

不会比普通命令更阻塞。Pipeline 只是把命令打包发送,Redis 还是一条一条按顺序执行的。该执行多久还是多久,只是网络往返省了。但如果 Pipeline 里塞了特别多命令,Redis 执行完这批命令的时间会比较长,这段时间其他客户端的请求确实要排队等着。所以还是建议控制单次 Pipeline 的命令数量。

Redis Cluster 模式下 Pipeline 还能用吗?有什么限制?

能用但有限制。Cluster 模式下数据按 slot 分布在不同节点,Pipeline 打包的命令如果涉及多个 slot,客户端需要按 slot 分组,分别发送到对应节点。Jedis 的 JedisCluster 不直接支持 Pipeline,得用 JedisClusterPipeline 或者 Lettuce 这类支持 Cluster Pipeline 的客户端。另外如果命令涉及的 key 在不同节点,原子性就更没保障了。

post.comments