Redis的消息交互
当我们使用客户端对Redis进行一次操作时,如下图所示,客户端将请求传送给服务器,服务器处理完毕后,再将响应回复给客户端,这要花费一个网络数据包来回的时间。
如果连续执行多条指令,那就会花费多个网络数据包来回的时间,如下所示
回到客户端,客户端是经历了写-读-写-读四个操作,两次完整的网络数据包来回,这也是真正的耗时所在
,如果我们调整读写顺序,改成写-写-读-读,这两个指令同样可以完成,但是网络数据包来回就**优化成了仅有一次。这便是管道操作的本质(通过调整客户端读写顺序、优化网络数据包来回次数)**
。客户端通过对管道中的指令列表改变读写顺序就可以大幅节省IO时间,管道中指令越多,效果越好。
管道的本质
上图就是一个完整的请求交互流程,具体步骤为
- 客户端进程调用write将消息写到操作系统内核为套接字分配的发送缓冲 send buffer。
- 客户端操作系统内核将发送缓冲的内容发送到网卡,网卡硬件将数据通过网际路由送到服务器的网卡。
- 服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲 recv buffer。
- 服务器进程调用read从recv buffer取出消息进行处理
- 服务器进程调用write将响应消息写到内核为套接字分配的发送缓冲send buffer。
- 服务器操作系统将发送缓冲send buffer中的内容发送到服务器网卡,网卡硬件将数据通过网际路由送到客户端的网卡。
- 客户端操作系统将网卡的数据放到内核为套接字分配的recv buffer。
- 客户端进程从recv buffer将数据取出并返回给上层业务逻辑。
实际上,客户端write请求,只负责将数据写到本地操作系统内核的发送缓冲就返回了
。剩下的事全部交给操作系统内核异步将数据送到目标机器,但是如果发送缓冲满了,那么就需要等待缓冲空出空闲时间,这个就是写操作IO的真正耗时。
此外,客户端read操作只负责将数据从本地recv buffer中取出来就完事了,但是如果接收缓冲是空的,那么就需要等待数据到来,这个就是读IO操作真正耗时。
所以,对于value = redis.get(key) 这样一个简单的请求,write操作几乎没有耗时,直接写到send buffer就返回,而read操作比较耗时,因为他需要等待消息经过网络路由转发到目标机器处理后的响应消息,在回送到当前的内核读缓冲才可以返回,这才是一个网络来回的真正开销。
而对于管道来说,连续的write操作根本没有耗时,之后第一个read操作会等待一个网络的来回开销,然后所有的响应消息就都已经回送到内核的缓冲区了。后续的read操作直接就可以从缓冲拿到结果,瞬间就返回了。
总结:
- 连续的write操作几乎没有耗时
- 真正耗时的就是第一次read操作需要一个完整的网络来回
- 在第一次read操作等待的时间内,其他的响应消息也都会送到客户端目标缓冲区。因此read操作几乎可以瞬间返回。
管道正是利用改变客户端读、写顺序,优化网络传输链路,节省IO开销,他并不是服务器端的特性。