本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Springboot下使用Redis管道(pipeline)进行批量操作
mylove136 · 165浏览 · 发布于2023-05-15 +关注

本文主要介绍了Spring boot 下使用Redis管道(pipeline)进行批量操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

之前有业务场景需要批量插入数据到Redis中,做的过程中也有一些感悟,因此记录下来,以防忘记。下面的内容会涉及到

  • 分别使用for、管道处理批量操作,比较其所花费时间。

  • 分别使用RedisCallback、SessionCallback进行Redis pipeline 操作

  • 解释RedisCallback、SessionCallback这两种用法的区别

管道(pipeline)的优势

以下内容结合了redis官方文档,总结出自己的想法。

Redis Pipeline官网地址:https://redis.io/docs/manual/pipelining/

1.网络传输(RTT)开销少

Redis的传输层是基于TCP协议,一次操作请求的完成,存在网络传输来回的开销,即使Redis每秒能接受10万的请求,但也会因为网络传输而浪费很多时间,导致降低整体的性能。所以面对大量的批量处理,可以使用Redis的管道(pipeline),优势在于多次指令操作只会使用一次的网络传输的开销。

PS:像批量插入、批量获取,RedisTemplate提供了multiSet、multiGet的方法可以进行操作,不过像multiSet并不支持批量设置key的过期时间,可以考虑业务场景进行使用

2.提高redis每秒可以执行操作的数量

在进行批量操作的前提下

不使用管道的时候,每一次Redis执行命令,都要涉及到读(read)和写(write)的系统调用,系统会将用户端切换到内核端。上下文切换会有一定的消耗使用管道的话,多条命令只需要一个读(read),多条响应只需要一个写(write),可想而知,这其中省下了很多的消耗。

环境配置

  • JDK8

  • Spring boot 2.6.13

  • spring-boot-starter-data-redis

分别使用for、管道处理批量操作,比较其所花费时间

public void testForOrPipeline(){
     //使用for
    StopWatch stopWatch1=new StopWatch();
    stopWatch1.start();
    for(int i=0;i<10000;i++){
        String value = String.valueOf(i);
        String key = "test:" + value;
        redisTemplate.opsForValue().set(key, value, 10, TimeUnit.SECONDS);
    }
    stopWatch1.stop();
    System.out.println("for所需时间:"+stopWatch1.getTotalTimeSeconds()+"s");
    //使用管道
    StopWatch stopWatch2=new StopWatch();
    stopWatch2.start();
    List<Boolean> list = redisTemplate.executePipelined(new SessionCallback<Object>() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            for (int i = 0; i < 10000; i++) {
                String value = String.valueOf(i);
                String key = "test:" + value;
                operations.opsForValue().set(key, value, 10, TimeUnit.SECONDS);
            }
            return null;
        }
    });
    stopWatch2.stop();
    System.out.println("管道所需时间:"+stopWatch2.getTotalTimeSeconds()+"s");
}
方法第一次第二次第三次第四次
for3.07s3.07s3.13s3.12s
pipeline0.55s0.63s0.60s0.68s

PS: 本地,且只有一个客户端的情况下测试(做不到严谨性,见谅)

目前只是本地跑(网络传输所带来的开销本身会很小),如果redis服务端是在其他地区的服务器上,这两种方式所需的时间相差还会越来越大。

分别使用RedisCallback、SessionCallback进行Redis pipeline 操作

RedisCallback

private void RedisCallBackHandler() {
    //这里获取String类型的序列化器
    RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
    //第二个参数是指定结果反序列化器,用于反序列化管道中读到的数据,不是必传,
    //如果不传,则使用自定义RedisTemplate的配置,
    //如果没有自定义,则使用RedisTemplate默认的配置(JDK反序列化)
    List list = redisTemplate.executePipelined(new RedisCallback<Object>() {
        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            for (int i = 0; i < 10; i++) {
                String value = String.valueOf(i);
                String key="test:"+value;
                connection.setEx(stringSerializer.serialize(key),10,stringSerializer.serialize
                (value));
            }
            //这里bytes只会获取到null,因为这里get操作只是放在管道里面,并没有
            //真正执行,所以获取不到值
            //byte[] bytes = connection.get("test:1".getBytes());
            connection.get("test:1".getBytes());
            //executePipelined 这个方法需要返回值为null,不然会抛异常,
            //这一点可以查看executePipelined源码
            return null;
        }
    }, stringSerializer);
    list.stream().forEach(result->{
        System.out.println(result);
    });
}

执行结果:

198dc1410c55bb13eacb2d1170e7baf9_2023051414575421.png

SessionCallback

private void SessionCallBackHandler() {
    //这里获取String类型的序列化
    RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
    //第二个参数是指定结果反序列化器,用于反序列化管道中读到的数据,不是必传,
    //如果不传,则使用自定义RedisTemplate的配置,
    //如果没有自定义,则使用RedisTemplate默认的配置(JDK反序列化)
    List list = redisTemplate.executePipelined(new SessionCallback<Object>() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            for (int i = 0; i < 10; i++) {
                String value = String.valueOf(i);
                String key = "test:" + value;
                operations.opsForValue().set(key, value, 10, TimeUnit.SECONDS);
            }
            //这里o只会获取到null,因为这里get操作只是放在管道里面,并没有真正执行,
            所以获取不到值
            //Object o = operations.opsForValue().get("test:1");
            operations.opsForValue().get("test:1");
            //executePipelined 这个方法需要返回值为null,不然会抛异常,
            //这一点可以查看executePipelined源码
            return null;
        }
    }, stringSerializer);
    list.stream().forEach(result->{
        System.out.println(result);
    });
}

执行结果:

3eb56c74790e306dcf4d1b41a73b15ce_2023051414575522.png

解释RedisCallback、SessionCallback这两种用法的区别

上面代码显示了RedisCallback、SessionCallback这两种都能实现相同的效果,那么这两个又有什么区别呢?

SessionCallback 的使用比RedisCallback要友好一些

SessionCallback的execute方法提供给使用者使用的是RedisOperations接口类,RedisTemplate实现类

RedisCallback的doInRedis方法提供给使用者使用的是RedisConnection接口类,也就是LettuceConnection是实现类

RedisConnection提供了字节数组类型的get和set方法,有关序列化部分的细节还需要我们去关心。(和使用原生jdbc感受差不多),而RedisTemplate负责序列化和连接管理,不需要让使用者关系这一块的部分。总结: 个人感觉从日常使用上应该都倾向于SessionCallback,而个别特殊有关底层的业务,可能就需要RedisCallback。


相关推荐

PHP实现部分字符隐藏

沙雕mars · 1325浏览 · 2019-04-28 09:47:56
Java中ArrayList和LinkedList区别

kenrry1992 · 908浏览 · 2019-05-08 21:14:54
Tomcat 下载及安装配置

manongba · 970浏览 · 2019-05-13 21:03:56
JAVA变量介绍

manongba · 963浏览 · 2019-05-13 21:05:52
什么是SpringBoot

iamitnan · 1086浏览 · 2019-05-14 22:20:36
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页