Java Springboot下redis用pipelining管道模式写入性能调优实例讲解

news2024/11/24 6:32:48

Springboot下redis写入pipelining管道模式性能调优实例

一、真实场景

生产真实项目过程中,需要将数据库的数据同步写入redis,此过程中遇到写入redis的瓶颈。每次启动项目都要将数据库数据重载到redis,这个过程耗费了大量的时间。

二、解决思路pipelining(管道)

Redis pipelined
Redis Pipelined是由Client提供的(是防止client端 阻塞的操作)一种请求redis的方式。Redis本身具有很高的吞吐量,因此性能最大的考察便是网络状况,如果应用到redis的网络状况不好,每次请求都将会出现轻微的 阻塞和延迟,这种延迟对于批量请求是很可怕的,譬如要进行数千次数据插入,或是批量获取数据时,我们就需要用到Pipelined。
Pipelined可以将多个请求无 阻塞的发出并按顺序将请求结果“打包”返回,这有点类似于并发请求,可以有效地利用等待结果的 阻塞时间。
注意,Pipelined并不能保证原子性,即pipelined执行的内容可能会被其他客户端或是线程的指令"插队",若想要原子性操作,需要使用事务。

基于RedisTemplate的pipelined
使用RedisTemplate可以轻松实现pipelined,需要依靠原生的RedisConnection对象实现相关操作!

pipelining(管道)
Pipeline:redis的管道命令,允许client将多个请求依次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可,可以改善性能.
pipeline不是原子操作

为何用?
减少请求次数,将多条请求命令合成一次请求通过管道发给redis server,再通过回调函数一次性接收多个命令的结果,减少网络IO次数,在高并发情况下可带来明显性能提升。注意的是,redis server是单线程,多个命令合成一次请求到达redis server依然还是顺序一个个执行的,仅仅只是减少了请求IO次数。
如何用?
RedisCallback和SessionCallBack:
1.作用: 让RedisTemplate进行回调,通过他们可以在同一条连接中一次执行多个redis命令。
2.SessionCalback提供了良好的封装,优先使用它。
3.RedisCallback使用的是原生RedisConnection,用起来比较麻烦,可读性差,但原生api提供的功能比较齐全。

三、论据性能对比相较于传统模式

<dependency>
    <groupId>redis.clients</groupId> #maven引入
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
   compile('redis.clients:jedis:2.9.0')  #gradle引入
   compile('org.springframework.data:spring-data-redis:2.0.8.RELEASE')
package pipeline;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

public class BatchOperSet {

    private static final String HOST = "127.0.0.1";
    private static final int PORT = 6379;

    // 批量插入数据到Redis,正常使用
    public static void batchSetNotUsePipeline() throws Exception {
        Jedis jedis = new Jedis(HOST, PORT);
        String keyPrefix = "normal";
        long begin = System.currentTimeMillis();
        for (int i = 1; i < 10000; i++) {
            String key = keyPrefix + "_" + i; 
            String value = String.valueOf(i);
            jedis.set(key, value);
        }
        jedis.close();
        long end = System.currentTimeMillis();
        System.out.println("not use pipeline batch set total time:" + (end - begin));
    }

    // 批量插入数据到Redis,使用Pipeline
    public static void batchSetUsePipeline() throws Exception {
        Jedis jedis = new Jedis(HOST, PORT);
        Pipeline pipelined = jedis.pipelined();
        String keyPrefix = "pipeline";
        long begin = System.currentTimeMillis();
        for (int i = 1; i < 10000; i++) {
            String key = keyPrefix + "_" + i; 
            String value = String.valueOf(i);
            pipelined.set(key, value);
        }
        pipelined.sync();
        jedis.close();
        long end = System.currentTimeMillis();
        System.out.println("use pipeline batch set total time:" + (end - begin));
    }

