使用redis pipeline提升性能
前言
本篇来介绍一下redis pipeline,主要是由于最近一次在帮开发同学review代码的时候,发现对redis有个循环操作可以优化。场景大概是这样的,根据某个uid要从redis查询一批数据,每次大概1000个key左右,如果查得到就返回,否则查db,然后写回缓存。由于每次要查的key比较多,虽然redis单次查询很快,但如果key很多,每次查询redis都需要读写socket,与client间的网络数据传输,都需要消耗时间,累加起来也会变得非常慢。开发同学决定使用批量的方式,例如每次操作100个key,使用RedisTemplate批量查询代码如下:
redisTemplate.opsForValue().multiGet(keys);
如果查询到的是null,则表示缓存不存在或过期,则查询数据库,再批量写回redis,伪代码如下:
for (Long id : list) {
operations.opsForValue().set("key", id, 30, TimeUnit.MINUTES);
}
他并没有使用批量的方式,如果有100个,这里就需要执行100次set命令,经过了解后原因是批量写入并不能设置过期时间,我们看它的api确实只能设置key-value,但没有过期时间也是不行的。
void multiSet(Map<? extends K, ? extends V> map);
单个循环设置肯定不行,除了自己执行方法会比较慢,影响用户体验,可能导致接口超时外,由于redis是单线程执行命令的,还会影响其它命令的执行,所以必须优化。
优化的方式就是本篇要介绍的:pipeline。
pipeline
pipeline是管道的意思,它最主要的作用就是降低RRT(client-server数据传输往返时间)。在请求-响应过程,除了传递我们的数据,还需要协议信息,例如http协议的请求头,响应头,这些信息也会增加传输时间。举个例子,假设一次RRT是10ms,那么执行10条命令,就需要100ms,如果我们将其打包到一起执行,RRT就还是10ms(虽然传输的数据变多了,但协议本身的信息没有变多,基本可以忽略不计),传输效率提升了10倍。除此之外,redis server每次处理命令都需要对Socket进行IO操作,这涉及到用户态、内核态的切换,如果批量进行处理,对性能的提升也很有帮助。
pineline将一批命令打包一起执行,但不保证他们的原子性,不像事务一样可以保证一起成功或失败,可能前面的命令执行成功了,后面的执行失败。
这和我们平时操作数据库的思想是一样的,单个查询转换为批量查询,单个插入转换为批量插入,同样需要注意是,批量虽好,但不能一次过多,否则处理起来比较久,反而得不偿失。
更多的知识可参考官方文档:https://redis.io/docs/manual/pipelining/
我们使用springboot 2.x版本,使用spring-boot-starter-data-redis,它给我们默认集成的redis client是lettuce。在使用一个不熟悉或比较新的东西的时候,本人有一个习惯,会先google一下,例如:“RedisTemplate pipeline 注意事项”,“RedisTemplate pipeline 坑”,看看有没有前人踩过坑,借鉴一下。这次也一样,google之后果然发现有点坑,例如这篇提到的Spring Data Redis与Lettuce使用pipeline时,实际命令并不是一起执行的,有时是单条执行,有时是合并几条执行。

我们自己写下测试代码如下:
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (int i = 0; i < 100; i++) {
operations.opsForValue().set("testPipeline2" + i, i, 1, TimeUnit.MINUTES);
}
return null;
}
});
在set位置打个断点,然后到redis server使用monitor命令观察,看命令到底是不是一条一条给过来的。monitor命令会将server执行的命令都打印出来,生产环境慎用。
按照上面的分析,正常情况下这些命令应该是一起发送到server端一起执行的,不会断断续续,但实际我们观察确实不是一起给过来,断断续续的,如下:

我们把lettuce替换成jedis看看。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
还是执行上面的代码,打断点,使用jedis可以观察到,每次循环monitor都不会观察到有命令执行,直到最后才一批给过来。

但我们不想直接替换lettuce为jedis,一个是它是spring boot默认集成的,拥有更好的性能,二是替换后不知道其它功能有没有影响,那怎么办呢?
我们项目还使用redission分布式锁,其实redission也是一个redis client,理论上它应该实现所有client的功能,pipeline自然也有实现。
我们使用redission如下:
RBatch batch = redissonClient.createBatch();
for (int i = 0; i < 100; i++) {
batch.getBucket("testBatch" + i).setAsync(i, 1, TimeUnit.MINUTES);
}
batch.execute();
这次我们把断点打在execute位置,看看是不是execute时才一起提交到server执行,答案显然是的。

