如何批量获取redis数据呢?

本文探讨了在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.youkuaiyun.com/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");
    }

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

<think> 我们正在讨论Python向Redis存储数据的方法。根据提供的引用,我们可以总结出几种常见的方法: 引用[2]和[3]提到了使用`set`方法存储键值对,这是最基本的方法。 引用[4]提到了使用Redis的时间序列模块(RedisTimeSeries)来存储时序数据,但这需要Redis模块支持。 引用[5]展示了存储和获取数据时需要注意的编码问题。 常见的方法包括: 1. 使用`set`方法存储单个键值对。 2. 使用`mset`方法存储多个键值对。 3. 使用其他数据结构,如列表、集合、有序集合、哈希等,Redis提供了相应的命令(如`lpush`, `hset`等)。 4. 如果需要存储时间序列数据,可以使用RedisTimeSeries模块(如果已安装)。 下面我们分别给出示例: 首先,确保已经安装`redis`库: ```bash pip install redis ``` 然后,连接Redis数据库(引用[3]和[4]): ```python import redis # 创建连接,根据实际情况填写参数 r = redis.Redis(host='localhost', port=6379, db=0, password='your_password') ``` 方法1:使用`set`存储单个键值对 ```python r.set('name', 'wusuowei') # 存储字符串 r.set('age', 18) # 注意:Redis会自动将数字转为字符串存储,但取出来时是字符串 ``` 方法2:使用`mset`存储多个键值对 ```python r.mset({'key1': 'value1', 'key2': 'value2'}) ``` 方法3:使用哈希数据结构存储对象 ```python r.hset('user:1000', mapping={'name': 'Alice', 'age': 25}) ``` 方法4:存储列表 ```python r.rpush('mylist', 'a', 'b', 'c') # 在列表右侧插入元素 ``` 方法5:存储集合 ```python r.sadd('myset', 'a', 'b', 'c') ``` 方法6:使用时间序列(需要Redis服务器加载了timeseries模块) 首先确保Redis服务器已加载TimeSeries模块,然后使用redis-py的相应方法(需要安装redistimeseries库?)或者使用命令: ```python # 假设已经安装了redistimeseries库(注意:这不是官方库,这里仅示意) # 通常,我们可以使用r.execute_command()来执行时间序列命令 # 例如创建一个时间序列 r.execute_command('TS.CREATE', 'temperature', 'RETENTION', '60000') # 添加数据点 r.execute_command('TS.ADD', 'temperature', '*', 25.3) ``` 注意:时间序列的使用较为复杂,且需要Redis模块支持,如果只是存储普通数据,前几种方法更常用。 另外,引用[5]提到了编码问题,当我们获取数据时,Redis返回的是字节串(bytes),通常需要解码为字符串: ```python name = r.get('name').decode('utf-8') # 如果存储的是字符串 ``` 但是,如果存储的是数字,可能需要转换类型: ```python age = int(r.get('age')) ``` 或者,在连接时指定`decode_responses=True`,这样返回的就是字符串: ```python r = redis.Redis(host='localhost', port=6379, db=0, password='your_password', decode_responses=True) ``` 这样,使用`get`方法返回的就是字符串,对于数字,我们仍然需要转换。 总结:Python向Redis存储数据的基本方法是通过`set`、`mset`以及各种数据结构的对应命令(如`hset`、`rpush`等)。存储时注意数据类型,获取时注意解码。 根据上述内容,我们生成相关问题: §§相关问题§§ 1. 如何从Redis获取存储的数据? 2. 如何使用Python操作Redis的哈希表结构? 3. 如何验证数据是否成功存储到Redis? 4. Python操作Redis时如何处理编码问题? 5. Redis支持哪些数据结构?Python中如何操作这些数据结构? 6. 如何存储和读取时间序列数据Redis? </think> ### Python向Redis存储数据的方法 根据引用内容,以下是Python向Redis存储数据的核心方法(使用`redis-py`库): #### 1. **基本键值对存储**(引用[2][3]) ```python import redis # 连接Redis(默认参数可省略) r = redis.Redis( host='localhost', port=6379, db=0, password=None # 如有密码需填写 ) # 存储数据(自动序列化) r.set('name', 'wusuowei') # 存储字符串 r.set('age', 18) # 存储整数 r.set('user:1001', '{"email": "test@example.com"}') # 存储JSON ``` #### 2. **批量存储多个键值对**(引用[5]) ```python # 使用mset存储字典数据 data = { 'key1': 'value1', 'key2': 'value2', 'temperature': 25.5 } r.mset(data) ``` #### 3. **哈希表存储结构化数据**(引用[5]) ```python # 存储用户信息(哈希类型) r.hset('user:1001', mapping={ 'name': '张三', 'age': 28, 'email': 'zhangsan@example.com' }) ``` #### 4. **时间序列数据存储**(需RedisTimeSeries模块,引用[4]) ```python # 检查是否支持时间序列模块 if 'timeseries' in r.info()['modules'][0]['name']: # 存储时间序列数据 r.execute_command('TS.ADD', 'temperature', '*', 36.5) # *表示当前时间戳 else: print("RedisTimeSeries模块未启用") ``` #### 5. **数据存储注意事项** - **编码处理**:读取时需解码`bytes`类型(引用[5]) ```python name = r.get('name').decode('utf-8') # 字节转字符串 ``` - **清空数据**:谨慎使用`flushall`(引用[2]) ```python r.flushall() # 清空所有数据库 ``` ### 关键步骤总结 1. 安装库:`pip install redis` 2. 建立连接:指定主机、端口、数据库 3. 选择存储方式: - 简单键值:`set()` - 批量操作:`mset()` - 结构化数据:`hset()` - 时序数据RedisTimeSeries扩展 4. 验证存储: ```python print(r.get('name')) # 读取验证 ``` > **最佳实践**:对于结构化数据,优先使用哈希类型(`hset`);对于数值型监控数据,建议启用RedisTimeSeries模块[^2][^4]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值