写在前面

去年下半年,出于学习Redis的目的,在看完《Redis in Action》一书后,开始尝试翻译Redis官方文档。尽管Redis中文官方网站有了译本,但是看别人翻译好的和自己翻译英文原文毕竟还是有很大的不同。这一系列文章之前发布在GitBook上,为了方便管理,跟其他文章一起放在同一个平台,遂全部迁移至简书。由于本人学习Redis时间不长,认识有限,同时也缺少实战经验,翻译中有任何不恰当之处,欢迎各位及时斧正,本人将不胜感激。对英文官方文档感兴趣的朋友也可以直接访问https://redis.io/ 进行获取。

使用流水线来提升redis的查询速度

请求/响应协议和RTT

Redis是一个使用客户端-服务端模型和请求/响应协议的TCP服务。这意味着完成一次请求通常需要经过以下步骤:

客户端向服务端发起一次查询请求并读取socket,这通常是以阻塞方式来等待服务端响应。

服务端处理命令并将响应发回给客户端。

例如下面是一个4条命令序列的执行情况:

客户端:INCR x

服务端:1

客户端:INCR x

服务端:2

客户端:INCR x

服务端:3

客户端:INCR x

服务端:4

客户端和服务端通过网络来连接。这样的连接可以很快(loopback接口)也可以很慢(两台主机之间建立的是一个经过了多次跳转的网络连接)。不管网络延迟如何,数据包从客户端发往服务端,然后携带响应从服务端发往客户端总是会消耗时间的。

这个时间被称为RTT(Round Trip Time)。当客户端需要一次性处理很多请求时很容易看到这是如何影响到性能的(比如说向一个列表中添加很多元素,或者用很多键值填充数据库)。例如假设RTT时间为250毫秒(在网络连接很慢的网络条件下),那么即使服务端每秒可以处理100000个请求,我们每秒最多也只能处理4个请求。

如果使用loopback接口,RTT时间就会短很多(例如在我的机器上ping127.0.0.1只需要0.044毫秒),但是如果我们需要批量处理很多写请求,这个时间仍然是很大的一笔开销。

好在我们还有一种方式可以来改善这种状况。

Redis流水线

即使客户端旧的请求还没有得到响应,一个请求/响应服务器也可以处理新的请求。这样一来我们就可以一次向服务端发送多条命令而根本不用等待响应,最后在一个步骤中读取所有回复。

这就是流水线,这是一种几十年来被广泛采用的技术。例如很多POP3协议的实现已经支持了这种特性,它极大地加快了从服务器下载新邮件的过程。

Redis很早就支持了流水线功能,所以无论你正在使用的是哪个版本,你都可以使用Redis的流水线技术。下面是一个使用这种原生能力的例子:

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

+PONG

+PONG

+PONG

这一次我们没有为每次调用都消耗RTT,而是4个命令只消耗一次时间。

更明确地说,通过使用流水线技术,我们第一个例子的操作顺序将会是下面这个样子:

客户端: INCR x

客户端: INCR x

客户端: INCR x

客户端: INCR x

服务端:1

服务端:2

服务端:3

服务端:4

重要提示:当客户端使用流水线技术来发送命令时,服务端将不得不使用内存来排队答复。所以如果你需要使用流水线来发送很多命令,最好是将他们按照合理的数量来分批处理,比如先发送10000条命令,读取响应,再发送另外10000条命令等等。速度几乎是一样的,但是将需要大量额外的内存来存储这10000条命令的答复。

这不仅仅关乎RTT

流水线不仅仅是一种用来减少RTT延迟成本的方式,实际上对于一台给定的Redis服务器,它极大地提高了每秒钟你所能处理的操作数量。一个事实是,当不采用流水线技术时,从访问数据结构并且产生响应的角度来看,每一条命令的时间消耗都是很少的,但是从处理socket IO的角度来看,这个时间消耗确是很大的。它涉及到调用read()和write()这些系统调用,这意味着要从用户侧到内核侧。而上下文切换是一个巨大的时间开销,会严重影响响应速度。

当使用流水线时,一个简单的read()系统调用就可以读取很多命令,同样的,一个简单的write()系统调用就可以将很多回复传送出去。正因为如此,每秒钟可以处理的查询命令的数量几乎随着管道长度的增加而呈线性增长,最终可以达到不使用流水线这种基本情况时的10倍,正如你从下图看到的那样:

image.png

一些真实世界的代码样例

在下面这个基准测试中,我们将会使用基于Ruby的redis客户端,支持流水线操作,来测试流水线对于速度的提升效果:

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

如你所见,开启流水线后,我们把传输速度提升了5倍。

流水线 VS 脚本

使用Redis脚本(2.6及以上版本的redis可用),很多使用流水线的场景可以获得更高效的处理,因为使用脚本可以在服务端执行大量工作。脚本的一大优势是它可以使读写数据只需要很小的时延,使得读、计算和写操作变得很快(流水线在这种场景下做不到这一点,因为客户端在调用写命令之前需要读命令的返回结果)

有时候应用也会需要向流水线发送EVAL或者EVALSHA命令。这完全是有可能的并且Redis已经通过SCRIPT LOAD命令明确支持了这一点(它保证EVALSHA命令会调用成功)。

附录:为什么即使在loopback接口上一个忙碌的循环也很慢

即使在这个页面的背景之下,你还是会想知道为什么即使在loopback接口上执行并且服务端和客户端运行在同一台物理机上时,一个一个像下面的Redis基准测试(伪代码)还是会很慢:

