一、pipeline出现的背景
Redis是一种基于客户端-服务端(CS)模型以及请求/响应协议的TCP服务,这意味着通常情况下一个请求会遵循以下步骤:
- 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
- 服务端处理命令,并将结果返回给客户端。
这样一次命令消耗的时间就包括四个部分:请求从客户端到服务器的时间、命令排队的时间和命令真正执行时间、结果从服务器到客户端的时间,从第一个到第四个消耗的时间总和称为 Round Trip Time(简称RTT,往返时间)。当客户端与服务器存在网络延迟时,RTT就可能会很大,这样就会导致性能问题。
二、作用与原理
管道(Pipeline)就是为了改善这个情况的,利用管道技术,客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低RTT时间从而提升性能。另一个原因也很重要,就是减少IO系统调用。例如,一个read或write系统调用,需要从用户态,切换到内核态。
要支持 pipeline,既要服务端的支持,也要客户端支持。对于服务端来说,所需要的是能够处理一个客户端通过同一个 TCP 连接发来的多个命令。对于客户端,则是要将多个命令缓存起来,缓冲区满了就发送,然后再写缓冲区,最后才处理 Redis 的应答。
Redis的管道在客户端通常会设置一个命令缓冲区来存储即将被批量发送的命令,客户端首先将执行的命令写入到缓冲区中,最后再一次性发送 Redis。但是有一种情况就是,缓冲区的大小是有限制的,如果命令数据太大,可能会有多次发送的过程,但是仍不会处理 Redis 的应答。比如Jedis,限制为8192,超过了,则刷缓存,发送到Redis,但是不去处理Redis的应答。
如果客户端使用管道发送了多条命令,那么服务器就会将多条命令放入一个队列中,这一操作会消耗一定的内存。而且redis必须在处理完所有命令前先缓存起所有命令的处理结果,这也是耗内存的,所以管道中命令的数量并不是越大越好(太大容易撑爆内存),而是应该有一个合理的值。
三、举例说明
Redis客户端与Redis服务器之间使用TCP协议进行连接,一个客户端可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务器处理,redis处理完请求命令后会将结果通过响应报文返回给client,因此当执行多条命令的时候都需要等待上一条命令执行完毕才能执行。
其执行过程如下图所示:
由于通信会有网络延迟,假如client和server之间的包传输时间需要0.125秒,那么上面的三个命令6个报文至少需要0.75秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令。这显然没有充分利用 redis的处理能力。
而管道(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline通过减少客户端与redis的通信次数来实现降低RTT,而且Pipeline 实现的原理是队列,而队列是先进先出的,这样就保证数据的顺序性。其过程如下图所示:client可以将三个命令放到一个tcp报文一起发送,server则可以将三条命令的处理结果放到一个tcp报文返回。
三、原生批命令(mset, mget)与Pipeline对比
- 原生批量命令是原子性
,
Pipeline是非原子性的。 - 原生批量命令是一个命令对应多个key
,
Pipeline支持多个命令。 - 原生批量命令是
Redis
服务端支持实现的,而Pipeline需要服务端与客户端的共同实现。
四、Pipeline正确使用方式
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时机,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。而且Pipeline每次只能作用在一个Redis节点。
参考:
https://blog.csdn.net/qq_24313635/article/details/83054300
https://blog.csdn.net/w1lgy/article/details/84455579