持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
你们遇到过Redis中存储的key是乱码的情况么?
以'userId:10'这个key为例,为什么我代码中定义的明明是userId:10,而在Redis的控制台查询到的key确是"\xac\xed\x00\x05t\x00\tuserId:10"。
明明是两个完全不同的key么!
那么对于Redis来说,是不是两个key呢?我们继续往下看
写个demo,来验证下我们的想法
// 创建测试乱码对象
@Component
public class RedisTestLM{
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate redisTemplate;
public void set1(){
redisTemplate.opsForValue().set("userId:20","test");
}
public void set2(){
stringRedisTemplate.opsForValue().set("userId:20","test");
}
}
// 定义执行方法
private void testLM(){
redisTestLM.set1();
redisTestLM.set2();
}
首先我们使用不同的Redis操作模板对象分别去添加同一个key为"userId:20"的数据。两种方式执行后,在Redis中会产生几条数据呢?
竟然出现了两个key,一个是正常的字符串,一个是乱码的。由此给上面的疑问给出结论,两种方式得到的key就是完全不同的key。
那么,两种方式操作同一个key为啥会在Redis中出现两种完全不一样的key呢?
大家应该都明白,我们项目要操作Redis,自然就需要数据进行网络传输,既然要进行数据的网络传输,那么序列化方式的选择就尤为重要了。
显而易见,造成上述问题的很大可能就是因为序列化方式的不同,我们翻看源码去验证下。
RedisTemplate
篇幅有限,只截取相关代码
既然是key的序列化方式不同造成的结果,我们就首先看看key的序列化对象是怎么初始化的
f (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
可以看到,如果没有主动设置keySerializer,会给他默认赋值defaultSerializer对象,那么defaultSerializer对象到底是个啥呢?
// 创建JdkSerializationRedisSerializer对象并赋值
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
// 通过构造方法设置真正的序列化和反序列化对象
public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
this(new SerializingConverter(), new DeserializingConverter(classLoader));
}
public SerializingConverter() {
this.serializer = new DefaultSerializer();
}
// 真正序列化的方法
public void serialize(Object object, OutputStream outputStream) throws IOException {
if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires a Serializable payload but received an object of type [" + object.getClass().getName() + "]");
} else {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);
objectOutputStream.flush();
}
}
通过源码可以看到,defaultSerializer这个对象实际上调用的序列化方法是一个对象的序列化方式,用于字符串的序列化自然就会有编码的问题。
StringRedisTemplate
篇幅有限,只截取相关代码
我们再来看看StringRedisTemplate的源码,发现构造方法里直接就设置了相关属性的序列化
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}
再来看看设置的序列化方法是怎么序列化的
static RedisSerializer<String> string() {
return StringRedisSerializer.UTF_8;
}
public byte[] serialize(@Nullable String string) {
return string == null ? null : string.getBytes(this.charset);
}
通过源码可以看到,这个序列化方法先是设置字符编码,然后直接将字符串转换成byte数组,最后就得到了正确的redis的key。
结语
经过上述分析,我们可以发现在对Redis操作的时候,key序列化方式的选择很重要。
所以在今后对于redis的开发过程中,尽量确保操作对象设置了正确的序列化方式。