简介
pipeline就是管道,管道本身是能够承载流式数据的一个长链路,可以进行事件的缓冲。
redis客户端与服务器端的交互
Redis本身是基于一个Request一个Response方式的同步请求,正常情况下,客户端发送一个命令,等待Redis服务器返回结果,Redis服务器接收到命令,处理后响应结果给客户端。
无论网络延如何延时,数据包总是能从客户端到达服务器,并从服务器返回数据回复客户端。 这个时间被称之为 RTT (Round Trip Time - 往返时间)
如果同时需要执行大量的命令,那么就要等待上一条命令应答后再执行,这中间不仅仅多了RTT(Round Time Trip),而且还频繁调用系统IO,发送网络请求,同时需要redis调用多次read() 和write()系统方法,系统方法会将数据从用户态转移到内核态,这样就会对进程上下文有比较大的影响了。
普通请求模型

由于通信会有网络延迟,假如 client 和 server 之间的包传输时间需要0.125秒。那么上面的三个命令6个报文至少需要0.75秒才能完成。
这样即使 redis 每秒能处理100个命令,而我们的 client 也只能一秒钟发出四个命令,这显然没有充分利用 redis 的处理能力
pipeline请求模型

管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,pipeline 通过减少客户端与 redis 的通信次数来实现降低往返延时时间,而且 Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。
(Pipeline 的默认的同步的个数为53个,也就是说 arges 中累加到53条数据时会把数据提交。)
Pipeline与原生批量命令对比
- 原生批量命令是原子性(例如:mset, mget),pipeline是非原子性
- 原生批量命令一次只能执行一种命令,pipeline支持批量执行不同命令
- 原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成
Pipeline与事务对比
- 事务具有隔离性,在执行过程中不会穿插执行其他客户端发送的命令
pipeline与lua脚本对比
- 脚本可以实现类似pipeline一次I/O处理多个命令的场景,同时可以解决命令之间的依赖问题
- pipeline由于是发送批量请求,但是一次响应结果,所以在拿到响应结果之前无法判断操作是否成功,即发送的批量请求之间不支持相互依赖
- lua脚本同样是客户端一次请求,服务端执行多个命令,由于脚本可以支持判断,所以可以支持命令之间的相互依赖和逻辑处理
使用Pipeline注意事项
-
使用pipeline组装的命令个数不能太多,不然数据量过大,会带来下面两个问题
- 客户端执行exec发送缓冲命令到服务端后,将阻塞进程,直到服务端处理完pipeline发送的所有命令并返回结果后才能继续响应其他请求,当数据量过大时,客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存
- 客户端发送命令之前,命令都将存储在客户端缓冲区
- pipeline事实上所能容忍的操作个数,和socket-output缓冲区大小/返回结果的数据尺寸都有很大的关系(默认每个套接字 缓冲区的大小为 8K);
- 同时也意味着每个redis-server同时所能支撑的pipeline链接的个数,也是有限的,这将受限于server的物理内存或网络接口的缓冲能力
-
关于pipeline的时序性问题
- pipeline缓冲的指令发送到服务器端时,本身是按照指令缓冲的顺序执行,因为缓冲指令是使用队列的方式实现的,但是缓冲指令中间可能会穿插其他客户端发送来的命令
-
关于pipeline的原子性问题
- pipeline缓冲的指令只是会依次执行,但是不能保证原子性,如果执行中间某一个指令发生异常,将会继续执行后续的指令
-
cluster并不支持pipeline操作
适用场景
- 不合适一次操作强依赖结果的值
管道(Pipelining) VS 脚本(Scripting)
php 使用redis pipeline代码示例
使用管道
// 实例化redis对象
$redis = new Redis();
// 连接redis
$redis->connect('127.0.0.1', 6379);
// 开启管道
$redis->pipeline();
// 将命令放入队列
for ($i=1;$i<=1000;$i++)
$redis->set('test_key_'.$i, $i);
}
// 执行命令
$response = $redis->exec();
// 关闭redis
$redis->close();
使用事务加管道,保证管道中命令的原子性
// 实例化redis对象
$redis = new Redis();
// 连接redis
$redis->connect('127.0.0.1', 6379);
// 开启事务与管道
$pipe=$redis->multi(Redis::PIPELINE);
// 将命令放入队列
for ($i=1;$i<=1000;$i++)
$pipe->set('test_key_'.$i, $i);
}
// 执行命令
$response = $pipe->exec();
// 关闭redis
$redis->close();