最近問題有點多,這不,系統中的一個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, boolean, boolean)
- org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback, 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系統上添加一個清緩存的方法,來配合我們的測試~
所以,還是儘量讓系統具有相同的序列化方式吧,要不然只能去服務器上刪除緩存了。。。