翻译参考:Pipelining:Learn how to send multiple commands at once, saving on round trip time.
请求/相应协议和RTT
Redis是基于TCP的C/S模型服务器,TCP是请求/响应协议。这意味着一个请求的完成要有以下几步:
- 客户端向服务器发送查询请求,或客户端从socket读取服务器的响应(socket通常是阻塞的)。
- 服务器处理客户端的请求命令,将响应发给客户端。
例如有四个命令如下:
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
客户端和服务器通过网络连接。这个连接可能很快,例如一个回环,或者很慢,例如这个连接经过很多跳。不论怎么,网络延时都有,因为一个数据包从客户端发送到服务端,再从服务端发回给客户端需要时间。
这个时间叫做RTT(Round Trip Time)。当客户端需要连续执行多个命令(例如向同一个链表添加许多元素,或从一个数据库中取很多键值),很容易看出这将如何影响其性能。例如当RTT为250ms时,即使Redis服务器处理能力为100k/s,它也只能每秒处理四个请求。
如果使用的回环网络,RTT事件会小很多(我的电脑ping 127.0.0.1为0.044ms),但是如果你依次执行多个命令时,还是有很大的往返时间。
幸运的是,针对这种情况,可以有方法提升其性能。
Redis流程线作业
请求响应服务器可以这样来实现,服务器在客户端没有收到响应时可以处理新的请求。这样的话,客户端可以一次性向服务器发送多个请求而不必等待响应,最后只需一步读取所有响应。
这叫做流程线作业(pipelining),这项技术已经广泛使用了几十年。例如,许多POP3协议的实现已经支持这个特性,极大的加速了从服务器下载新邮件的速度。
Redis最开始就支持流水线作业,因此无论你使用那个版本,你都可以使用这个特性。这是一个使用netcat的例子:
$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
在上面的例子中,不必为每一个命令耗费RTT,而是一次性发送了三个命令。为了显示对比,上面的第一个例子使用流水线作业将是:
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
注意 :当客户端使用流水作业发送命令时,服务器将使用queue来存储响应,这将占用内存。因此如果你需要一次发送大量命令,最好把命令控制在合理数量,例如10k。发送10k,读取响应,再发送10k。速度几乎形同,但是额外使用最大内存为queue存储这10k命令的响应。
一个基准测试
在下面的基准测试中,我们使用Redis的Ruby客户端,它支持流水线作业。测试使用流水线作业技术带来速度上的性能提升:
require 'rubygems'
require 'redis'
def bench(descr)
start = Time.now
yield
puts "#{descr} #{Time.now-start} seconds"
end
def without_pipelining
r = Redis.new
10000.times {
r.ping
}
end
def with_pipelining
r = Redis.new
r.pipelined {
10000.times {
r.ping
}
}
end
bench("without pipelining") {
without_pipelining
}
bench("with pipelining") {
with_pipelining
}
在我的Mac OS X系统上运行上面的脚本,通过回环来访问服务器,这样的环境下流水线作业提升的性能最小,因为实际的RTT已经很小了:
without pipelining 1.185238 seconds
with pipelining 0.250783 seconds
可以看出,使用流水线作业技术,在传输性能上提升了5倍。
流水线作业vs脚本
Redis2.6及其以后的版本支持Redis scripting,在流水线作业的例子中使用Redis scripting,比在服务器端使用scripts处理工作更加高效。使用脚本的一大优势为在读数据、写数据时有比较小德延时(latency),使读、写、计算等操作非常快(在这种场景下流水线作业不能起作用,因为客户端要读取响应才能写新的命令)。
有时应用需要使用流水线技术来发送EVAL或EVALSHA命令。Redis显式支持,可是使用命令SCRIP LOAD(它确保了调用EVALSHA不会失败)。