前言

本篇来介绍一下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提升性能的更多相关文章

  1. 使用Redis管道提升性能

    首发于 樊浩柏科学院 Redis 的 管道 (pipelining)是用来打包多条无关命令批量执行,以减少多个命令分别执行带来的网络交互时间.在一些批量操作数据的场景,使用管道可以显著提升 Redis ...

  2. openfire源码解读之将cache和session对象移入redis以提升性能

    原文:http://blog.csdn.net/jinzhencs/article/details/50522322 前言: 目前我们的openfire服务器只能支撑单机2W 集群4W.(估测在线用户 ...

  3. redis通过pipeline提升吞吐量

    案例目标 简单介绍 redis pipeline 的机制,结合一段实例说明pipeline 在提升吞吐量方面发生的效用. 案例背景 应用系统在数据推送或事件处理过程中,往往出现数据流经过多个网元: 然 ...

  4. redis使用管道pipeline提升批量操作性能(php演示)

    Redis是一个TCP服务器,支持请求/响应协议. 在Redis中,请求通过以下步骤完成: 客户端向服务器发送查询,并从套接字读取,通常以阻塞的方式,用于服务器响应. 服务器处理命令并将响应发送回客户 ...

  5. 使用springboot cache + redis缓存时使用gzip压缩以提升性能

    背景 在高并发的场景中,我们通常会使用缓存提升性能.在使用springboot cache时,我们通常会使用基于JSON的序列化与反序列化. JSON具有可读性强,结构简单的特点,使用灵活. 但是JS ...

  6. Redis Pipeline原理分析

    转载请注明出处:http://www.cnblogs.com/jabnih/ 1. 基本原理 1.1 为什么会出现Pipeline Redis本身是基于Request/Response协议的,正常情况 ...

  7. 如何用好redis pipeline

    编者注:pipeline是Redis的一个提高吞吐量的机制,适用于多key读写场景,比如同时读取多个key的value,或者更新多个key的value.工作过程中发现挺多小伙伴都对pipeline多少 ...

  8. 等待 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 ...

  9. Redis基础与性能调优

    Redis是一个开源的,基于内存的结构化数据存储媒介,可以作为数据库.缓存服务或消息服务使用. Redis支持多种数据结构,包括字符串.哈希表.链表.集合.有序集合.位图.Hyperloglogs等. ...

  10. 说说 Redis pipeline

    更多技术文章,请关注我的个人博客 www.immaxfang.com 和小公众号 Max的学习札记. Redis 客户端和服务端之间是采用 TCP 协议进行通信的,是基于 Request/Respon ...

随机推荐

  1. Mysql- DDL/DML/DQL/DCL 数据库基本操作语句(持续更新中)

    Mysql基本语法 前言: 在测试项目中经常需要使用到简单的Mysql语句,但是不知道语句结构是什么,经常在百度查来查去: 以下就是总结Mysql常用的基础操作语句: 只需要执行从创建开始执行示例中的 ...

  2. 封装vue基于element的select多选时启用鼠标悬停折叠文字以tooltip显示具体所选值

    相信很多公司的前端开发人员都会选择使用vue+element-ui的形式来开发公司的管理后台系统,基于element-ui很丰富的组件生态,我们可以很快速的开发管理后台系统的页面(管理后台系统的页面也 ...

  3. Kubernetes 证书详解

    K8S 证书介绍 在 Kube-apiserver 中提供了很多认证方式,其中最常用的就是 TLS 认证,当然也有 BootstrapToken,BasicAuth 认证等,只要有一个认证通过,那么 ...

  4. C# EFCore 根据Oracle/SqlServer数据库表生成实体类和DbContext

    官方文档: https://docs.microsoft.com/zh-cn/ef/core/managing-schemas/scaffolding?tabs=dotnet-core-cli 本文以 ...

  5. Springboot——参数校验

    springboot参数校验注解 在controller层需要对前端传来的参数进行校验 校验简单数据类型 使用springboot自带的validation工具可以从后端对前端传来的数据进行校验 使用 ...

  6. Win10环境配置(二) ——Java篇

    Win10环境配置(二) --Java篇 1.工具准备 官网下载地址:[jdk-11.0.11](Java SE Development Kit 11- - Downloads (oracle.com ...

  7. 从 pheatmap 无缝迁移至 ComplexHeatmap

    pheatmap 是一个非常受欢迎的绘制热图的 R 包.ComplexHeatmap 包即是受之启发而来.你可以发现Heatmap()函数中很多参数都与pheatmap()相同.在 pheatmap  ...

  8. 哈希工具john

    john:一种极其强大且适应性强的哈希破解工具 爆破字典使用臭名昭著的 rockyou.txt 词表--这是一个非常大的常用密码词表 使用的工具 字典:rockyou.txt 哈希识别工具:hash- ...

  9. 什么是Sparse by default for crates.io

    当 Rust crate 发布到 crates.io 上时,可以启用"Sparse by default"特性,这意味着默认情况下,crate 不会包含所有依赖项在上传到 crat ...

  10. 时隔多年,从新认识浮动float

    开场白 随着css的发展,在加上各种优秀ui库的兴起. 我们在项目中浮动用的很少. 但并不意味着我们不使用浮动了. 曾几何时,浮动这个属性是那个遥远时代的'超级明星' 排版,布局,都需要使用他. 今不 ...