接下来我们简单测试一下性能差距,分别是单个请求,使用lettuce,使用jedis,使用redission,执行10000次,耗时如下:
单个请求:73029ms
lettuce: 712ms
jedis: 413ms
redission: 341ms
lettuce出乎意料执行还是很快,就想上面提到的,它有时还是会部分打包一起执行,但终究不是一次执行,有兴趣的可以深入了解一下。
更多分享,欢迎关注我的github:https://github.com/jmilktea/jtea
使用redis pipeline提升性能的更多相关文章
- 使用Redis管道提升性能
首发于 樊浩柏科学院 Redis 的 管道 (pipelining)是用来打包多条无关命令批量执行,以减少多个命令分别执行带来的网络交互时间.在一些批量操作数据的场景,使用管道可以显著提升 Redis ...
- openfire源码解读之将cache和session对象移入redis以提升性能
原文:http://blog.csdn.net/jinzhencs/article/details/50522322 前言: 目前我们的openfire服务器只能支撑单机2W 集群4W.(估测在线用户 ...
- redis通过pipeline提升吞吐量
案例目标 简单介绍 redis pipeline 的机制,结合一段实例说明pipeline 在提升吞吐量方面发生的效用. 案例背景 应用系统在数据推送或事件处理过程中,往往出现数据流经过多个网元: 然 ...
- redis使用管道pipeline提升批量操作性能(php演示)
Redis是一个TCP服务器,支持请求/响应协议. 在Redis中,请求通过以下步骤完成: 客户端向服务器发送查询,并从套接字读取,通常以阻塞的方式,用于服务器响应. 服务器处理命令并将响应发送回客户 ...
- 使用springboot cache + redis缓存时使用gzip压缩以提升性能
背景 在高并发的场景中,我们通常会使用缓存提升性能.在使用springboot cache时,我们通常会使用基于JSON的序列化与反序列化. JSON具有可读性强,结构简单的特点,使用灵活. 但是JS ...
- Redis Pipeline原理分析
转载请注明出处:http://www.cnblogs.com/jabnih/ 1. 基本原理 1.1 为什么会出现Pipeline Redis本身是基于Request/Response协议的,正常情况 ...
- 如何用好redis pipeline
编者注:pipeline是Redis的一个提高吞吐量的机制,适用于多key读写场景,比如同时读取多个key的value,或者更新多个key的value.工作过程中发现挺多小伙伴都对pipeline多少 ...
- 等待 Redis 应答 Redis pipeline It's not just a matter of RTT
小结: 1.When pipelining is used, many commands are usually read with a single read() system call, and ...
- Redis基础与性能调优
Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用. Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hyperloglogs等. ...
- 说说 Redis pipeline
更多技术文章,请关注我的个人博客 www.immaxfang.com 和小公众号 Max的学习札记. Redis 客户端和服务端之间是采用 TCP 协议进行通信的,是基于 Request/Respon ...
随机推荐
- Node 调试利器,前端、Node 开发必备 - VSCode JS Debug Terminal
经常看到有同学抱怨 Node 调试麻烦或者是搞不清怎么调试各种脚本.Jest.Webpack 等等,而偶尔看到的调试相关的文章又全都是在写 inspect.launch.json 这些方案,其实有一定 ...
- uni-app Flex布局
Flexbox #Flex 容器 Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性. nvue布局模型基于 CSS Flexbox, ...
- PictureBox保存图片照片到数据库
Private Sub PAPHOTO_SAVE() Try If TxtPictureURL.Text.ToString <> "" Then Dim SQL_Str ...
- el-table自适应列宽
这里可对内容为文本的列进行自适应列宽 以下为 工具方法 /** * 使用span标签包裹内容,然后计算span的宽度 width: px * @param valArr */ function get ...
- 【Java】连接MySQL问题总结
前言 最近在学习Java的数据库相关操作,在看视频时自己找资源而产生的一些问题,在此做个总结. 正文 在刚开始学习的时候,你可能跟着老师这样写代码,虽然某些地方已经冒出了红色的波浪线,但你半信半疑的继 ...
- SpringMVC 简单的开始
SpringMVC简单的开始 利用Spring模板配置写一个web项目. 1.核心配置文件(模板代码) <?xml version="1.0" encoding=" ...
- harbor改造为https---血泪史
- docker镜像的原理
docker镜像的原理 docker镜像是由特殊的文件系统叠加而成 最低端是bootfs,并使用宿主机的bootfs 第二层是root文件系统rootfs,称之为base image 再往上是可叠加的 ...
- WPF 入门笔记 - 04 - 数据绑定
慢慢来,谁还没有一个努力的过程. --网易云音乐 概述 数据绑定概述 (WPF .NET) 什么是数据绑定? 数据绑定(Data Binding)是WPF一种强大的机制,用于在应用程序的各个部分之间建 ...
- 洛谷 P8179 Tyres
滴叉题/se/se 题意 直接复制了 有 \(n\) 套轮胎,滴叉需要用这些轮胎跑 \(m\) 圈.使用第 \(i\) 套轮胎跑的第 \(j\) 圈(对每套轮胎单独计数)需要 \(a_i+b_i(j- ...