    public static void main(String[] args) {    
        try {
            batchSetNotUsePipeline();
            batchSetUsePipeline();      
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

写入性能对比的运行结果如下:

not use pipeline batch get total time:2990
use pipeline batch get total time:41

结论:pipeline模式的写入性能更快

四、实际案例举例

真实案例的采用redis数据结构
Hash(散列)
基本概念:
Redis 散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值既可以是字符串又可以是数值,并且用户同样可以对散列存储的数字值执行自增或自减操作。这个和 Java 的 HashMap 很像,每个 HashMap 有自己的名字,同时可以存储多个 k/v 对。
底层实现:
哈希对象的编码可以是 ziplist 或者 hashtable 。
哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节并且保存的键值对数量小于 512 个,使用ziplist 编码;否则使用 hashtable;
应用场景:
Hash 更适合存储结构化的数据,比如 Java 中的对象;其实 Java 中的对象也可以用 string 进行存储,只需要将 对象 序列化成 json 串就可以,但是如果这个对象的某个属性更新比较频繁的话,那么每次就需要重新将整个对象序列化存储,这样消耗开销比较大。可如果用 hash 来存储 对象的每个属性,那么每次只需要更新要更新的属性就可以。
购物车场景:可以以用户的 id 为 key ,商品的 id 为存储的 field ,商品数量为键值对的value,这样就构成了购物车的三个要素。

redis 127.0.0.1:6379> hset myhash field1 "zhang"            #给键值为myhash的键设置字段为field1,值为zhang。
(integer) 1
redis 127.0.0.1:6379> hget myhash field1             #获取键值为myhash,字段为field1的值。
"zhang"
redis 127.0.0.1:6379> hget myhash field2             #myhash键中不存在field2字段,因此返回nil。
(nil)
redis 127.0.0.1:6379> hset myhash field2 "san"                   #给myhash添加一个新的字段field2,其值为san。
(integer) 1
redis 127.0.0.1:6379> hlen myhash                    #hlen命令获取myhash键的字段数量。
(integer) 2
redis 127.0.0.1:6379> hexists myhash field1                  #判断myhash键中是否存在字段名为field1的字段,由于存在,返回值为1。
(integer) 1
redis 127.0.0.1:6379> hdel myhash field1                 #删除myhash键中字段名为field1的字段,删除成功返回1。
(integer) 1
redis 127.0.0.1:6379> hdel myhash field1                    #再次删除myhash键中字段名为field1的字段,由于上一条命令已经将其删除,因为没有删除,返回0。
(integer) 0
redis 127.0.0.1:6379> hexists myhash field1                     #判断myhash键中是否存在field1字段,由于上一条命令已经将其删除,因为返回0。
(integer) 0
redis 127.0.0.1:6379> hsetnx myhash field1 zhang            #通过hsetnx命令给myhash添加新字段field1,其值为zhang,因为该字段已经被删除,所以该命令添加成功并返回1。
(integer) 1
redis 127.0.0.1:6379> hsetnx myhash field1 zhang            #由于myhash的field1字段已经通过上一条命令添加成功,因为本条命令不做任何操作后返回0。
(integer) 0

在这里插入图片描述
我们可以用Redis Desktop Manager工具进去看redis数据库里面的内容。

五、Hash结构模式例子

Hash传统代码模式如下:

#按制定的大KEY值往redis放入全部map内容 
public static <T> void hPutAll(String hashKey, Map<String,T> map, long timeout){
        try{
            if(StringUtils.isBlank(hashKey)){
                return;
            }
            BoundHashOperations<String, String, String> stringObjectObjectBoundHashOperations = redisClient.redisTemplate.boundHashOps(hashKey);

            map.forEach((key,v)->{
                stringObjectObjectBoundHashOperations.put(key,gson.toJson(v));
            });
        }catch (Exception e){
            Logger.error("redis hputall异常",e);
        }
    }

Hash管道模式如下:

 public static <T> void hPutAll(String hashKey, Map<String,T> map, long timeout){
        try{
            if(StringUtils.isBlank(hashKey)){
                return;
            }
			redisClient.redisTemplate.executePipelined(new SessionCallback<Object>() {
				public Object execute(RedisOperations ro) {
					BoundHashOperations<String, String, String> hashOps = redisClient.redisTemplate
							.boundHashOps(hashKey);

					map.forEach((key, v) -> {
						hashOps.put(key, gson.toJson(v));

					});

					//redisClient.redisTemplate.expire(hashKey, defaultTimeOut, TimeUnit.SECONDS);
					return null;
				}
			});

六、SET结构模式例子

附加set数据结构用管道模式:

@Component
public class RedisTest {
 
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
 
    @PostConstruct
    public void init() {
        test1();
    }
 
    public void test1() {
        List<Object> pipelinedResultList = redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                ValueOperations<String, Object> valueOperations = (ValueOperations<String, Object>) operations.opsForValue();
 
                valueOperations.set("yzh1", "hello world");
                valueOperations.set("yzh2", "redis");
 
                valueOperations.get("yzh1");
                valueOperations.get("yzh2");
 
                // 返回null即可,因为返回值会被管道的返回值覆盖,外层取不到这里的返回值
                return null;
            }
        });
        System.out.println("pipelinedResultList=" + pipelinedResultList);
    }
}

管道预热代码
有的系统对延迟要求很高,那么redis管道第一次请求很慢,就需要在系统启动时进行管道的预热,保证系统启动后每次请求的低延迟。

  @PostConstruct
    public void init() {
        long startTime = System.currentTimeMillis();
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                operations.hasKey((K) "");
                return null;
            }
        });
        log.info("redis初始化管道请求end,耗时:{}ms", System.currentTimeMillis() - startTime);
    }

