使用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 ...
随机推荐
- 2022-05-18:假设数组a和数组b为两组信号: 1) length(b) <= length(a); 2) 对于任意0<=i<length(b), 有b[i+1] - b[i] == a[i+1
2022-05-18:假设数组a和数组b为两组信号: length(b) <= length(a): 对于任意0<=i<length(b), 有b[i+1] - b[i] == a[ ...
- golang技术栈常见网址
go所有,包含goadmin golang标准库文档 golang修养之路 Golang Profiling: 关于 pprof go问题 go语言设计与实现 go.mod解析 proto3 prot ...
- vue全家桶进阶之路27:Vue.js 3.0的下载和安装
使用脚手架vue-cli创建vue3项目,创建前需要准备以下: 1.node.js环境 见:https://www.cnblogs.com/beichengshiqiao/p/17251233.htm ...
- protoBuf 实现客户端与服务端
转载请注明出处: 1.定义消息格式 在 src/main/proto 目录下创建 person.proto 文件,并定义消息格式,例如: syntax = "proto3"; pa ...
- 【GiraKoo】安装Visual Assist失败,提示“此扩展已经安装到所有适用的产品”
[问题解决]安装Visual Assist失败,提示"此扩展已经安装到所有适用的产品" 在安装Visual Assist插件时,提示错误. 点击下一步之后,进入插件安装界面.插件安 ...
- drf——反序列化校验源码(了解)、断言、drf之请求和响应、视图之两个视图基类
1.模块与包 # 模块与包 模块:一个py文件 被别的py文件导入使用,这个py文件称之为模块,运行的这个py文件称之为脚本文件 包:一个文件夹下有__init__.py # 模块与包的导入问题 '' ...
- UCOS II源码分析二
最近大家都沉浸在找到实习的快乐中,自我充电的时间相对减少了,今天重拾一下ucosii的学习,记录如下: 上一篇文章分析了ucosii源码文件组织结构以及简单介绍了各个文件夹里对应文件的功能,要是对uc ...
- web自动化01-环境搭建
1.自动化测试是什么? 借助工具实现 借助代码编写脚本实现 2.自动化测试需要掌握那些? web自动化测试基础 移动端自动化基础 pytest自动化测试框架 po设计模式 数据驱动 日志模块使用 自 ...
- 详解RocketMQ 顺序消费机制
摘要:顺序消息是指对于一个指定的 Topic ,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费. 本文分享自华为云社区<RocketMQ 顺序 ...
- Spark常用算子
Spark是一个快速.通用.可扩展的分布式数据处理引擎,支持各种数据处理任务.Spark提供了许多强大的算子,用于对数据集进行各种转换和操作. 以下是Spark中常用的一些算子: 1. map:对RD ...