官网文档:https://docs.spring.io/spring-data/redis/docs/current/reference/html/

一、前言

当需要执行大批量的写入或者查询时,使用 redis 一条条命令的执行性能肯定没有一次性执行完要好;假设执行完一条 redis 命令的网络耗时为 20ms,有 1 万条命令需要执行,算一下光发送这些命令的网络耗时就达到 200,000ms(200s),这是不能接受的,我们可以使用 RedisTemplate 提供的管道进行批量执行。
根据官网的描述:Redis 提供了对 pipelining 的支持,在向服务器发送多个命令时,无需等待每一条命令响应,然后在一个步骤中读取所有的响应。经过打包命令发送与返回,在一定程度上节省了网络io耗时。

二、Pipelining 介绍与使用

我们使用 Spring 的RedisTemplate 来执行管道操作,RedisTemplate 提供了管道的方法,如下图:

可以看到主要为 SessionCallbackRedisCallback,它们的区别主要为 API 的封装,RedisCallback 为原生的 api,SessionCallback 为 Spring 封装的 api。

下图为 SessionCallback 的方法:

追踪源码可以知道入参 RedisOperations<K, V> operations 其实就是 RedisTemplate 本身,因此所有操作都是经过封装的 api

 private Object executeSession(SessionCallback<?> session) {return session.execute(this);}

下图为 RedisConnection 的方法:

同样的,我们追踪源码可以知道入参为 RedisConnection

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {// 略...RedisConnectionFactory factory = getRequiredConnectionFactory();RedisConnection conn = RedisConnectionUtils.getConnection(factory, enableTransactionSupport);try {boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);RedisConnection connToUse = preProcessConnection(conn, existingConnection);boolean pipelineStatus = connToUse.isPipelined();if (pipeline && !pipelineStatus) {connToUse.openPipeline();}RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));// 入参为 connectionT result = action.doInRedis(connToExpose);// close pipelineif (pipeline && !pipelineStatus) {connToUse.closePipeline();}return postProcessResult(result, connToUse, existingConnection);} finally {RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);}}

因此它们两个的主要区别就是提供的方法不同,如果想使用原生的 api 则使用 RedisCallback,想使用 Spring 给我们封装后的 api 则使用 SessionCallback

介绍了管道的基本实现,下面以 SessionCallback 实现来讲讲如何使用:
写入:

SessionCallback<?> sessionCallback = new SessionCallback<>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {// 转为你 RedisTemplate 即可RedisTemplate<String, Object> ops = (RedisTemplate<String, Object>) operations;ops.opsForValue().set("key", "value");// 必须返回 null,return null;}
};
redisTemplate.executePipelined(sessionCallback);

需要注意的是返回值必须为 null,否则会报错;源码判断结果是否为 null 逻辑如下:

Object result = executeSession(session);
if (result != null) {throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");
}

读取:

SessionCallback<?> sessionCallback = new SessionCallback<>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {// 转为你 RedisTemplate 即可RedisTemplate<String, Object> ops = (RedisTemplate<String, Object>) operations;ops.opsForValue().get("key");// 必须返回 null,return null;}
};
redisTemplate.executePipelined(sessionCallback);

需要注意的是我们不能直接将结果存储起来,类似这样 ×

List<Object> results = new ArrayList<>(10);
SessionCallback<?> sessionCallback = new SessionCallback<>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {// 转为你 RedisTemplate 即可RedisTemplate<String, Object> ops = (RedisTemplate<String, Object>) operations;results.add(ops.opsForValue().get("key"));// 必须返回 null,return null;}
};

这样子是拿不到需要的查询结果,正确的做法是从管道执行的返回结果获取 √

List<Object> resultObjs = redisTemplate.executePipelined(sessionCallback);

而从源码可以知道,结果确实也是从管道中拿到的

Object result = executeSession(session);
if (result != null) {throw new InvalidDataAccessApiUsageException("Callback cannot return a non-null value as it gets overwritten by the pipeline");
}
// 里面的实现大致是从 future 中 get 拿到结果;具体步骤这里不做分析,有兴趣的可以自己看源码
List<Object> closePipeline = connection.closePipeline();

三、测试

下面测试一下使用管道与不使用管道写入 key 耗时。注:Redis 为单节点

  • 先测试不使用管道,写入 1 万个 key 耗时
    @Testpublic void write() {TimeInterval timer = DateUtil.timer();String keyPrefix = "writeTest:";ValueOperations<String, Object> operations = redisTemplate.opsForValue();for (int i = 0; i < 10000; i++) {operations.set(keyPrefix + i, i);}System.out.println("写入 1 万个 key 耗时:" + timer.intervalMs() + " ms");}

输出结果为:写入 1 万个 key 耗时:5796 ms

  • 再测试使用管道的耗时情况
@Testpublic void write() {TimeInterval timer = DateUtil.timer();String keyPrefix = "writeTest:";SessionCallback<?> sessionCallback = new SessionCallback<>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> opt) throws DataAccessException {RedisTemplate<String, Object> template = (RedisTemplate<String, Object>) opt;ValueOperations<String, Object> operations = template.opsForValue();for (int i = 0; i < 10000; i++) {operations.set(keyPrefix + i, i);}return null;}};redisTemplate.executePipelined(sessionCallback);System.out.println("写入 1 万个 key 耗时:" + timer.intervalMs() + " ms");}

