Redis进阶实践之十八 使用管道模式提升Redis查询的速度html
1、引言
学习redis 也有一段时间了,该接触的也差很少了。后来有一天,之前的同事问我,如何向redis中批量的增长数据,确定是大批量的,为了这主题,我又从新找起了解决方案。目前的解决方案大都是从官网上查找和翻译的,每一个实例也都调试了,正确无误。把结果告诉我同事的时候,我也更清楚这个主题如何操做了,里面的细节也更清楚了。固然也有人说能够经过脚原本作这个操做,没错,可是我对脚本语言尚未研究很透,就不来班门弄斧了。
2、管道的由来
提及这个主题也是我同事帮的忙,关于批量增长增长数据到Redis服务器中,我已经写了一篇文章了,那篇文章只是介绍的操做,咱们学技术,就要作到知其然知其因此然,因此就有了这篇文章。若是想查看个人上一篇文章,能够点击这里《Redis进阶实践之十六 Redis大批量增长数据》
一、请求/响应协议和RTT
Redis是使用 客户端-服务器(Client-Server) 模型的TCP服务器,称为请求/响应模式。
这意味着经过如下步骤才能完成请求:
1.一、客户端向服务器发送查询,并一般以阻塞的方式从套接字读取服务器响应。
1.二、服务器处理命令并将响应发送回客户端。
例如,这四个命令序列就是这样的:python
Client: INCR X Server: 1 Client: INCR X Server: 2 Client: INCR X Server: 3 Client: INCR X Server: 4
客户端和服务器经过网络链路进行链接。这样的连接能够很是快(一个回送接口)或很是慢(经过互联网在两台主机之间创建不少跳转的链接)。不管网络延迟如何,数据包都会从客户端传输到服务器,而后从服务器传回客户端以进行回复。
这个时间来回被称为RTT(往返时间)。当客户端须要连续执行多个请求时(例如,将许多元素添加到同一个列表或使用多个键填充数据库),很容易看到这会很影响性能。例如,若是RTT时间为250毫秒(在因特网上链接速度很是慢的状况下),即便服务器可以每秒处理100k个请求,此时咱们也只可以每秒最多处理四个请求。
若是使用的接口是本地回送接口(loopback),则RTT要短得多(例如,个人主机报告0.0,040毫秒ping 127.0.0.1),但若是您须要连续执行不少写操做,则仍然须要不少的时间。
幸运的是,有一种方法能够改善这种作法。
二、Redis的管道
请求/响应服务器能够这样实现,即便客户端没有阅读上一条命令的回复,它也可以处理新的请求。经过这种方式,能够发送多个命令到服务器而无需等待回复,最后一步读取回复。
这被称为管道技术,而且是被普遍使用的技术。例如,许多POP3协议的实现已经支持这个功能,显著加快了从服务器下载新电子邮件的过程。
Redis自从早期的版本开始就支持管道的操做,所以不管您运行哪一种版本,均可以使用Redis进行管道的操做。这是使用原始netcat实用程序的示例:linux
[root@linux ~]# (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc 192.168.127.130 6379 +PONG +PONG +PONG
(若是执行nc命令,提示:command not found,安装命令便可,即:yum install nc)
此次咱们没有为每次通话支付RTT的时间成本,只是把三命令做为了一个命令执行,最后只为这一次执行花费了时间。
很是明确地说,经过管道的操做,咱们第一个例子的操做顺序以下:
redis
Client: INCR X Client: INCR X Client: INCR X Client: INCR X Server: 1 Server: 2 Server: 3 Server: 4
重要提示:当客户端使用管道发送多条命令时,服务器将被迫使用内存排队答复。因此若是你须要使用管道发送大量的命令,最好将这些命令以合理的数目进行分组来批量发送,例如10k命令,读取回复,而后再发送另外一个10k的命令,相似这样。速度几乎相同,所使用的额外内存的最大量将是将最大限度地排队此10k命令的回复所需的数量。
三、这不只仅是RTT的问题
管道不只仅是为了减小往返时间所带来的延迟成本,它实际上能够提升您在给定的Redis服务器上每秒执行的总操做量。这是事实,即在不使用管道的状况下,从访问数据结构和生成答复的角度来看,每一个命令的执行成本都不高的,但从执行套接字 I/O 操做的角度来看,这是很是昂贵的。当涉及调用read()和write()调用的时候,这个调用操做意味着要切换操做环境,要从用户登录切换到内核登录。最后来看,其实上下文切换才是致使速度大幅度的下降的罪魁祸首。
当使用Redis的管道的时候,许多命令一般经过对一个read()函数的系统的调用来读取,而且经过对一个write()函数的系统的调用来传递多个响应。所以,每秒执行的总查询数量随着管道的操做呈线性增长,并最终达到未使用管道的基线的10倍,以下图所示:
四、一些真实世界的代码示例
在如下基准测试中,咱们将使用支持管道的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
正如您所看到的,使用管道,咱们将传输速度改提高五倍。
五、管道VS脚本
使用Redis脚本(Redis版本2.6或更高版本中可用),能够在服务器端更高效执行处理大量的管道用例的工做。 脚本的一大优势是它可以以最小的延迟读取和写入数据,使得读取,计算,写入等操做很是快速(在这种状况下,管道操做不起做用,由于客户端在调用写入命令以前须要读取命令的回复)。
有时,应用程序可能还想在管道中发送EVAL或EVALSHA命令。这是彻底可能的,而且Redis经过SCRIPT LOAD命令明确是支持的(它保证能够在没有失败风险的状况下调用EVALSHA)。
六、 EVALSHA sha1 numkeys key [key ...] arg [arg ...]
Redis可使用该命令的版本是2.6.0,或者更高的版本。
时间复杂度:取决于执行的脚本。
经过其SHA1摘要评估缓存在服务器端的脚本。使用SCRIPT LOAD命令将脚本缓存在服务器端。该命令在其余方面与EVAL相同。
七、附录:为何即便在回送接口上,一个繁忙的循环也很慢?
即便在本页面介绍的全部背景下,您仍然可能想知道为何如在下所示的Redis基准测试中(在伪代码中),即便在回送接口中执行,而且服务器和客户端在同一物理机器上运行时,也很慢:ruby
FOR-ONE-SECOND: Redis.SET("foo","bar") END
毕竟,若是Redis进程和基准测试都在同一个框中运行,那么这不只仅是经过内存将消息从一个地方复制到另外一个地方,而没有任何实际的延迟和实际网络?
缘由是系统中的进程并不老是在运行,其实是内核调度器让进程运行的,因此会发生以下的状况,例如,当基准测试程序被容许运行,从Redis服务器读取回复(与最后执行的命令相关),并写入新的命令。该命令如今位于回送接口缓冲区中,但为了被服务器读取,内核调度器应该安排服务器进程(当前在系统调用中阻塞)运行,等等。 所以,实际上,因为内核调度程序的工做原理,回送接口仍然会有网络延迟的。
基本上,使用一个繁忙的循环来执行基准测试是一件愚蠢的事情,能够在网络服务器中测量性能时完成相关测试。明智的作法是避免以这种方式作基准测试。
3、结束
大批量插入数据的文章就写到这里了,这篇文章也介绍了 管道的一些底层的机制,对你们,对咱们之后使用Redis 会有好处。等之后我对脚本语言,ruby,或者python学有所成的时候,在经过这些工具来作一些脚本执行批量插入Redis 的实力吧,也会把相应的感觉和心得写出来。继续努力吧。对了,若是你们想观看英文,能够《点击这里》。
服务器
天下国家,可均也;爵禄,可辞也;白刃,可蹈也;中庸不可能也网络