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, 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系統上添加一個清緩存的方法,來配合我們的測試~

所以,還是儘量讓系統具有相同的序列化方式吧,要不然只能去服務器上刪除緩存了。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章