RedisTemplate Pipeline 管道使用
官网文档: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
提供了管道的方法,如下图:
可以看到主要为 SessionCallback
与 RedisCallback
,它们的区别主要为 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 中使用管道的两种方式,分别为 SessionCallback
与 RedisCallback
的方法重载;介绍了它们之间的区别,并介绍了 SessionCallback
方法如何进行写入与读取;最后简单的对使用管道与不使用管道在批量写入的情况下性能的差异。
RedisTemplate Pipeline 管道使用相关推荐
- GPU上创建目标检测Pipeline管道
GPU上创建目标检测Pipeline管道 Creating an Object Detection Pipeline for GPUs 今年3月早些时候,展示了retinanet示例,这是一个开源示例 ...
- Redis05:Redis的高级特性:expire 生存时间、pipeline 管道、info命令、Redis的持久化、Redis 的安全策略、Redis监控命令-monitor
一.expire 生存时间 Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会自动删除它. 它的一个典型应用场景是:手机验证码 我们平时在登录或者注册的时候,手机会接收到一 ...
- RedisTemplate使用PipeLine管道命令
一.为何用? 减少请求次数,将多条请求命令合成一次请求通过管道发给redis server,再通过回调函数一次性接收多个命令的结果,减少网络IO次数,在高并发情况下可带来明显性能提升.注意的是,red ...
- Python机器学习:多项式回归002scikit中的多项式回归与pipeline(管道)
直接看代码 import numpy as np import matplotlib.pyplot as plt x = np.random.uniform(-3,3,size=100) #在最新版本 ...
- pipeline(管道的连续应用)
# -*- coding: utf-8 -*- """ Created on Tue Aug 09 22:55:06 2016@author: Administrator ...
- php redis pipeline管道技术
概念 如果需要一次执行多个redis命令,以往的方式需要发送多次命令请求,有redis服务器依次执行,并返回结果,为了解决此类问题,设计者设计出了redis管道命令:客户端可以向服务器发送多个请求,而 ...
- python pipeline管道模式的初级实践
任务:要从一段文本中提取出手机号.微信号和一些意图信息.其中有一些模块的输入是需要一些模块的输出的,于是就想到可不可以用管道模式,对这一系列操作进行处理. 1.相关库的使用 pipeline的框架,我 ...
- NLP冻手之路(4)——pipeline管道函数的使用
✅ NLP 研 0 选手的学习笔记 文章目录 一.需要的环境 二.pipeline简介 三.pipeline的使用 3.1 情感分类 3.2 完形填空 3.3 文本生成 3.4 命名实体识别 3.5 ...
- scrapy pipeline 管道 (图片,文件)
一.scrapy的图片管道可以方便的快速的批量的 下载图片连接 一. 普通使用方法 (1)settings. py 'scrapy. pipelines. imges. ImagesPipelin ...
最新文章
- 数据标注、模型调参debug...通通自动化!华为云AI开发集大成之作ModelArts 2.0发布...
- 你了解的继承方式html,法定继承、遗嘱继承、遗赠,这三种房产过户方式你了解多少?...
- java jar包 配置文件_java 导入jar包中配置文件
- 移动端H5设计稿的问题与解决办法汇总
- sqlserver没有ldf附加数据库
- 数据预处理—5.box-cox变换及python实现
- 22.分布式系统基础设施
- IMF 自定义 IMFTimer、IMFTimerTask
- 技嘉主板+AMD CPU开启CPU虚拟化方法
- 雨夜赶长路,房企必经的三场“价值战事”
- 行业:美团将在快手开放平台上线美团小程序
- C语言结构体学生基本资料,用结构体定义10个学生基本信息
- python中texttable库显示实时数据_用Python串口实时显示数据并绘图pyqtgraph
- matlab已知函数表达式画函数图像,怎么用matlab画已知函数表达式的一个函数图像?函数比较复杂的……...
- 武田宣布多项细胞疗法合作,以推进公司的新型免疫肿瘤学阵容
- 【matlab】butter高通/低通/带通滤波
- 可以运行vivado的云服务器,百度云服务器FPGA标准开发环境的逻辑开发与编译示例 - 全文...
- Stc8硬件乘除法器_16位除16位_汇编
- armv7刷梅林386.3_2官改后格式化u盘为ext4
- java 匹配汉字拼音(匹配多音字)