Redis缓存删不掉?查看你的Key序列化方式吧

最近问题有点多,这不,系统中的一个Redis缓存删不掉了。。。。

问题

我们的应用程序中,存在一个发Queue清缓存的功能。具体实现就是将要删除的Key作为Queue的内容传入,然后调用方法来清除Redis缓存。这个功能是在A系统上完成的。

但问题是缓存并不是在A系统上添加的。为了完成某些测试,需要将B系统上添加的一个Redis缓存删掉,尝试了N次,发送了N个Queue删除,系统无任何报错,但是缓存一直都没有删除掉。

由于无法删除缓存,也不能直接连接Redis删除,我们不得不做了复杂的“迂回操作”,浪费了大量时间。

原因

后来闲下来了,排查问题发现,原来是B系统的Redis key序列化方式与A系统不一致,导致缓存删除失败。

A系统的序列化方式配置成了StringRedisSerializer,而B系统没有指定,使用了默认的JdkSerializationRedisSerializer。

相关源码解析如下

源码解释

1. 设缓存

当我们调用redisTemplate的set方法时,

redisTemplate.opsForValue().set(key, value);

会调用DefaultValueOperations的set方法

@Override
public void set(K key, V value) {
    byte[] rawValue = rawValue(value);
    execute(new ValueDeserializingRedisCallback(key) {
        @Override
        protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
            connection.set(rawKey, rawValue);
            return null;
        }
    }, true);
}

在该方法里,会回调执行ValueDeserializingRedisCallback方法。

具体调用链路如下

  • org.springframework.data.redis.core.AbstractOperations.ValueDeserializingRedisCallback#doInRedis
  • org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback<T>, boolean, boolean)
  • org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback<T>, boolean)
  • org.springframework.data.redis.core.AbstractOperations#execute
  • org.springframework.data.redis.core.DefaultValueOperations#set(K, V)

最终会调用回调方法的doInRedis方法。

public final V doInRedis(RedisConnection connection) {
    byte[] result = inRedis(rawKey(key), connection);
    return deserializeValue(result);
}

在inRedis时,会进行rawKey操作,将key转换为byte数组

byte[] rawKey(Object key) {
    Assert.notNull(key, "non null key required");
    if (keySerializer() == null && key instanceof byte[]) {
        return (byte[]) key;
    }
    return keySerializer().serialize(key);
}

在rawKey方法中,可以看到,会调用Redis的keySerializer来进行key值的serialize。

2. 清缓存

当我们调用redisTemplate的delete方法来删缓存时,

redisTemplate.delete(key);

会调用RedisTemplate的delete方法

@Override
public Boolean delete(K key) {
    byte[] rawKey = rawKey(key);
    Long result = execute(connection -> connection.del(rawKey), true);
    return result != null && result.intValue() == 1;
}

看到第一行的rawKey,我们就知道,此处的key也是需要序列化的。

private byte[] rawKey(Object key) {
    Assert.notNull(key, "non null key required");
    if (keySerializer == null && key instanceof byte[]) {
        return (byte[]) key;
    }
    return keySerializer.serialize(key);
}

可以看到,清缓存操作也是跟serializer相关联的。

3. 总结

这就不难明白,为什么A系统清不了B系统的缓存了。根本原因还是它们使用的是不同的serializer。

那么,如果如果不赋值,默认使用的是什么serializer呢?

答案是JdkSerializationRedisSerializer。

Spring在bean实例化完成前,会调用RedisTemplate的afterPropertiesSet方法

public void afterPropertiesSet() {
        super.afterPropertiesSet();
        boolean defaultUsed = false;
        // 如果没有配置defaultSerializer,则默认使用JdkSerializationRedisSerializer
        if (defaultSerializer == null) {
            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null ? classLoader : this.getClass().getClassLoader());
        }
        if (enableDefaultSerializer) {
            if (keySerializer == null) {
                keySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (valueSerializer == null) {
                valueSerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashKeySerializer == null) {
                hashKeySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashValueSerializer == null) {
                hashValueSerializer = defaultSerializer;
                defaultUsed = true;
            }
        }
        if (enableDefaultSerializer && defaultUsed) {
            Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
        }
        if (scriptExecutor == null) {
            this.scriptExecutor = new DefaultScriptExecutor<>(this);
        }
        initialized = true;
    }

如果没有配置defaultSerializer,则默认使用JdkSerializationRedisSerializer

所以如果下次遇到Redis缓存删不掉的问题,看看Key的序列化方式吧~

解决方法

由于A、B系统的序列化方式不同,如果随意修改,上线后会影响产线。因此我们不得不临时在B系统上添加一个清缓存的方法,来配合我们的测试~

所以,还是尽量让系统具有相同的序列化方式吧,要不然只能去服务器上删除缓存了。。。