大多数人一直对Redis管道有一个误解,他们以为这是Redis服务器提供的一种特别的技术,有了这种技术就可以加速Redis的存取效率。但是实际上Redis管道(Pipeline)本身并不是Redis服务器直接提供的技术,这个技术本质上是由客户端提供的,跟服务器没有什么直接的关系。
Redis的消息交互
当我们使用客户端对Redis进行一次操作时,如下图所示,客户端将请求传送给服务器,服务器处理完毕后,再将响应回复给客户端。这要花费一个网络数据包的来回时间。
如果连续执行多条指令,那就会花费多个网络数据包来回的时间。如下图所示。
回到客户端代码层面,客户端是经历了写-读-写-读四个操作才完整的执行了两条指令。
现在如果我们调整读写顺序,改成写-写-读-读,这两个指令同样可以正常完成。
两个连续的写操作和两个连续的读操作总共只会花费一次网络来回,就好比连续的write操作合并了,连续的read操作也合并了一样。
这便是管道操作的本质,服务器根本没有任何区别对待,还是收到一条消息,执行一条消息,回复一条消息的正常流程。客户端通过对管道中的指令列表改变读写顺序就可以大幅度节省IO时间,管道中指令越多,效果越好。
深入理解管道本质
接下来我们深入分析一个请求交互的流程,真实的情况是它很复杂,因为要经过网络协议栈,这个就得深入内核了。
上图就是一个完整的请求交互图。
- 客户端进程挑用write将消息写到操作系统内核为套接字分配的发送缓冲send buffer。
- 客户端操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过[网际路由]送到服务器的网卡。
- 服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recv buffer。
- 服务器进程调用read从接收缓冲中取出消息进行处理。
- 服务器进程调用write将响应消息写到内核为套接字分配的发送缓冲send buffer。
- 服务器操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过[网际路由]送到客户端网卡。
- 客户端操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recv buffer。
- 客户端进程调用read从接收缓冲中取出消息返回给上层业务逻辑进行处理。
- 结束。
其中步骤5-8和1-4是一样的,只不过方向是反过来的,一个是请求,一个是响应。
我们开始以为write操作是要等到对方收到消息才会返回,但实际上不是这样的。write操作只负责将数据写到本地操作系统内核的发送缓冲然后就返回了。剩下的事交给操作系统内核异步将数据送到目标机器。但是如果发送缓冲满了,那么就需要等待缓冲空出空间来,这个就是写IO操作的真正耗时。
我们开始以为read操作是从目标机器拉取数据,但实际上不是这样的。read操作只负责将数据从本地操作系统内核的接收缓冲中取出来。但是如果缓冲是空的,那么就需要等待数据到来,这个就是读IO操作的真正耗时。
所以对于value = redis.get(key)这样一个简单的请求来说,write操作几乎没有耗时,直接写到发送缓冲就返回,而read就比较耗时了,因为他要等待消息经过网际路由到目标机器处理后的响应消息,再回送到当前的内核读缓冲才可以返回。这是才是一个网络来回的真正开销。
而对于管道来说,连续的write操作根本没有耗时,之后第一个read操作会等待一个网络的来回开销,然后所有的响应消息就都已经回送到内核的读缓存了,后续的read操作直接就可以从缓冲拿到结果,瞬间就返回了。