七、LIST结构模式例子

附加LIST数据结构代码

public void redisPop(List<String> list) {
    List<Object> keys = redisTemplate.executePipelined(new SessionCallback<String>() {
        @Override
        public <K, V> String execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
            for(String str: list){
                for (int i = 0; i < 200; i++) {
                    redisOperations.opsForList().rightPop((K) str);
                }
            }
            return null;
        }
    });
   
   
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图片转字符图片工具类

├── cn.xsshome.imagetool //包名├── convert │ └── ImageToChar //图片转字符图片、文本方法 ├── slideverifycode │ └── SlideVerifyCodeGenerateUtil //滑块验证码工具类代…

多项式回归预测

目录 1、多项式回归 2、R-Squared 1、多项式回归 如果在实际中数据点显然不适合线性回归&#xff08;穿过数据点之间的直线&#xff09;&#xff0c;那么多项式回归可能是理想的选择 像线性回归一样&#xff0c;多项式回归使用变量 x 和 y 之间的关系来找到绘制数据点线的最佳…

空气中的声压级、声功率级、声强级的区别

空气中的声压级、声功率级、的区别 在学习声学理论时&#xff0c;经常听到&#xff0c;声压级、声强级、声功率级的名称&#xff0c;经常也听到它们的单位为dB.但是它们是怎样的区别呢&#xff1f;下面介绍这几个名词 一、定义和计算 1.声压级 声压级以 L p {L_p} Lp​表示&am…

《Netty》从零开始学netty源码(六十一)之解码器

目录 解码器LineBasedFrameDecoderDelimiterBasedFrameDecoderFixedLengthFrameDecoderLengthFieldBasedFrameDecoder 解码器 在上一篇中介绍了Netty的解码器抽象类ByteToMessageDecoder&#xff0c;Netty也定义了一些常用的解码器&#xff0c;这些解码器都实现了ByteToMessag…

第一波IT去美国化的公司不是华为

第一波去美国化的不是华为&#xff0c;是BAT京东等 互联网类公司发起的工程叫&#xff1a;去IOE IBM小型机&#xff0c;ORACLE数据库&#xff0c;EMC存储 几年前已经完成了技术去美化 趣讲大白话&#xff1a;安全是底线 【趣讲信息科技174期】 **************************** 第…

hash在后端的应用

目录 散列表hash 函数种类&#xff1a; 布隆过滤器场景&#xff1a;构成原理应用分析选择 hash 函数问题&#xff1a;只用2GB内存在20亿个整数中找到出现次数最多的数完整代码&#xff1a; 分布式一致性 hashhash迁移hash 偏移虚拟节点 散列表 hash 函数 计算速度快 强随机分布…

Windows下搭建Tomcat HTTP服务,发布外网远程访问

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 转载自cpolar内网穿透的文章&#xff1a;外网访问本地Tomcat服务器【cpolar…

常见卫星图源下载教程

文章目录 一、引言二、在线地图源是简介三、常见地信软件如何浏览与下载图源&#xff1f;&#xff08;1&#xff09;QGIS&#xff08;2&#xff09;arcgis pro&#xff08;3&#xff09;arcgis 四、各个图源的具体下载方法(1)Google Earth1&#xff09;qgis下载Google Earth2&a…

面对史上最难求职季,哪些测试技能更容易拿到offer?

在一线大厂&#xff0c;没有测试这个岗位&#xff0c;只有测开这个岗位。这几年&#xff0c;各互联网大厂技术高速更新迭代&#xff0c;软件测试行业也正处于转型期。传统的功能测试技术逐步淘汰&#xff0c;各种新的测试技术层出不穷&#xff0c;测试人员的薪资也水涨船高。与…

Java【问题 05】yml配置文件boolean一直为false问题分析解决

