如何批量获取redis数据呢?

背景

redis作为我们接口开发中的首选缓存,当从redis中每次获取一个key的value时,接口响应时间上一般是没啥问题的,但是当需要获取多个key的value时,一般需要使用mget等批量获取的方法。但是当value是以压缩格式写入到redis中的时候,还可以直接套用原来的方法吗?最近在一个接口的开发过程中遇到了这个问题,特此记录下解决方法。

先复习下redis、jedis和redisTemplate之间的关系:Redis是用ANSI C写的一个基于内存的Key-Value数据库,而Jedis是Redis官方推出的面向Java的Client,提供了很多接口和方法,可以让Java操作使用Redis,而Spring Data Redis是对Jedis进行了封装,集成了Jedis的一些命令和方法,可以与Spring整合。(https://blog.csdn.net/ystyaoshengting/article/details/85266322)。

问题及解决方案

我们的这个接口是模型相关的,接口的主要处理逻辑是拼接出特征,将特征输入到模型,得到模型的结果后返回。其中拼接特征阶段的主要工作就是从redis中取出所有的特征。redis有集群也有单例,保存了各个维度的特征,因为数据量较大、特征稀疏、redis容量限制等原因,数据在写入到redis中的时候使用SNAPPY格式压缩的。redis中的测试数据信息如下:

在接口逻辑中,从redis单例中获取数据时,为减少redis client与server建立连接的次数,使用批量的方式获取数据,最开始使用的是类似如下的代码:

List<String> keysList = new ArrayList<>();
keysList.add("key1");
keysList.add("key2");
keysList.add("key3");
keysList.add("key4");
//批量获取value
List<String> valuesList = redisTemplate.opsForValue().multiGet(keysList);



//解压缩方法
String valueFinal = Snappy.uncompressString(valueTemp.getBytes());

到这一步得到的是压缩后的value,但是在后面的解压过程中发现只有ad前缀的value可以正常解压,而adde前缀的value在解压过程中报错。查看了这两类key在redis中的编码方式,发现一个是raw一个是embstr,以为是这个原因造成的(当然这个想法后面被证实了是错误的),但是怎么解决呢?没有什么思路。。。接着查看了下multiGet()方法的源码,

public List<V> multiGet(Collection<K> keys) {
        if (keys.isEmpty()) {
            return Collections.emptyList();
        } else {
            byte[][] rawKeys = new byte[keys.size()][];
            int counter = 0;

            Object hashKey;
            for(Iterator var4 = keys.iterator(); var4.hasNext(); rawKeys[counter++] = this.rawKey(hashKey)) {
                hashKey = var4.next();
            }

            List<byte[]> rawValues = (List)this.execute((connection) -> {
                return connection.mGet(rawKeys);
            }, true);
            return this.deserializeValues(rawValues);
        }
    }

可以看到这里是将多个key转换成二维字节数组,发送到redis server端后得到的结果是byte[]的list,然后对这个list进行了反序列化。(这里对key和value的我都设置的是StringRedisSerializer。)看到 这里就想着可不可以仿照着这个方法,自己构建rawKeys,然后调用execute方法得到rawValues,然后对rawValues中的元素在进行解压缩呢,这样的话就可以跳过这个反序列化的过程。于是实验了一下:

List<String> redisKeys = new ArrayList<>();
redisKeys.add("key1");
redisKeys.add("key2");
		
byte[][] rawKeys = new byte[redisKeys.size()][];
for (int i = 0; i < redisKeys.size(); i++) {
	rawKeys[i] = redisKeys.get(i).getBytes();
}
List<byte[]> tempRedisValues = redisTemplate.execute(redisConnection -> {
	return redisConnection.mGet(rawKeys);
}, true);

这样仿照下来竟然是OK的,问题算是解决了,但是为什么反序列化一下就出现解压缩失败的问题了呢?还得再研究一下。。。

学习探究

于是本地用jedis连接测试环境redis,打印value的字节流:

adde前缀的key的value未压缩的值:
2.0,2.0,2.0,2.0,2.0,0.0,0.0,0.0,0.0,0.0,91728.0,91728.0,91728.0,91728.0,0,715.0,715.0,715.0,715.0,0,7.0,7.0,7.0,7.0,7.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.007794784580498866,0.007794784580498866,0.007794784580498866,0.007794784580498866,0.0,0.0,0.0,0.0,0.0,0.0

adde前缀的key的value压缩前的字节流:
50 46 48 44 50 46 48 44 50 46 48 44 50 46 48 44 50 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 57 49 55 50 56 46 48 44 57 49 55 50 56 46 48 44 57 49 55 50 56 46 48 44 57 49 55 50 56 46 48 44 48 44 55 49 53 46 48 44 55 49 53 46 48 44 55 49 53 46 48 44 55 49 53 46 48 44 48 44 55 46 48 44 55 46 48 44 55 46 48 44 55 46 48 44 55 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 44 48 46 48 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 44 48 46 48 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 44 48 46 48 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 44 48 46 48 

adde前缀的key的value压缩后的字节流:
-1 2 12 50 46 48 44 62 4 0 8 48 46 48 66 4 0 24 57 49 55 50 56 46 48 98 8 0 20 48 44 55 49 53 46 78 6 0 8 48 44 55 1 24 58 4 0 78 100 0 -110 20 0 -106 38 0 74 -62 0 -78 58 0 64 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 1 119 -6 21 0 1 67 62 4 0 

adde前缀的key的value反序列化之后得到的字符串打印后的显示情况:
�2.0,>0.0B91728.00,715.N0,7:Nd��&J��:@07794784580498866w�C>

adde前缀的key的value反序列化之后得到的字符串的字节流:
-17 -65 -67 2 12 50 46 48 44 62 4 0 8 48 46 48 66 4 0 24 57 49 55 50 56 46 48 98 8 0 20 48 44 55 49 53 46 78 6 0 8 48 44 55 1 24 58 4 0 78 100 0 -17 -65 -67 20 0 -17 -65 -67 38 0 74 -17 -65 -67 0 -17 -65 -67 58 0 64 48 55 55 57 52 55 56 52 53 56 48 52 57 56 56 54 54 1 119 -17 -65 -67 21 0 1 67 62 4 0 


---------------------------


ad前缀的key的value未压缩的值:
4,3,5,2,1,74,71,0,0,0,0,0,0,0,0,0.0

adde前缀的key的value压缩前的字节流:
52 44 51 44 53 44 50 44 49 44 55 52 44 55 49 44 48 44 48 44 48 44 48 44 48 44 48 44 48 44 48 44 48 46 48 

ad前缀的key的value反序列化之后得到的字符串打印后的显示情况:
#@4,3,5,2,1,74,71,0>.0

ad前缀的key的value反序列化之后得到的字符串的字节流:
35 64 52 44 51 44 53 44 50 44 49 44 55 52 44 55 49 44 48 62 2 0 4 46 48 

adde前缀的key的value压缩后的字节流:
35 64 52 44 51 44 53 44 50 44 49 44 55 52 44 55 49 44 48 62 2 0 4 46 48 


通过对比字节流发现adde前缀的value在将redis server端传过来的byte[]反序列化之后再通过getBytes()方法获取字节流,这个过程中字节流发生了变化,导致了解压缩的失败。

按理来说字节数组变为String过程中使用的UTF-8编码,对字符串调用getBytes()方法获取字节数组的过程也是用的UTF-8编码,但是为什么adde前缀的key的value的字节数据就发生了变化呢?对比了下,发现应该是压缩后的字节数组中出现了负数导致的。redis客户端上显示adde前缀的value是:\xff\x02\x0c2.0,>\x04\x00\b0.0B.....,而其字节流是-1 2 12 50 46 48 44 62 4 0 8 48.....。继续对比发现是反序列化过程中将所有的负数都替换成了-17 -65 -67。查看源码发现这里确实有个替换的可能:

protected CharsetDecoder(Charset cs,
                             float averageCharsPerByte,
                             float maxCharsPerByte)
    {
        this(cs,
             averageCharsPerByte, maxCharsPerByte,
             "\uFFFD");
    }

但是具体是怎么对上的呢?目前还看不太懂,就先不求甚解吧[捂脸]。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值