FOR-ONE-SECOND:

Redis.SET("foo","bar")

END

毕竟如果Redis过程和基准测试运行在一起时,难道它不是仅仅将信息在内存上从一个地方复制到另一个地方,中间没有任何真正的延时和网络参与进来吗?

原因在于系统上的过程不是一直在运行,实际上是内核调度器才让过程运行起来,所以基准测试开始运行时,从Redis服务端读取返回数据(跟最后一条执行的命令相关),并且写了一条新命令。现在命令存在于loopback接口的缓存里,但是为了能够被服务端读取到,内核会通过调度让服务端的过程(当前被阻塞在系统调用里)运行起来,等等。所以在实际场景下,因为内核调度器的工作机制,loopback接口还是涉及到了类网络延时。

基本上在网络服务器中测量性能时,一个忙碌的循环基准测试是最愚蠢的事情。明智的做法是避免使用这种方法进行基准测试。

java redis 流水线,Redis系列(1) —— 流水线相关推荐

  1. 【日常Exception】第二十四回:nested exception is java.lang.NoClassDefFoundError: redis/clients/jedis/util/Pool

    热门系列: 程序人生,精彩抢先看 日常异常,是否也有你似曾相识的那一个 1.问题 近期遇到的一个异常问题如题所示,下面是完整的异常内容,原景重现: PropertyAccessException 1: ...

  2. c#获取对象的唯一标识_在 Java 中利用 redis 实现分布式全局唯一标识服务

    作者: 杨高超 juejin.im/post/5a4984265188252b145b643e 获取全局唯一标识的方法介绍 在一个IT系统中,获取一个对象的唯一标识符是一个普遍的需求.在以前的单体应用 ...

  3. Redis在CentOS 6.8中的安装方法,JAVA初级使用Redis连接池

    1.打开https://redis.io/在Download it下面直接点击"Redis 5.0.3 is the latest stable version."下载redis- ...

  4. Caused by: java.lang.NoClassDefFoundError: redis/clients/util/Pool

    严重: Exception sending context initialized event to listener instance of class org.springframework.we ...

  5. 在java中使用redis

    在java中使用redis很简单,只需要添加jedist.jar,通过它的api就可以了.而且,api和redis的语法几乎完全相同.以下简单的测试: 参考:http://www.runoob.com ...

  6. 【转】Java代码操作Redis的sentinel和Redis的集群Cluster操作

    总共四台机器,crxy99,crxy98分别是主节点和从节点.   crxy97和crxy96是两个监控此主从架构的sentinel节点. 直接看代码: 1 import org.junit.Test ...

  7. 在 Java 中利用 redis 实现 LBS 服务

    前言 LBS(基于位置的服务) 服务是现在移动互联网中比较常用的功能.例如外卖服务中常用的我附近的店铺的功能,通常是以用户当前的位置坐标为基础,查询一定距离范围类的店铺,按照距离远近进行倒序排序. 自 ...

  8. Redis介绍 Java客户端操作Redis

    Redis介绍 && Java客户端操作Redis 本文内容 redis介绍 redis的 shell 客户端简介 redis的 java 客户端简介 环境配置 redis 2.8.1 ...

  9. Redis介绍 Java客户端操作Redis

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow Redis介绍 & ...

最新文章

  1. Android-无障碍服务(AccessibilityService)
  2. android开发--翻转闹铃(从制作到打包)
  3. iOS 崩溃日志在线符号化实践
  4. 关于Cocos2d-x属性和引用
  5. python基础——元组、文件及其它
  6. 如何在bootstap中修改checkbox的样式
  7. java scrollpane放按钮_java – 如何在BoxLayout上放置scrollPane?
  8. 数据本地存储方法封装(笔记)localStorage、sessionStorage
  9. [转]simhash进行文本查重
  10. 语言常用c100单词,英语口语练习_夏普新款PW-C100-G电子词典测评_沪江英语
  11. oracle的partition,ORACLE PARTITION简介
  12. 机器视觉经典案例-表面划伤检测案例
  13. JSP文件怎么运行JAVA_jsp文件怎么运行
  14. 诱人的 react 视频教程-基础篇(14 个视频)
  15. android 2k 屏幕 字体模糊,2k显示器上的字体模糊
  16. 截图并使用libjpeg库压缩BMP为JPG与将JPG转换为BMP
  17. 第三方Charts绘制图表四种形式:饼状图,雷达图,柱状图,直线图
  18. C语言”%p”的意思-----printf(%p,p)
  19. dbca静默建库踩坑
  20. 【小程序专栏】总结uniapp开发小程序的开发规范

热门文章

  1. mysql 命令行 主从复制_MySQL 的主从复制(高级篇)
  2. javame学习_从零基础自学Java教程:648集全网最新Java学习教程,一学就会
  3. pwm控制舵机转动角度程序_Mixly 第15课 舵机的使用
  4. java小白会有那些工作_Java小白找工作与学习的第四天
  5. activex控件 新对象 ocx 初始化_Office已经支持64位的树控件Treeview了
  6. html5 observer api,基于HTML5新特性Mutation Observer实现编辑器的撤销和回退操作
  7. 那些地方会用C语言多线程,如何用C语言实现多线程
  8. padding 后尺寸变化 设置_padding margin border 和元素大小
  9. 英伟达_如何超越英伟达?
  10. INTEL和AMD两大巨头的前身