编者注: pipeline 是 Redis 的一个提高吞吐量的机制, 适用于多 key 读写场景, 比如同时读取多个 key 的 value, 或者更新多个 key 的 value. 工作过程中发现挺多小伙伴都对 pipeline 多少有些了解, 但是更深入的理解或者说有哪些坑就不知道了, 下面咱们就一起分析下 Redis pipeline 机制, 揭开它的神秘面纱.
Redis 本身是基于 Request/Response 协议 (停等机制) 的, 正常情况下, 客户端发送一个命令, 等待 Redis 返回结果, Redis 接收到命令, 处理后响应. 在这种情况下, 如果同时需要执行大量的命令, 那就是等待上一条命令应答后再执行, 这中间不仅仅多了 RTT(Round Time Trip), 而且还频繁调用系统 IO, 发送网络请求. 为了提升效率, 这时候 pipeline 出现了, 它允许客户端可以一次发送多条命令, 而不等待上一条命令执行的结果, 这和网络的 Nagel 算法有点像(TCP_NODELAY 选项).pipeline 不仅减少了 RTT, 同时也减少了 IO 调用次数(IO 调用涉及到用户态到内核态之间的切换).
要支持 Pipeline, 其实既要服务端的支持, 也要客户端支持. 对于服务端来说, 所需要的是能够处理一个客户端通过同一个 TCP 连接发来的多个命令, 可以理解为, 这里将多个命令切分, 和处理单个命令一样(之前老生常谈的黏包现象),Redis 就是这样处理的. 而客户端, 则是要将多个命令缓存起来, 缓冲区满了或者达到发送条件就发送出去, 最后才处理 Redis 的应答.
注意: Redis 的 Pipeline 和 Transaction(Redis 事务)不同, Transaction 会存储客户端的命令, 最后一次性执行, 而 Pipeline 则是处理一条 (批次), 响应一条, 从二者的不同处理机制来看, Redis 事务中命令的执行是原子的(注意, 其中一部分命令出现错误后续命令会继续执行, 这里的原子说的是命令执行是完整的, 中间不会被其他 Redis 命令所打断), 而 pipeline 中命令的执行不一定是原子的. 但是这里却有一点不同, 就是 pipeline 机制中, 客户端并不会调用 read 去读取 socket 里面的缓冲数据(除非已经发完 pipeline 中所有命令), 这也就造成了, 如果 Redis 应答的数据填满了该接收缓冲(SO_RECVBUF), 那么客户端会通过 ACK,WIN=0(接收窗口) 来控制服务端不能再发送数据, 那样子, 数据就会缓冲在 Redis 的客户端应答缓冲区里面. 所以需要注意控制 Pipeline 的大小. 如下图:
这里可以设想一下, 如果客户端通过 ACK,WIN=0(接收窗口)来控制服务端不能再发送数据, 那么数据就会堆积在服务端 socket 发送缓冲区中, 如果服务端 socket 发送缓冲区也满了, 那么此时服务端调用 write(socket)就会阻塞或者失败.
既然提到了 tcp/ip 的滑动窗口概念, 这里就简单总结下滑动窗口:
滑动窗口在 TCP 中的作用是提供 TCP 的可靠性和流控特性, 滑动窗口可分为发送窗口和接收窗口, 它们分别对应于发送缓冲区和接收缓冲区. 发送窗口的大小是根据客户端接收缓冲区的大小而设定的(三次握手的目的是连接服务器指定端口, 建立 TCP 连接, 并同步连接双方的序列号和确认号, 交换 TCP 窗口大小信息).
发送窗口中包含的内容是已发送但还未收到 Ack 的数据和未发送但对端允许发送的数据.
TCP 接收缓冲区中包含应用为读取数据, 已接收数据(已回复 ACK), 待接收, 其中待接收空间可称为接收窗口.
使用 pipeline 过程中, 要注意控制一次 pipeline 中的命令总大小, 不能使响应结果撑爆 socket 接收缓冲区. 这里我们思考一个问题, 还有没有其他方式提高 pipeline 的处理性能呢? 理论上是有的, 比如可以使用数据压缩机制, 进一步减小数据传输的总大小, 不过这需要客户端和服务端提供解压缩机制, 同时会耗费一定量服务器 CPU.
来源: https://www.cnblogs.com/luoxn28/p/11794540.html