【翻译】Using pipelining to speedup Redis queries

原文地址:https://redis.io/topics/pipelining

Request/Response protocols and RTT

请求/响应协议和RTT

Redis is a TCP server using the client-server model and what is called a Request/Response protocol.

redis 是一个客户端/服务端模式的请求/响应协议的TCP服务器

This means that usually a request is accomplished with the following steps:

那么一次请求会分以下几步骤来完成:

  • The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.

客户端向服务器发送一个查询,并从套接字读取服务器响应(通常以阻塞的方式)。

  • The server processes the command and sends the response back to the client.

服务器处理请求并返回结果给客户端。

So for instance a four commands sequence is something like this:

一个请求如下面所示:

  • Client: INCR X
  • Server: 1
  • Client: INCR X
  • Server: 2
  • Client: INCR X
  • Server: 3
  • Client: INCR X
  • Server: 4

Clients and Servers are connected via a networking link. Such a link can be very fast (a loopback interface) or very slow (a connection established over the Internet with many hops between the two hosts). Whatever the network latency is, there is a time for the packets to travel from the client to the server, and back from the server to the client to carry the reply.

客户端和服务端通过网络连接,这样的连接可能非常快(一个环形接口),也可能非常慢(在Internet上建立的连接,在两台主机之间有许多跳点)。无论是什么延迟,从这个客户端发送请求给服务端,以及等待服务端返回数据给客户端都是需要一定时间的。

This time is called RTT (Round Trip Time). It is very easy to see how this can affect the performances when a client needs to perform many requests in a row (for instance adding many elements to the same list, or populating a database with many keys). For instance if the RTT time is 250 milliseconds (in the case of a very slow link over the Internet), even if the server is able to process 100k requests per second, we'll be able to process at max four requests per second.

这种模式称之为RTT(往返时间),如果一个客户端加入了大量的请求(对一个list加入大量的element或者使用了很多key去操作redis),这个是很影响性能的,举个例子,如果RTT需要250毫秒(在网络非常缓慢的情况下),即使这个服务器可以每秒处理100k的请求,name我们服务器实际上能处理最大的请求数量也是每秒钟4个请求。

If the interface used is a loopback interface, the RTT is much shorter (for instance my host reports 0,044 milliseconds pinging 127.0.0.1), but it is still a lot if you need to perform many writes in a row.

Fortunately there is a way to improve this use case.

如果使用的接口是环回接口,RTT就会短得多(例如,我的主机报告的ping值为127.0.0.1,为0.044毫秒),但是如果您需要在一行中执行许多写操作,那么还是需要不少的时间的。

幸运的是,有一种方法可以改进这个用例。

Redis Pipelining

redis管道操作

A Request/Response server can be implemented so that it is able to process new requests even if the client didn't already read the old responses. This way it is possible to send multiple commands to the server without waiting for the replies at all, and finally read the replies in a single step.

请求/响应服务器可以在客户端还没有读这个响应的时候去处理新的请求,所以我们可以发送多个命令给服务端而无需等待服务端的响应,最后我们才得到服务端响应

This is called pipelining, and is a technique widely in use since many decades. For instance many POP3 protocol implementations already supported this feature, dramatically speeding up the process of downloading new emails from the server.

这种操作叫做管道,这是一项几十年来被广泛使用的技术,例如许多POP3协议已经支持该功能了,极大的加速了从服务器下载邮件的功能。

Redis has supported pipelining since the very early days, so whatever version you are running, you can use pipelining with Redis. This is an example using the raw netcat utility:

redis很早就已经支持管道了,所以无论你使用哪一个版本的redis,都可以使用redis,下面是一个操作实例:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379 +PONG +PONG +PONG

This time we are not paying the cost of RTT for every call, but just one time for the three commands.

这次我们没有在每一个请求上花费RTT,三个请求只使用了一次RTT

To be very explicit, with pipelining the order of operations of our very first example will be the following:

为了便于理解,我们在第一次的实例中使用管道后的顺序如下所示:

  • Client: INCR X
  • Client: INCR X
  • Client: INCR X
  • Client: INCR X
  • Server: 1
  • Server: 2
  • Server: 3
  • Server: 4

IMPORTANT NOTE: While the client sends commands using pipelining, the server will be forced to queue the replies, using memory. So if you need to send a lot of commands with pipelining, it is better to send them as batches having a reasonable number, for instance 10k commands, read the replies, and then send another 10k commands again, and so forth. The speed will be nearly the same, but the additional memory used will be at max the amount needed to queue the replies for these 10k commands.

重要提示:当客户端通过管道发送请求时,服务器会强制使用内存对响应进行队列,所以如果你需要通过管道发送大量的命令,最好是使用合理的数量,比如10k命令,读取响应,然后再发送10k命令,以此类推,这个速度几乎相同,所使用的额外内存最大将达到对这些10k命令的响应进行排队所需的容量。(意味着一次过量的请求所使用的内存占用会远远大于分小批次去请求占用的内存,所以建议分小批次去取数据)

It's not just a matter of RTT

不仅仅是提高了RTT

