redis-Pipeline(管道)

今天遇到一个业务问题,有百万条数据需要处理,处理过程中需要对每条数据进行校验,与本地表中数据查看是否匹配(存在),如果每次请求DB的话给数据库带来的压力不言而喻;

也曾考虑过在需要的时候将数据分批查到一个List中,但是数据量太大,加上可能多个业务场景用到,每次取数的耗时包括占用的JVM空间也pass掉了这种方式;

再者就是采用缓存方式,一来也不会对DB造成压力,二来也方便查询。

但是如何将这一千万数据存入缓存?循环百万次插入是不可能的,肯定有更好的方式。

redis-Pipeline(管道)

简单讲 Redis-Pipeline是Redis客户端的一个高级功能,它可以在一次网络往返中发送多个命令。

这个功能可以提高Redis客户端与Redis服务器之间的性能,因为通过一次网络往返发送多个命令可以减少网络延迟时间,并且可以减少Redis服务器的CPU负载。

直接上代码:

/**
 * 普通方式进行数据刷入
 */
public void ordinaryRedis() {
    //分批取数据
    Set<String> dmDevLabel = batchSelectBranch();
    long sl = System.currentTimeMillis();
    System.out.println("删除之前缓存");
    redisUtil.del("iot_Set_BranchDevLabel_ordinary");
    //数据同步刷入缓存
    dmDevLabel.parallelStream().forEach(devLabel ->{
        redisUtil.sSet("iot_Set_BranchDevLabel_ordinary", devLabel);
    });
    long el = System.currentTimeMillis();
    System.out.println("刷入缓存用时:"+ (sl - el)/1000 +" s");
    //判断数据在缓存中是否存在 不存在 说明与DM中数据不匹配 删除(不纳入计算)
    long redisSize = redisUtil.sGetSetSize("iot_Set_BranchDevLabel_ordinary");
    if (redisSize > 0) {
        boolean isExist = redisUtil.sIsMember("iot_Set_BranchDevLabel_ordinary", "T23131D012012021010504976c2fcbc9e6e8805b36000036915460");
        System.out.println(isExist);
    }
}

采用管道刷入数据:

/**
 * 管道方式分批进行数据刷入
 */
public void pipelineRedis() {
    //分批取数据
    Set<String> dmDevLabel = batchSelectBranch();
    long sl = System.currentTimeMillis();
    System.out.println("删除之前缓存");
    redisUtil.del("iot_Set_BranchDevLabel");
    System.out.println("开始刷入缓存");
    Set<Set<String>> setPipe = splitSet(dmDevLabel, 100);
    for (Set<String> set : setPipe) {
        Boolean isPipe = redisUtil.executePipelinedBySet(set, "Set_BranchDevLabel", 0);
        if (!isPipe) {
            redisUtil.executePipelinedBySet(set, "Set_BranchDevLabel", 0);
        }
    }
    long el = System.currentTimeMillis();
    System.out.println("刷入缓存用时:"+ (el - sl)/1000 +" s");
    //判断数据在缓存中是否存在
    long redisSize = redisUtil.sGetSetSize("Set_BranchDevLabel");
    if (redisSize > 0) {
        boolean b = redisUtil.sHasKey("Set_BranchDevLabel", "66666666666666666666666");
        System.out.println(b);
    }
}




//这里也可以采用第二种方式,在循环总量数据时候根据fori循环下标进行分批。
//Set<String> dmDevLabelSet = new HashSet<>();
//for (int i = 0; i < courtsBranchDetails.size(); i++) {
//    IotCourtsBranchDetails BranchDetail = courtsBranchDetails.get(i);
//    String esnLabel = BranchDetail.getEsn() + "_" + BranchDetail.getDevLabel();
//    if (i == 0) {
//        dmDevLabelSet.add(esnLabel);
//    } else if (i % 100000 == 0 || i == courtsBranchDetails.size()) {
//        dmDevLabelSet.add(esnLabel);
//        //不设置缓存过期时间
//        Boolean isPipe = redisUtil.executePipelinedBySet(dmDevLabelSet, "iot_Set_BranchDevLabel", 0);
//        if (!isPipe) {
//            //如果某批刷入失败重试一次
//            redisUtil.executePipelinedBySet(dmDevLabelSet, "iot_Set_BranchDevLabel", 0);
//            WriteTxtUtil.writeTxtLog("数据批量刷入缓存", "异常时间:\t" + DateUtil.getTime() + "失败批次:\t" + i);
//        }
//        dmDevLabelSet = new HashSet<>();
//    } else {
//        dmDevLabelSet.add(esnLabel);
//    }
//}




/**
 * 将一个 Set<String> 均分成多个 Set<String>
 *
 * @param originalSet 原始 Set<String>
 * @param subsetSize  每个子集的大小
 * @return 均分后的多个 Set<String>
 */
public static Set<Set<String>> splitSet(Set<String> originalSet, int subsetSize) {
    Set<Set<String>> subsets = new HashSet<>();
    int count = 0;
    Set<String> subset = new HashSet<>();
    for (String element : originalSet) {
        subset.add(element);
        count++;
        if (count == subsetSize) {
            subsets.add(subset);
            subset = new HashSet<>();
            count = 0;
        }
    }
    if (count > 0) {
        subsets.add(subset);
    }
    return subsets;
}

redis管道封装方法:

/**
 * 功能描述: 使用pipelined批量存储
 * redis 类型Set
 * 如果返回true,则表示刷入成功;如果返回false,则表示刷入失败。
 *
 * @auther: jiangfy
 */
public Boolean executePipelinedBySet(Set<String> set, String key, long seconds) {
        RedisSerializer<String> serializer = redisTemplate.getValueSerializer();
    RedisSerializer<String> keySerializer = redisTemplate.getKeySerializer();
    List<Object> resultList = redisTemplate.executePipelined(new RedisCallback<String>() {
        @Override
        public String doInRedis(RedisConnection connection) throws DataAccessException {
            set.forEach((value) -> {
                connection.sAdd(keySerializer.serialize(key), serializer.serialize(value));
            });
            if (seconds > 0) {
                connection.expire(keySerializer.serialize(key), seconds);
            }
            return null;
        }
    }, serializer);
    return resultList != null && resultList.size() > 0;
}

需要注意的是,最开始使用的 redisTemplate.getStringSerializer() 的字符串序列化器 导致数据虽然插入了但是不能正常的获取,排查了好久问题,redis中有就是取不到,后来调整了对应的序列化器后正常;

了解下这几个序列化器吧:

这三个都是 RedisTemplate 中的序列化器,用于将数据序列化为字节数组存储到 Redis 中,或将从 Redis 中获取的字节数组反序列化为对应的数据类型。

  • getValueSerializer():用于序列化对象值的序列化器。
  • getKeySerializer():用于序列化键的序列化器。
  • getStringSerializer():用于序列化字符串的序列化器。

它们的区别在于序列化的对象类型不同。getValueSerializer() 序列化的是 Redis 中存储的对象值,getKeySerializer() 序列化的是 Redis 中存储的键名,而 getStringSerializer() 则序列化的是字符串类型的值。

在实际使用中,需要根据存储的数据类型来选择合适的序列化器,否则可能会出现数据类型转换错误等问题。例如,当存储的值为字符串类型时,应该使用 getStringSerializer() 序列化器。当存储的值为 Java 对象时,可以使用默认的 JdkSerializationRedisSerializer,或根据需要选择其他的序列化器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值