yml配置文件boolean一直为false 1.问题说明2.bug复现2.1 yml配置2.2 配置类2.3 测试类2.4 结果输出 3.源码分析3.1 Data3.2 Generate Getters and Setters 4.问题解决4.1 修改参数名称4.2 添加Getter和Setter方法 1.问题说明 application.yml配置文件里的布尔值获取后一直为fa…

MapReduce【自定义OutputFormat】

MapReduce默认的输出格式为TextOutputFormat&#xff0c;它的父类是FileOutputFormat&#xff0c;即按行来写&#xff0c;且内容写到一个文本文件中去,但是并不能满足我们实际开发中的所有需求&#xff0c;所以就需要我们自定义OutPutFormat。 自定义OutPutFormat 输出数据到…

Java 与排序算法(4):希尔排序

一、希尔排序 希尔排序&#xff08;Shell Sort&#xff09;是插入排序的改进版&#xff0c;由 Donald Shell 在 1959 年提出。希尔排序通过将待排序序列分成多个子序列&#xff0c;分别进行插入排序&#xff0c;最后再进行一次整体的插入排序&#xff0c;从而提高了排序效率。…

对于 CRC 校验的 学习笔记

参考资料 CRC校验原理及实现 - 知乎 (zhihu.com) <-- 这个讲的特别好&#xff0c;我的博客主要是抄他的&#xff0c;最后加了一点代码库的分析。 [CRC校验]手算与直观演示_哔哩哔哩_bilibili <-- 这个视频非常直观 【FPGA】CRC校验算法从数学原理到代码实现 CRC 参数…

一体集成的 API 调试工具,居然才听说?

在 Eolink ApiKit之前&#xff0c;定义 API 用 Swagger&#xff0c;生成文档用 YAPI&#xff0c;前端自测用 Mock&#xff0c;接口测试用 Postman&#xff0c;性能测试用 JMeter。 有了 Eolink ApiKit之 之后&#xff0c;Apikit Postman Swagger Mock JMeter&#xff0c;团…

办公室想装修?玻璃隔断让你的办公区域成为艺术品!

玻璃隔断是现代办公室装修中非常受欢迎的设计元素。它们不仅可以实现空间区分&#xff0c;还能为办公区带来现代感和艺术气息。 玻璃隔断的优点 1. 明亮&#xff1a;玻璃隔断可以让自然光线进入整个房间&#xff0c;使空间变得更加明亮&#xff0c;有益于工作效率和员工的情绪健…

Apikit 超强的接口管理神器

接口管理现状 一、常用解决方案 使用 Swagger 作为接口文档工具 使用 Postman 调试接口 使用 RAP Mock 数据 使用 JMeter 做接口自动化测试 二、存在的问题 维护不同工具之间数据一致性非常困难、非常低效。并且这里不仅仅是工作量的问题&#xff0c;更大的问题是多个系…

理解和使用pom.xml 及在idea中具体如何查看依赖冲突情况

pom基本配置说明&#xff0c;直接在下面菜鸟教程中就可以看到 Maven POM | 菜鸟教程 一、关闭父依赖约束 去掉这个模块&#xff0c;依赖约束就会失效&#xff0c;这样每个包之间的依赖冲突的可能性非常大。 <parent><groupId>org.springframework.boot</gr…

期末复习总结!!【MySQL】库和表的基本操作 + 增删改查CURD

文章目录 前言一、数据库的基本操作1, 查看库2, 创建库3, 使用库4, 删除库 二、表的基本操作1, 创建表2, 查看表3, 查看表结构4, 删除表 三、增加(Create)四、查询(Retrieve) (重点)1, 全列查询2, 指定列查询3, 查询字段为表达式4, 指定别名5, 去重6, 排序7, 条件查询7.1, 基本…

【考前熟悉】系统集成项目管理师-相关计算公式

前言 计算公式汇总&#xff1a;三点估算PERT、标准差、工作概率、预期收益EMV、加权算法、沟通渠道 净现值、进度网络、挣值分析、预测技术 文章目录 前言计算公式汇总1. 期望工期&#xff08;活动持续时间&#xff09;/三点估算PERT&#xff1a;&#xff08;最悲观日期 最乐观…

linux下安装vsftpd

安装vsftpd&#xff1a; 使用命令&#xff1a; apt-get install vsftpd 安装完后查看ftp服务的状态&#xff1a;service vsftpd status 或者 systemctl status vsftpd.service 开机启动ftp服务&#xff1a;systemctl enable vsftpd.service 配置vsftpd.conf 关于配置文件中…