使用流水线(pipelining)加速Redis查询

原文地址

请求/响应协议和RTT(往返时间(Round Trip Time))

Redis是一个TCP服务器使用客户端-服务器模型,被称为请求/响应协议。
这意味着请求通常通过以下步骤完成:

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

客户端和服务器通过网络连接。这样的链路可以非常快(环回接口),也可以非常慢(在Internet上建立的连接,在两台主机之间有许多跳点)。无论网络延迟是什么,数据包从客户机传输到服务器,然后从服务器返回到客户机以携带应答都需要一段时间。

这个时间称为RTT(往返时间)。很容易看出,当客户机需要在一行中执行许多请求时(例如向同一个列表添加许多元素,或使用许多键填充数据库),这会如何影响性能。例如,如果RTT时间是250毫秒(在Internet上链接非常慢的情况下),即使服务器每秒能够处理100k个请求,我们每秒最多也只能处理4个请求。

如果使用的接口是环回接口,RTT就会短得多(例如,我的主机报告的ping值为127.0.0.1,为0 044毫秒),但是如果您需要在一行中执行许多写操作,那么RTT仍然很短。
幸运的是,有一种方法可以改进这个用例。

redis 流水线

可以实现请求/响应服务器,这样即使客户端没有读取旧响应,它也能够处理新请求。通过这种方式,可以向服务器发送多个命令,而根本不需要等待响应,并最终在一个步骤中读取响应。

这被称为管道铺设,是一项几十年来广泛使用的技术。例如,许多POP3协议实现已经支持这个特性,大大加快了从服务器下载新邮件的过程。

Redis从早期就支持pipelining,所以无论你运行的是什么版本,你都可以在Redis中使用pipelining。这是一个使用原始netcat实用程序的例子:

重要提示:当客户端使用管道发送命令时,服务器将被迫使用内存对响应进行排队。因此,如果您需要使用管道发送大量命令,最好以数量合理的批次发送,例如10k命令,读取应答,然后再次发送另一个10k命令,以此类推。速度几乎相同,但所使用的额外内存最大将达到对这些10k命令的响应进行排队所需的容量。

这不仅仅是RTT的问题

Pipelining不仅仅是为了减少由于往返时间而造成的延迟成本,它实际上大大提高了你在给定Redis服务器上每秒可以执行的总操作量。这是因为,如果不使用管道,从访问数据结构和生成应答的角度来看,为每个命令提供服务是非常便宜的,但从执行套接字I/O的角度来看,则是非常昂贵的。这涉及到调用read()和write()系统调用,这意味着从用户地到内核地。上下文切换是一个巨大的速度损失

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

一些真实的代码示例

在下面的基准测试中,我们将使用支持pipelining的Redis Ruby客户端来测试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
}

运行上述简单的脚本将提供以下数字在我的Mac OS X系统,运行在环回接口,管道将提供最小的改善,因为RTT已经很低:

without pipelining 1.185238 seconds
with pipelining 0.250783 seconds

如您所见,使用流水线,我们将传输性能提高了五倍。

流水线和脚本

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

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

附录:为什么即使在环回接口上,忙碌的循环也会很慢?

即使有了这页的所有背景,你可能仍然想知道为什么一个Redis基准像下面(在伪代码),是很慢,即使在环回接口执行,当服务器和客户端运行在同一物理机器:

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

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

原因是系统并不总是运行过程,实际上它是内核调度器,让进程运行,那么会发生什么是,例如,允许基准运行,从复述,服务器读取应答(最后一个命令执行相关),和写一个新的命令。该命令现在在环回接口缓冲区中,但是为了让服务器读取该命令,内核应该安排服务器进程(目前在系统调用中被阻塞)运行,等等。因此在实际中,环回接口仍然涉及类似网络的延迟,这是因为内核调度器的工作方式。

基本上,在度量网络服务器中的性能时,繁忙循环基准测试是最愚蠢的做法。明智的做法是避免以这种方式进行基准测试。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值