Redisの分布式の鍵が出会う序列化の問題を詳しく解きます。
シーンの説明
最近はRedisを使って分布式のロックのようなシーンに遭遇しました。Redisと分布式のロックの類比を実現しました。ロックを解除するのに失敗しました。つまりキャッシュは削除できません。またRedisのピットを踏んでしまいました。
これはどのような状況ですか?
本文は主にこれに対して複盤を作る。
問題の調査
ロックを解除するのに問題があるなら、まずロックを解除するコードを見てみましょう。
ロックを解除する
リリースロックはLuaスクリプトを使用しています。コードロジックとLuaスクリプトは以下の通りです。
ロック例コードの解放
リリースが成功すれば、Redisキャッシュの削除に成功し、戻り値は1になります。失敗したら0に戻ります。
コードは一見問題ないようですが、試してみてください。
ロックを解除するなら、その前に必ずロックをかけて、ロックの論理を見てみましょう。
錠をかける
ここをロックするロジックといえば、コードの中には二つの実現方法があります。
コード例1
Q:ちょっと待ってください。なぜ二つのtemplateがありますか?
A:押して言いました。私が掘った穴です。RedisTemplateは私が加えたものです。今思い出しても、当初はなぜこのようにしたのか分かりませんでした。本当に頭が一時的にパンクしたかもしれません。
まずこの二つの方法をテストします。
テストします
それぞれロックをかけます。ここで、lock 01はk 1とv 1、lock 02はk 2とv 2です。
k 1、k 2の値をそれぞれ見てください。
v 1にはダブルクォーテーションがありますが、v 2にはありません。
推測は序列化されるべき問題です。Redisの配置を見てみますか?
RedisTemplateの設定
ロックはそこで見られますが、k 1はRedis Templateを使っています。k 2はStringRedisTemplateです。この二つの構成にはどんな違いがありますか?
このうちRedisTemplateの構成はカスタムであり、以下の通りである。
ポイントを入れてSteringRedis Templateを見てください。
RedisTemplateのkeyはStringRedis Serializerを使っています。valueはJackson 2 JsonRedis Serializerを使っています。
ここでは基本的に問題点に位置づけられます。つまりRedis TemplateのvalueプログレッシブとStringRedis Templateは一致しません。
もし同じに変更すればいいですか?検証してみます。
検証推論
RedisTemplateのvalueプログレッシブ方式をStringRedis Serializerに変更する:
v 1のダブルクォーテーションがなくなり、ロックを解除するサービスも正常に削除されました。
はい、ここの問題です。
両者のソースコードの序列化については、興味のあるたらい友達が研究を続けてもいいです。ここではもう深く検討しません。
結び目
本論文で出会ったこの問題は、主に異なるRedisTemplateを使用してロックとリリースロックをかけていますが、この二つのtemplateは異なる順序化方式を使用しています。最終的にはまだ順序化による問題です。
最初は本当に軽率でした。しかもまだ予測できませんでした。
生産環境については、慎重かつ慎重に:深淵に臨んで、薄氷を踏むようである。
この記事では、Redisの分散式のロックについての序文を紹介します。Redisの分散式のロックに関する問題の内容は以前の文章を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。
最近はRedisを使って分布式のロックのようなシーンに遭遇しました。Redisと分布式のロックの類比を実現しました。ロックを解除するのに失敗しました。つまりキャッシュは削除できません。またRedisのピットを踏んでしまいました。
これはどのような状況ですか?
本文は主にこれに対して複盤を作る。
問題の調査
ロックを解除するのに問題があるなら、まずロックを解除するコードを見てみましょう。
ロックを解除する
リリースロックはLuaスクリプトを使用しています。コードロジックとLuaスクリプトは以下の通りです。
ロック例コードの解放
public Object release(String key, String value) {
Object existedValue = stringRedisTemplate.opsForValue().get(key);
log.info("key:{}, value:{}, redis :{}", key, value, existedValue);
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(COMPARE_AND_DELETE, Long.class);
return stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
}
ロックを解除するためのLuaスクリプト
if redis.call('get',KEYS[1]) == ARGV[1]
then
return redis.call('del',KEYS[1])
else
return 0
end;
スクリプトを削除すると、先にRedis keyの古い値を取得し、入力valueと比較して、両者が等しい時に削除されます。リリースが成功すれば、Redisキャッシュの削除に成功し、戻り値は1になります。失敗したら0に戻ります。
コードは一見問題ないようですが、試してみてください。
ロックを解除するなら、その前に必ずロックをかけて、ロックの論理を見てみましょう。
錠をかける
ここをロックするロジックといえば、コードの中には二つの実現方法があります。
コード例1
public Object lock01(String key, String value) {
log.info("lock01, key={}, value={}", key, value);
return redisTemplate.opsForValue().setIfAbsent(key, value, LOCKED_TIME, TimeUnit.SECONDS);
}
コード例2
public Object lock02(String key, String value) {
log.info("lock02, key={}, value={}", key, value);
return stringRedisTemplate.opsForValue().setIfAbsent(key, value, LOCKED_TIME, TimeUnit.SECONDS);
}
実はそれらの違いは前者がRedisTemplateを使っていますが、後者はStringRedisTemplateを使っています。Q:ちょっと待ってください。なぜ二つのtemplateがありますか?
A:押して言いました。私が掘った穴です。RedisTemplateは私が加えたものです。今思い出しても、当初はなぜこのようにしたのか分かりませんでした。本当に頭が一時的にパンクしたかもしれません。
まずこの二つの方法をテストします。
テストします
それぞれロックをかけます。ここで、lock 01はk 1とv 1、lock 02はk 2とv 2です。
k 1、k 2の値をそれぞれ見てください。
v 1にはダブルクォーテーションがありますが、v 2にはありません。
推測は序列化されるべき問題です。Redisの配置を見てみますか?
RedisTemplateの設定
ロックはそこで見られますが、k 1はRedis Templateを使っています。k 2はStringRedisTemplateです。この二つの構成にはどんな違いがありますか?
このうちRedisTemplateの構成はカスタムであり、以下の通りである。
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// Jackson2JsonRedisSerialize
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer
= new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// key、value ( value)
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
StringRedis Templateの構成はSpringBootのデフォルトである:
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
PS:Spring Bootバージョンは2.1.13 RELEASEです。ポイントを入れてSteringRedis Templateを見てください。
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
//
setKeySerializer(RedisSerializer.string());
setValueSerializer(RedisSerializer.string());
setHashKeySerializer(RedisSerializer.string());
setHashValueSerializer(RedisSerializer.string());
}
// ...
}
プログレッシブ設定に注意して、引き続きフォローします。どのような方式ですか?
public interface RedisSerializer<T> {
static RedisSerializer<String> string() {
return StringRedisSerializer.UTF_8;
}
}
public class StringRedisSerializer implements RedisSerializer<String> {
public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);
// ...
}
StringRedis TemplateのkeyとvalueはデフォルトではStringRedis Serializer(Standard Charets.UTF_)を使っていることが見られます。8)序列化を行う。RedisTemplateのkeyはStringRedis Serializerを使っています。valueはJackson 2 JsonRedis Serializerを使っています。
ここでは基本的に問題点に位置づけられます。つまりRedis TemplateのvalueプログレッシブとStringRedis Templateは一致しません。
もし同じに変更すればいいですか?検証してみます。
検証推論
RedisTemplateのvalueプログレッシブ方式をStringRedis Serializerに変更する:
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// ...
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
// ...
return redisTemplate;
}
}
二つのロックロジックを呼び出して、k 1、k 2の値を見てください。v 1のダブルクォーテーションがなくなり、ロックを解除するサービスも正常に削除されました。
はい、ここの問題です。
両者のソースコードの序列化については、興味のあるたらい友達が研究を続けてもいいです。ここではもう深く検討しません。
結び目
本論文で出会ったこの問題は、主に異なるRedisTemplateを使用してロックとリリースロックをかけていますが、この二つのtemplateは異なる順序化方式を使用しています。最終的にはまだ順序化による問題です。
最初は本当に軽率でした。しかもまだ予測できませんでした。
生産環境については、慎重かつ慎重に:深淵に臨んで、薄氷を踏むようである。
この記事では、Redisの分散式のロックについての序文を紹介します。Redisの分散式のロックに関する問題の内容は以前の文章を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。