使用Redis pipelining
可以加速Redis
查询
1. 请求/响应协议和RTT
Redis
是一个使用client-server
模型和请求/响应协议的TCP
服务器。
过程如下:
client
发送一个请求给server
,通常以阻塞的方式从套接字中读取server
的响应。server
处理命令并将响应发送回client
。
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
client
和 server
通过网络连接。 这样的连接可能非常快(环回接口),也可能非常慢(通过 Internet
建立的连接在两台主机之间有很多跃点)。 无论网络等待时间是多少,数据包都会有一段时间从 client
传到 server
,然后再从 server
传回 client
。
这段时间称为RTT(往返时间)
。 当 client
需要连续执行多个请求时(例如,将多个元素添加到同一列表中,或使用许多键填充数据库),很容易看到这会对性能产生怎样的影响。 例如,如果RTT
时间为250毫秒(在 Internet
上的链接非常慢的情况下),即使 server
有每秒处理10万个请求的能力,我们每秒也最多只可以处理4个请求。
如果使用的接口是环回接口(loopback interface)
,则 RTT
会短得多(例如,我的主机显示 ping
到127.0.0.1时为0.044毫秒),但是如果连续执行多次写操作,RTT
则会大得多。
幸运的是,有一种方法可以改善此用例。
3. Redis Pipelining
可以实现请求/响应
服务器,以便 即是 client
尚未读取旧响应,它也可以处理新请求。 这样,可以将多个命令发送到服务器,而根本不用等待答复,最后来读取答复。
这就是pipelining
,在近数十年来已经广泛使用的技术。 例如,许多POP3
协议已支持此功能,从而极大地加快了从服务器下载新电子邮件的过程。
Redis
从很早就开始支持管道传输,因此无论您运行的是哪个版本,都可以在Redis
中使用管道传输。
按如下过程,只需一次RTT
即可:
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Client: INCR X
- Server: 1
- Server: 2
- Server: 3
- Server: 4
需要注意的是
,当 client
用 pipelining
发送命令时,server必须将回复也放入内存队列中。因此,如果使用 pipelining
, 最好一次性批量发送大量(例如 10K)的命令。所以管道中命令的数量应该有一个合理的值,不能无限多
。
3. 不只是RTT问题
pipelining
不仅减少 RTT
的延迟成本,它实际上还可以极大地提高给定 Redis
服务器每秒可执行的总操作数。 在不使用 pipelining
时,执行命令得到回复这个过程很快,但是整个Redis的服务效率被 socket I/O
限制了。
使用 pipelining
时,通常使用1次 read()
系统调用读取许多命令,再使用1次 write()
系统调用传递多个答复。 因此,每秒执行的总查询数最初随着 pipelines
地增长而几乎呈线性增加,最终趋于某一个值。
4. Pipelining VS Scripting
使用 Redis LUA
脚本(在Redis 2.6
或更高版本中可用),使用Redis LUA脚本
来完成某些 pipelining
的工作更加高效。 LUA脚本
的一大优势在于,它能够以最小的延迟读取和写入数据,从而使读取,计算,写入等操作非常快(在某些情况下,pipelining
无济于事,例如 client
需要在执行写命令之前得到服务器的回复)。
有时,应用程序可能还希望在管道中发送EVAL
或EVALSHA
命令。Redis
通过SCRIPT LOAD
命令实现此功能(它可以保证调用EVALSHA
不会失败)。
5. 扩展
前面我们提到,为了解决网络开销带来的延迟问题,可以把客户端和服务器放到一台物理机上。但是有时用benchmark进行压测的时候发现这仍然很慢。
这时客户端和服务端实际是在一台物理机上的,所有的操作都在内存中进行,没有网络延迟,按理来说这样的操作应该是非常快的。为什么会出现上面的情况的呢?
实际上,这是由内核调度导致的。比如说,benchmark运行时,读取了服务器返回的结果,然后写了一个新的命令。这个命令就在回环接口的send buffer中了,如果要执行这个命令,内核需要唤醒Redis服务器进程。所以在某些情况下,本地接口也会出现类似于网络延迟的延迟。其实是内核特别繁忙,一直没有调度到Redis服务器进程。