Redis的Key为什么总是会乱码?

326 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

你们遇到过Redis中存储的key是乱码的情况么?

image.png

image.png

以'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中会产生几条数据呢?

image.png

竟然出现了两个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的开发过程中,尽量确保操作对象设置了正确的序列化方式。