原文http://redis.io/topics/pipelining
请求/回复协议和RTT(往返时延)
Redis是一个基于客户端-服务端模型的TCP服务,叫做请求/回复协议。
这个意思是一个请求通常包括了以下的步骤:
1.客户端发送给服务端一个请求,从socket中读取,以阻塞的方式等待服务端的回复。
2.服务端处理请求,然后发送回复给客户端。
举个例子,如下是一个四个命令组成的串:
- 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时间有250毫秒(就这个来说是个很慢的链接),就算这个服务端能够一秒处理100K个请求,我们能够一秒处理4个请求。
在使用回环接口的接口中RTT很短(举个例子我的主机报告用了0.04秒用来ping127.0.0.1)。但这依然是很大的如果你请求量很大的话。
幸运的是有一个方法用来改善这个情况:
Redis Pipelining
一个请求/回复服务端可以实施这个方法来处理新的请求,即使客户端没有准备好读取旧的回复。这种方法可以使得客户端在不用等待回复的情况下传送很多请求到服务端,最后在一个步骤中读取所有的回复。
这个叫做pipelining(管道),这种技术在几十年来被广泛的使用。举个例子很多pop3协议已经支持了这种特性,极大的提高了从服务端下载新邮件的速度。
Reids在很早之前支持这种特性了,所以无论你在使用哪个版本,你都可以使用pipelining。下面是使用原生的NETCAT工具的例子:
$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
现在我们不用花费时间在每次一PING的RTT上了,三个命令只要一个RTT时间。
更精确的说,使用了pipelining之后我们的第一个例子会变成如下:
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Server: 1
- Server: 2
- Server: 3
- Server: 4
一些标准
在下面的标准例子中我们使用支持pipelining的Ruby的Redis客户端来测试由于pipelining带来的速度提升:
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
}
在我的OS系统中运行以上这个简单脚本后会产生如下的数据,运行在回环接口上,pipelining在RTT上的提升会变得最小:
without pipelining 1.185238 seconds
with pipelining 0.250783 seconds
正如你所见的使用了pipelining之后,我们使得RTT时间变成了五分之一。
pipelining VS scripting
使用redis脚本可以使得一些使用Pipelining的情况变得更加效率。使用redis脚本使得服务端做更多的工作。使用redis脚本一个很大的好处是使得读写数据的时差很小(几乎同时读/写的意思),使得像读、计算、写这些操作变得非常快。(pipelining在这种情况下没用,因为客户端需要在调用写命令之前需要读命令的回复。)
有时候应用可能通过Pipelining的方式发送EVAL,EVALSHA。这是完全可行的,redis的SCRIPT LOAD命令非常精确的支持这个(这个保证了EVALSHA可以在没有失败的风险下调用)。