输出结果为:写入 1 万个 key 耗时:626 ms

从输出的结果就能明显看出管道的使用对批量插入与读取的性能有很大的提升!!

四、总结

本文主要介绍了在 Spring 中使用管道的两种方式,分别为 SessionCallbackRedisCallback 的方法重载;介绍了它们之间的区别,并介绍了 SessionCallback 方法如何进行写入与读取;最后简单的对使用管道与不使用管道在批量写入的情况下性能的差异。

RedisTemplate Pipeline 管道使用相关推荐

  1. GPU上创建目标检测Pipeline管道

    GPU上创建目标检测Pipeline管道 Creating an Object Detection Pipeline for GPUs 今年3月早些时候,展示了retinanet示例,这是一个开源示例 ...

  2. Redis05:Redis的高级特性:expire 生存时间、pipeline 管道、info命令、Redis的持久化、Redis 的安全策略、Redis监控命令-monitor

    一.expire 生存时间 Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会自动删除它. 它的一个典型应用场景是:手机验证码 我们平时在登录或者注册的时候,手机会接收到一 ...

  3. RedisTemplate使用PipeLine管道命令

    一.为何用? 减少请求次数,将多条请求命令合成一次请求通过管道发给redis server,再通过回调函数一次性接收多个命令的结果,减少网络IO次数,在高并发情况下可带来明显性能提升.注意的是,red ...

  4. Python机器学习:多项式回归002scikit中的多项式回归与pipeline(管道)

    直接看代码 import numpy as np import matplotlib.pyplot as plt x = np.random.uniform(-3,3,size=100) #在最新版本 ...

  5. pipeline(管道的连续应用)

    # -*- coding: utf-8 -*- """ Created on Tue Aug 09 22:55:06 2016@author: Administrator ...

  6. php redis pipeline管道技术

    概念 如果需要一次执行多个redis命令,以往的方式需要发送多次命令请求,有redis服务器依次执行,并返回结果,为了解决此类问题,设计者设计出了redis管道命令:客户端可以向服务器发送多个请求,而 ...

  7. python pipeline管道模式的初级实践

    任务:要从一段文本中提取出手机号.微信号和一些意图信息.其中有一些模块的输入是需要一些模块的输出的,于是就想到可不可以用管道模式,对这一系列操作进行处理. 1.相关库的使用 pipeline的框架,我 ...

  8. NLP冻手之路(4)——pipeline管道函数的使用

    ✅ NLP 研 0 选手的学习笔记 文章目录 一.需要的环境 二.pipeline简介 三.pipeline的使用 3.1 情感分类 3.2 完形填空 3.3 文本生成 3.4 命名实体识别 3.5 ...

  9. scrapy pipeline 管道 (图片,文件)

    一.scrapy的图片管道可以方便的快速的批量的 下载图片连接   一. 普通使用方法 (1)settings. py 'scrapy. pipelines. imges. ImagesPipelin ...

最新文章

  1. 数据标注、模型调参debug...通通自动化!华为云AI开发集大成之作ModelArts 2.0发布...
  2. 你了解的继承方式html,法定继承、遗嘱继承、遗赠,这三种房产过户方式你了解多少?...
  3. java jar包 配置文件_java 导入jar包中配置文件
  4. 移动端H5设计稿的问题与解决办法汇总
  5. sqlserver没有ldf附加数据库
  6. 数据预处理—5.box-cox变换及python实现
  7. 22.分布式系统基础设施
  8. IMF 自定义 IMFTimer、IMFTimerTask
  9. 技嘉主板+AMD CPU开启CPU虚拟化方法
  10. 雨夜赶长路,房企必经的三场“价值战事”
  11. 行业:美团将在快手开放平台上线美团小程序
  12. C语言结构体学生基本资料,用结构体定义10个学生基本信息
  13. python中texttable库显示实时数据_用Python串口实时显示数据并绘图pyqtgraph
  14. matlab已知函数表达式画函数图像,怎么用matlab画已知函数表达式的一个函数图像?函数比较复杂的……...
  15. 武田宣布多项细胞疗法合作,以推进公司的新型免疫肿瘤学阵容
  16. 【matlab】butter高通/低通/带通滤波
  17. 可以运行vivado的云服务器,百度云服务器FPGA标准开发环境的逻辑开发与编译示例 - 全文...
  18. Stc8硬件乘除法器_16位除16位_汇编
  19. armv7刷梅林386.3_2官改后格式化u盘为ext4
  20. java 匹配汉字拼音(匹配多音字)

热门文章

  1. php 可以将图片分类吗,PHP图片处理教程.ppt
  2. 【转】bit、Byte、bps、Bps、pps、Gbps的单位详细说明及换算
  3. 彩色图片变成黑白打印风格图片的一种方式
  4. python 发送邮件附件很慢_python发送邮件附件
  5. 微信支付服务商模式下的支付-特约商户-----亲试有效
  6. linux系统磁盘分区查看,linux下磁盘查看和分区
  7. Python123分段函数计算
  8. 电压跟随器的使用方法
  9. 计算机的低配置的cpu,CF最低电脑配置要求是什么
  10. linux(linux常用命令,软件安装,vim编辑器)