Pipelining is not just a way in order to reduce the latency cost due to the round trip time, it actually improves by a huge amount the total operations you can perform per second in a given Redis server. This is the result of the fact that, without using pipelining, serving each command is very cheap from the point of view of accessing the data structures and producing the reply, but it is very costly from the point of view of doing the socket I/O. This involves calling the read() and write() syscall, that means going from user land to kernel land. The context switch is a huge speed penalty.

管道操作不仅仅是为了减少RTT时间,实际上他也提高了在redis服务器上每秒所能执行的最大操作数量,这是因为以下事实:不使用管道,从访问数据结构和生成答复的角度来看,服务每个命令非常简单,但是从执行套接字I / O的角度来看,这非常昂贵。 这涉及到调用read()和write()系统调用,这意味着从用户域到内核域。 上下文切换是巨大的速度损失。

When pipelining is used, many commands are usually read with a single read() system call, and multiple replies are delivered with a single write() system call. Because of this, the number of total queries performed per second initially increases almost linearly with longer pipelines, and eventually reaches 10 times the baseline obtained not using pipelining, as you can see from the following graph:

当使用管道时,通常通过一个read()系统调用来读取许多命令,通过一个write()系统调用来传递多个应答。因此,一开始,随着管道长度的增加,每秒执行的查询总数几乎是线性增长的,最终达到不使用管道获得基线的10倍,如下图所示

Some real world code example

一些代码展示

In the following benchmark we'll use the Redis Ruby client, supporting pipelining, to test the speed improvement due to pipelining:

下面的例子我们使用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 }

Running the above simple script will provide the following figures in my Mac OS X system, running over the loopback interface, where pipelining will provide the smallest improvement as the RTT is already pretty low:

运行上面的简单脚本将在我的Mac OS X系统中提供以下图形,运行在环回接口上,其中管道将提供最小的改进,因为RTT已经很低了

without pipelining 1.185238 seconds with pipelining 0.250783 seconds

As you can see, using pipelining, we improved the transfer by a factor of five.

从上面可以看来,使用了管道后,我们提升了五倍的速度

Pipelining VS Scripting

Using Redis scripting (available in Redis version 2.6 or greater) a number of use cases for pipelining can be addressed more efficiently using scripts that perform a lot of the work needed at the server side. A big advantage of scripting is that it is able to both read and write data with minimal latency, making operations like read, compute, write very fast (pipelining can't help in this scenario since the client needs the reply of the read command before it can call the write command).

使用redis脚本(2.6版本后)可以更有效地解决管道的许多用例,这些用例可以执行很多服务器端需要的工作。脚本的一大优点是,它能够以最小的延迟读写数据,使读、计算、写等操作非常快(在这种情况下,管道没有用处,因为客户端在调用写命令之前需要对读命令进行应答)。

Sometimes the application may also want to send EVAL or EVALSHA commands in a pipeline. This is entirely possible and Redis explicitly supports it with the SCRIPT LOAD command (it guarantees that EVALSHA can be called without the risk of failing).

有时,应用程序可能还希望在管道中发送EVAL或EVALSHA命令。这是完全可能的,Redis明确支持它与脚本加载命令(它保证EVALSHA可以被调用没有失败的风险)。

Appendix: Why are busy loops slow even on the loopback interface?

附录:为什么在大量请求的时候,环形接口也会很慢?

Even with all the background covered in this page, you may still wonder why a Redis benchmark like the following (in pseudo code), is slow even when executed in the loopback interface, when the server and the client are running in the same physical machine:

即使这里说了一堆废话,你可能仍然想知道为什么当服务器和客户端运行在同一物理机器的环回接口执行一个Redis基准(伪代码)的时候也很慢

FOR-ONE-SECOND: Redis.SET("foo","bar") END

After all if both the Redis process and the benchmark are running in the same box, isn't this just messages copied via memory from one place to another without any actual latency and actual networking involved?

The reason is that processes in a system are not always running, actually it is the kernel scheduler that let the process run, so what happens is that, for instance, the benchmark is allowed to run, reads the reply from the Redis server (related to the last command executed), and writes a new command. The command is now in the loopback interface buffer, but in order to be read by the server, the kernel should schedule the server process (currently blocked in a system call) to run, and so forth. So in practical terms the loopback interface still involves network-alike latency, because of how the kernel scheduler works.

Basically a busy loop benchmark is the silliest thing that can be done when metering performances in a networked server. The wise thing is just avoiding benchmarking in this way.

毕竟,如果Redis进程和基准测试都在同一个盒子里运行,这难道不只是消息从一个地方复制到另一个地方,没有任何实际的延迟和实际的网络涉及?

原因是系统中的进程并不总是运行,实际上是让该进程运行的内核调度程序,因此发生的事情是,例如,允许基准测试运行,从Redis服务器读取回复(相关 到最后执行的命令),并写入新命令。 该命令现在位于回送接口缓冲区中,但是为了由服务器读取,内核应安排服务器进程(当前在系统调用中被阻止)运行,依此类推。 因此,实际上,由于内核调度程序的工作原理,环回接口仍然涉及类似于网络的延迟。

基本上,繁忙的基准测试是衡量网络服务器中的性能时可以完成的最简单的事情。 明智的做法是避免以这种方式进行基准测试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值