您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
浅析redis pipeline模式
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
浅析redis pipeline模式
自猿其说Tech
2022-01-20
IP归属:未知
19120浏览
计算机编程
### 1 引言 在各个领域,有一个词眼出现得越来越频繁,即 Pipeline,例如netty的ChannelPipeline、redis的pipeline、Flink PipelineExecutor 体系,今天我们探究一下redis的pipeline模式。 ### 2 redis命令执行流程 当我们使用客户端对 Redis 进行一次操作时,如下图所示,客户端将请求传送给服务器,服务器处理完毕,再将响应回复给客户端。这要花费一个网络数据包来回的时间。Redis 性能瓶颈主要是网络,主要原因就在于 Redis 执行命令的时间通常在微妙级别。正常情况下,我们执行一条 Redis 命令流程要经过如下几个步骤: - 客户端发送 Redis 命令,阻塞等待 Redis 应答 - Redis 接收到命令,执行命令 - 应答,客户端收到响应信息 ![](//img1.jcloudcs.com/developer.jdcloud.com/89392ef8-421f-4c6f-8143-a7aba95ab73520220120135013.png) 其中 1 、3 称之为一次 RTT(Round Trip Time)。在这种情况下,如果同时执行大量命令,那当前命令需要等待上一条命令应答完成后才会执行,这个过程不仅仅只有多次 RTT,还有频繁的调用系统 IO,发送网络请求,如下图: ![](//img1.jcloudcs.com/developer.jdcloud.com/f2fc094e-6f22-4a9f-a4ca-ac2b4382c30e20220120135033.png) 把大量的时间消耗在来回路上,真正办事的时间就只有一点点,这种做法是非常不明智且低效的,为了解决这种低效的做法,pipeline 出现了,它允许客户端一次性发送多条命令,减少 RTT 和 IO 的调用次数(IO 调用涉及到用户态到内核态之间的切换)。如下图: ![](//img1.jcloudcs.com/developer.jdcloud.com/0e5453e3-7bf1-410b-88b4-39929dd3db2a20220120135054.png) ### 3 实现原理 要支持 pipeline,除了需要 Redis 服务端支持,也需要各个客户端的支持,例如 jedis 就对 pipeline 提供了很好的支持。对于服务端来说,所需要的就是能够处理客户端通过一个 TCP 连接发送的多个命令,对多个命令进行排队,执行。而客户端,则需要将多个命令缓存起来,待缓冲区满了就发送,同时还需要处理 Redis 的应答。 pipeline 不是什么新鲜技术,很多技术都使用过,它能提供性能的核心就在于:它能将一组 Redis 命令进行组装,通过一次 RTT 传输给 Redis,同时再将这组命令的执行结果按照顺序返回给客户端。将原来一组命令多次 RTT 以及 IO 交互变成了一组 RTT 和 IO 交互,大大减少了网络传输时间和 IO 调用的时间。 下图是一次请求交互的流程(图片来自:《Redis 深度历险:核心原理与应用实践》), ![](//img1.jcloudcs.com/developer.jdcloud.com/b318decc-6560-4cba-9e37-69c4fecc4c0d20220120135310.png) 需要注意的是,在使用 pipeline 的过程中需要控制其中命令的大小,如果一次组装的命令过多,则会增加造成一定的网络阻塞,也会增加客户端的等待时间。 对上图的交互流程做一次描述: 1. 客户端进程调用write将消息写到操作系统内核为套接字分配的发送缓冲send buffer。 1. 客户端操作系统内核将发送缓冲的内容发送到网卡。 1. 网卡硬件将数据通过「网际路由」送到服务器的网卡。 1. 服务器操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recv buffer。 1. 服务器进程调用read从接收缓冲中取出消息进行处理。 1. 服务器进程调用write将响应消息写到内核为套接字分配的发送缓冲send buffer。 1. 服务器操作系统内核将发送缓冲的内容发送到网卡, 1. 网卡硬件将数据通过「网际路由」送到客户端的网卡。 1. 客户端操作系统内核将网卡的数据放到内核为套接字分配的接收缓冲recv buffer。 1. 客户端进程调用read从接收缓冲中取出消息返回给上层业务逻辑进行处理。 结束。 其中步骤 5~8 和 1~4 是一样的,只不过方向是反过来的,一个是请求,一个是响应。我们开始以为 write 操作是要等到对方收到消息才会返回,但实际上不是这样的。write 操作只负责将数据写到本地操作系统内核的发送缓冲然后就返回了。剩下的事交给操作系统内核异步将数据送到目标机器。但是如果发送缓冲满了,那m么就需要等待缓冲空出空闲空间来,这个就是写操作 IO 操作的真正耗时。我们开始以为 read 操作是从目标机器拉取数据,但实际上不是这样的。read 操作只负责将数据从本地操作系统内核的接收缓冲中取出来就了事了。但是如果缓冲是空的,那么就需要等待数据到来,这个就是读操作 IO 操作的真正耗时。所以对于value = redis.get(key)这样一个简单的请求来说,write操作几乎没有耗时,直接写到发送缓冲就返回,而read就会比较耗时了,因为它要等待消息经过网络路由到目标机器处理后的响应消息,再回送到当前的内核读缓冲才可以返回。这才是一个网络来回的真正开销。 而对于管道来说,连续的write操作根本就没有耗时,之后第一个read操作会等待一个网络的来回开销,然后所有的响应消息就都已经回送到内核的读缓冲了,后续的 read 操作直接就可以从缓冲拿到结果,瞬间就返回了。 ### 4 性能测评 ```java package com.jd.redis; import com.jdwl.platform.wms.outbound.BaseTest; import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; import javax.annotation.Resource; package com.jd.redis; import com.jdwl.platform.wms.outbound.BaseTest; import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; import javax.annotation.Resource; /** * @program: wms5-pickingplan * @description: jedis管道测试类 * @author: wwj * @Date: 2021-11-26 10:23 */ public class JedisPipelined extends BaseTest { @Resource private JedisPool jedisSentinelPool; @Test public void testJedis() { //获取jedis客户端 Jedis jedis = jedisSentinelPool.getResource(); Pipeline pipeline = jedis.pipelined(); long pipelineBegin = System.currentTimeMillis(); for(int i = 0 ; i < 100000 ; i++){ pipeline.set("pipeline:test_"+i,i + ""); } pipeline.sync(); long pipelineEnd = System.currentTimeMillis(); System.out.println("the pipeline is :" + (pipelineEnd - pipelineBegin) + "ms"); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { jedis.set("jedis:test_"+i,i + ""); } long end = System.currentTimeMillis(); System.out.println("the jedis is:" + (end - start) + "ms"); ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/9513ffa7-2846-4ca9-bb50-f62530970e7c20220120135449.png) 如上图所示100000次redsi命令操作管道模式仅需要4s,管道与非管道模式的性能差距很大。 ### 5 小结 redis管道模式对于批量操作的性能提升很大,大数据量的操作阔以考虑使用管道模式 redsi管道的本质是将多个命令组装成一次命令,由客户端提交给服务端,大大减少了网络传输时间和 IO 调用的时间。 类比现有Pipeline模式,Pipeline解决的一类问题是,有时一些线程的步奏比较冗长,而且由于每个阶段的结果与下阶段的执行有关系,又不能分开。可以将任务的处理分解为若干个处理阶段,上一个阶段任务的结果交给下一个阶段来处理,这样每个线程的处理是并行的,可以充分利用资源提高计算效率。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者:王武杰(北斗星团队)
原创文章,需联系作者,授权转载
上一篇:深入解析iOS内存
下一篇:领域驱动设计和多态实现结合
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号