码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • RedisCluster如何高效率地批量插入数据


    RedisCluster如何高效率地批量插入数据

      • Redis环境
      • 入库方式
      • 测试代码
        • redis工具类
        • 测试接口
      • 测试结果

    Redis环境

    RedisCluster:

    三主三从,每个节点在单独的服务器上

    Spring的redisTemplate自带的Lettuce(6.2.1版本,至2022.10.22是最新版):

    spring:
      redis:
        password: xxx  #密码
        lettuce:  #lettuce连接池配置
          pool:
            max-active: -1
            max-idle: -1
            min-idle: 0
            max-wait: -1
          shutdown-timeout: 100
        cluster:  #集群配置
          nodes:
            - 1x2.xx.5.xx:6379
            - 1x2.xx.5.xx:6379
            - 1x2.xx.5.xx:6379
            - 1x2.xx.5.xx:6379
            - 1x2.xx.5.xx:6379
            - 1x2.xx.5.xx:6379
          max-redirects: 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    入库方式:

    使用ThreadPoolExecutor,本次测试结果为10线程,每线程插入10w条

    入库方式

    ① for循环执行set
    ② Pipline管道
    ③ multiSet()

    方式②是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能,解决了多个命令集中请求时造成网络资源浪费的问题,加快了 Redis 的响应速度,让 Redis 拥有更高的运行速度,其优势在于客户端与服务端的网络延迟越大,性能体能越明显,支持设置过期时间。

    方式③是RedisTemplate的方法,速度比Pipline要快很多很多,但是不支持设置过期时间。

    方式①是最原始的方式,效率很低,不推荐,这里也不进行测试了,大家可以自行测试。

    测试代码

    redis工具类

    package com.nwd.pressuretestutil.util;
    
    import com.topscomm.pressuretestutil.config.RedisConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.connection.RedisStringCommands;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.types.Expiration;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    import java.util.Map;
    
    @Component
    public class BatchRunRedisUtil {
        @Autowired
        RedisTemplate stringRedisTemplate;
    
        @Autowired
        RedisConfig redisConfig;
    
        //批量添加
        public void batchSet(Map map) {
            stringRedisTemplate.opsForValue().multiSet(map);
        }
    
        //批量添加 并且设置失效时间
        public void batchSetOrExpire(Map map, Long seconds) {
            RedisSerializer serializer = stringRedisTemplate.getStringSerializer();
            stringRedisTemplate.executePipelined(new RedisCallback() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    map.forEach((key, value) -> {
                        connection.set(serializer.serialize(key), serializer.serialize(value), Expiration.seconds(seconds), RedisStringCommands.SetOption.UPSERT);
                    });
                    return null;
                }
            }, serializer);
        }
    
        //批量获取
        public List batchGet(List list) {
            List objectList = stringRedisTemplate.opsForValue().multiGet(list);
            return objectList;
        }
    
        // Redis批量Delete
        public void batchDelete(List list) {
            stringRedisTemplate.delete(list);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    测试接口

    调用url:

    http://localhost:7001/redisOperation/redisBatchInsertByThread?insNum=100000&saveType=2&threadNum=10

    
    /**
      * 多线程入redis
       * insNum:每个线程的存入数量
       * saveType:插入方式,1代表multi,2代表pipline
       * */
      CountDownLatch latch = null;
      @GetMapping("/redisBatchInsertByThread")
      public String redisBatchInsertByThread(@RequestParam("insNum") Integer insNum,@RequestParam("saveType") Integer saveType,@RequestParam("threadNum") Integer threadNum){
          try {
              //定义线程池
              ThreadFactory tf = new CustomizableThreadFactory("Thread-");
              ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                      threadNum, threadNum, 60, TimeUnit.SECONDS,
                      new LinkedBlockingDeque(100000),
                      tf);
              //线程同步计数器
              latch =  new CountDownLatch(threadNum);
              long startTime = System.currentTimeMillis();
              for (int i=0;i {
                      Integer threadCount = Integer.parseInt(Thread.currentThread().getName().split("-")[1])-1;
                      String saveT = saveType==1?"multi":"pipline";
                      //multiSet()批量操作
                      long startTime1 = System.currentTimeMillis();
                      Map map = new HashMap(insNum);
                      for (int j = 0; j < insNum; j++) {
                          map.put(saveType+"_multi"+threadCount+":" + j, "b");
                      }
                      if(saveType==1){
                          batchRunRedisUtil.batchSet(map);
                      }else {
                          batchRunRedisUtil.batchSetOrExpire(map, 60L);
                      }
                      long endTime1 = System.currentTimeMillis();
                      System.out.println(saveT+"批量set消耗" + (endTime1 - startTime1) + "毫秒");
                      latch.countDown();
                  });
              }
              long endTime = System.currentTimeMillis();
              latch.await();
              System.out.println("共用时:"+ (endTime-startTime));
              return "ok";
          }catch (Exception e){
              e.printStackTrace();
              return "false:"+e.getMessage();
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    测试结果

    pipline:10线程;每条线程插入10w:

    pipline_Thread-5消耗14167毫秒
    pipline_Thread-6消耗14173毫秒
    pipline_Thread-9消耗14302毫秒
    pipline_Thread-8消耗14384毫秒
    pipline_Thread-7消耗14436毫秒
    pipline_Thread-10消耗14480毫秒
    pipline_Thread-3消耗14493毫秒
    pipline_Thread-1消耗14498毫秒
    pipline_Thread-2消耗14501毫秒
    pipline_Thread-4消耗14502毫秒
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    multi:10线程;每条线程插入10w:

    multi_Thread-1消耗5689毫秒
    multi_Thread-9消耗5918毫秒
    multi_Thread-2消耗5996毫秒
    multi_Thread-3消耗6038毫秒
    multi_Thread-6消耗6407毫秒
    multi_Thread-10消耗6899毫秒
    multi_Thread-5消耗6936毫秒
    multi_Thread-8消耗6936毫秒
    multi_Thread-7消耗6939毫秒
    multi_Thread-4消耗6967毫秒
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    由此可见,multi的效率比pipline快得多
    需要注意的是pipline在配置文件参数设置不是最优的情况下还会报连接异常
    如果业务场景无需设置超时时间,推荐使用multiSet
    如果必须要用pipline,压力也很大的话,可以考虑拆分成多个小pipline来执行。

  • 相关阅读:
    R语言dist函数计算dataframe数据中两两样本之间的距离返回样本间距离矩阵,通过method参数指定距离计算的方法、例如欧几里得距离
    设计模式之适配器模式
    LeetCode 面试题 05.03. 翻转数位
    关于.model.meta,.model.index,.model.data-00000-of-00001--学习笔记
    20 Python的time模块
    Spring源码:Bean生命周期(终章)
    会计制度设计试题及答案
    更智能!AIRIOT加速煤炭行业节能减排升级
    基于PHP的芒果销售交易平台
    【PowerQuery】Excel 一分钟以内刷新PowerQuery数据
  • 原文地址:https://blog.csdn.net/Funky_oaNiu/article/details/127459273
    • 最新文章
    • 【JVM】编译执行与解释执行的区别是什么?JVM 使用哪种方式?
      用 Hashids 优雅解决 C 端自增 ID 暴露问题
      V8引擎 精品漫游指南--Ignition篇(上) 指令 栈帧 槽位 调用约定 内存布局 基础内容
      LLVM Pass快速入门(四):代码插桩
      milkup:桌面端 markdown AI续写和即时渲染
      基于项目工程构建SBOM(软件物料清单)的研究
      鸿蒙应用开发UI基础第二节:鸿蒙应用程序框架核心解析与实操
      .NET 中如何快速实现 List 集合去重?
      扣子Coze实战:从0到1打造抖音+小红书热点监控智能体
      浅谈数据访问层
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    小工具 小游戏
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

    京公网安备 11010502049817号