Redis 分散ロックで発生するシリアル化の問題

Redis 分散ロックで発生するシリアル化の問題

[[389269]]

シナリオの説明

最近、Redis を使用しているときに、分散ロックに似たシナリオに遭遇しました。 Redis の分散ロックの実装と同様に、ロックの解放に失敗しました。つまり、キャッシュを削除できなかったことになります。私はまた別のRedisの穴に足を踏み入れました...

これはどのような状況ですか? また、どのようにトラブルシューティングすればよいですか?

この記事では主にこれについてレビューします。

トラブルシューティング

ロックの解除に問題があるので、まずはロックを解除するコードを見てみましょう。

ロックを解除

ロックを解除するには Lua スクリプトを使用します。コードロジックと Lua スクリプトは次のとおりです。

ロック解除サンプルコード

  1. パブリックオブジェクトリリース(文字列キー、文字列値) {
  2. オブジェクトexistedValue = stringRedisTemplate.opsForValue().get( key );
  3. log.info( "キー:{}、値:{}、redis の古い値: {}" キー、値、存在する値);
  4.    
  5. DefaultRedisScript<Long> redisScript = 新しい DefaultRedisScript<>(COMPARE_AND_DELETE, Long.class);
  6. stringRedisTemplate.execute ( redisScript 、 Collections.singletonList(キー)、 値 )を返します
  7. }

ロックを解除するために使用されるLuaスクリプト

  1. redis.call( 'get' ,KEYS[1]) == ARGV[1]の場合
  2. それから 
  3. redis.call( 'del' ,KEYS[1])を返す
  4. それ以外 
  5. 0を返す
  6. 終わり;

削除スクリプトでは、まず Redis キーの古い値が取得され、入力パラメータ値と比較されます。両者が等しい場合にのみ削除されます。

解放が成功した場合、つまり Redis キャッシュが正常に削除された場合、戻り値は 1 になります。それ以外の場合は失敗し、戻り値は 0 になります。

一見すると、コードは問題ないように見えます。テストしてみましょう。

ただし、ロックを解除する必要があるため、その前にロックする必要があります。まずロックのロジックを見てみましょう。

ロック

ロックのロジックに関しては、コードに実装する方法が 2 つあります。

サンプルコード1

  1. パブリックオブジェクト lock01(文字列キー、文字列値) {
  2. log.info( "lock01, キー={}, 値={}" ,キー, 値);
  3. redisTemplate.opsForValue().setIfAbsent(キー、値、LOCKED_TIME、TimeUnit.SECONDS )を返します
  4. }

サンプルコード2

  1. パブリックオブジェクト lock02(文字列キー、文字列値) {
  2. log.info( "lock02, キー={}, 値={}" ,キー, 値);
  3. stringRedisTemplate.opsForValue().setIfAbsent(キー、値、LOCKED_TIME、TimeUnit.SECONDS )を返します
  4. }

実際、それらの違いは、前者は RedisTemplate を使用し、後者は StringRedisTemplate を使用することです。

  • Q: ちょっと待ってください…なぜテンプレートが 2 つあるのですか?
  • A: 実を言うと、穴を掘ったのは私です。 RedisTemplate を追加しました... 今考えてみると、なぜそうしたのかまだわかりません。たぶん頭が固まってしまっただけでしょう。

まずこの 2 つの方法をテストしてみてはいかがでしょうか?

試してみる

それぞれロックするには 2 つの方法を使用します。lock01 は k1 と v1、lock02 は k2 と v2 です。

k1 と k2 の値をそれぞれ見てみましょう (ツール: RDM、Redis Desktop Manager を使用)。


v1 には二重引用符がありますが、v2 には二重引用符がないことがわかります。

シリアル化の問題だと思います。 Redis の設定を見てみましょう。

Redisテンプレートの設定

ロックを見るとわかるように、k1 は RedisTemplate を使用し、k2 は StringRedisTemplate を使用しています。それぞれの構成の違いは何ですか?

RedisTemplate の構成は次のようにカスタマイズされます。

  1. @構成
  2. @AutoConfigureAfter(RedisAutoConfiguration.クラス)
  3. パブリッククラスRedisConfig {
  4. @ビーン
  5. パブリックRedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  6. RedisTemplate<String, Object> redisTemplate = 新しい RedisTemplate<>();
  7. redisTemplate.setConnectionFactory(redisConnectionFactory);
  8.  
  9. // Jackson2JsonRedisSerializeを使用してデフォルトのシリアル化を置き換えます
  10. Jackson2JsonRedisSerializer<オブジェクト> jackson2JsonRedisSerializer
  11. = 新しい Jackson2JsonRedisSerializer<>(Object.class);
  12.  
  13. オブジェクトマッパー objectMapper = 新しいオブジェクトマッパー();
  14. objectMapper.setVisibility(PropertyAccessor. ALL 、 JsonAutoDetect.Visibility. ANY );
  15. オブジェクトマッパーのデフォルトタイピングを有効にします(ObjectMapper.DefaultTyping.NON_FINAL);
  16. objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
  17.  
  18. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  19.  
  20. //キーと値(特に値)のシリアル化ルールを設定します
  21. redisTemplate.setKeySerializer(新しい StringRedisSerializer());
  22. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  23. redisTemplate.afterPropertiesSet();
  24.  
  25. redisTemplateを返します
  26. }
  27. }

StringRedisTemplate の構成は SpringBoot のデフォルトの構成です。

  1. @構成
  2. @ConditionalOnClass({RedisOperations.class})
  3. @EnableConfigurationProperties({RedisProperties.class})
  4. @Import({LettuceConnectionConfiguration.class、JedisConnectionConfiguration.class}) をインポートします。
  5. パブリッククラスRedisAutoConfiguration {
  6. パブリックRedisAutoConfiguration() {
  7. }
  8.  
  9. @ビーン
  10. @条件付きで欠落しているBean
  11. パブリックStringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) はUnknownHostExceptionをスローします {
  12. StringRedisTemplate テンプレート = 新しい StringRedisTemplate();
  13. テンプレート。setConnectionFactory(redisConnectionFactory);
  14. 戻りテンプレート;
  15. }
  16. }

PS: SpringBootのバージョンは2.1.13.RELEASEです

StringRedisTemplate をクリックして確認してみましょう。

  1. パブリッククラスStringRedisTemplateはRedisTemplate<String, String>を拡張します。
  2.      
  3. パブリックStringRedisテンプレート() {
  4. // ここでシリアル化の設定に注意してください
  5. キーシリアライザーを設定します(RedisSerializer.string());
  6. RedisSerializer を setValueSerializer(RedisSerializer.string());
  7. RedisSerializer を setHashKeySerializer(RedisSerializer.string());
  8. RedisSerializer を setHashValueSerializer(RedisSerializer.string());
  9. }
  10. // ...
  11. }

シリアル化設定に注意し、方法が何であるかを確認するためにフォローアップを続けます。

  1. パブリックインターフェースRedisSerializer<T> {
  2. 静的RedisSerializer<String>文字列() {
  3. StringRedisSerializer.UTF_8を返します
  4. }
  5. }

  1. パブリッククラスStringRedisSerializerはRedisSerializer<String>を実装します。
  2. 公共 静的最終 StringRedisSerializer UTF_8 = 新しい StringRedisSerializer(StandardCharsets.UTF_8);
  3. // ...
  4. }

ご覧のとおり、StringRedisTemplate のキーと値は、デフォルトで StringRedisSerializer(StandardCharsets.UTF_8) によってシリアル化されます。

RedisTemplate のキーは StringRedisSerializer を使用し、値は Jackson2JsonRedisSerializer を使用してシリアル化されています (なぜこれを使用するかについては、ここでは書きません)。

この時点で、基本的に問題を特定できます。RedisTemplate の値のシリアル化が StringRedisTemplate と一致していません。

一貫性を保つために変更しても大丈夫でしょうか?確認してみてください。

推論を検証する

RedisTemplate の値のシリアル化メソッドを StringRedisSerializer に変更します。

  1. @構成
  2. @AutoConfigureAfter(RedisAutoConfiguration.クラス)
  3. パブリッククラスRedisConfig {
  4. @ビーン
  5. パブリックRedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  6. RedisTemplate<String, Object> redisTemplate = 新しい RedisTemplate<>();
  7.        
  8. // ...
  9.  
  10. redisTemplate.setKeySerializer(新しい StringRedisSerializer());
  11. redisTemplate.setValueSerializer(新しい StringRedisSerializer());
  12.  
  13. // ...
  14. redisTemplateを返します
  15. }
  16. }

さらに 2 つのロック ロジックを呼び出して、k1 と k2 の値を確認します。


ご覧の通り、v1 の二重引用符はなくなり、ロックを解除するサービスは正常に削除できます。

まあ、それがここでの問題です。

2 つのシリアル化のソース コードについては、興味のある友人が引き続き学習できるので、ここでは詳しく説明しません。

まとめ

この記事で発生した問題は、主に、ロックのロックと解除に異なる Redis テンプレートが使用され、これら 2 つのテンプレートが異なるシリアル化方法を使用しているために発生し、最終的にはシリアル化によって問題が発生します。

当時は本当に不注意だったので、しばらくは理解できませんでした...

実稼働環境に関しては、まるで奈落の底に立っているか、薄氷の上を歩いているかのように、依然として細心の注意を払う必要があります。

<<:  Grafana Tempo による分散トレース

>>:  ハイパーコンバージェンス「ハードからソフトへ」: ハイブリッド クラウド向けに設計された Azure Stack HCI が中国に進出

推薦する

中小企業が新規ウェブサイト構築や最適化の際に注意すべきポイント

この記事では、基本的な SEO スキルを教えるのではなく、新しいサイトを構築するときに検索エンジンと...

Harbor v2.9.0 のバージョン変更とオフライン展開

バージョンの変更1.1 更新内容セキュリティ センターの管理者ユーザーは、スキャン済みおよび未スキャ...

天猫は激怒し「禁止命令」を発令し、販売業者は「どちらかの側につく」ことを余儀なくされた。

今はチャネルが王様の時代です。発言権を持つチャネルディーラーの前では、サプライヤーは不利な立場に置か...

Alibaba Cloudデータセンターのアップグレードにより、きめ細かなシナリオが深まり、新しい小売業のデジタル化が加速

9月18日、2020年雲奇カンファレンスにおいて、アリババクラウドのデータミドルウェア製品が全面的に...

chicagovps-$6.36/512m メモリ/20g ハードディスク/1T トラフィック/G ポート/DDOS 保護

chicagovps は、DDOS 保護 VPS の提供を開始したことを発表しました。現在はニューヨ...

初心者がSEOを学ぶための方法と提案についての簡単な説明

最近、友人たちが「なぜこの人たちはSEOを学ばないのか」「なぜこんな簡単な質問も理解できないのか」「...

「あいまいな」ブランドプロモーション: 中小企業がブランドプロモーションでよく犯す7つの間違い

大手ブランドといえば、コカコーラの独特な赤と白のフォント、ナイキのスウッシュ、アディダスのスリースト...

Big Bird 草の根 SEO チュートリアル: ホームページのデザインとプログラムの選択

第1章第6節 フェイスプロジェクト---ホームページデザインホームページをどうデザインするかも重要な...

racknerd: 年間 16.5 ドル、ロサンゼルス VPS、1.5G メモリ/1 コア/20g ハード ドライブ/3TB トラフィック

Racknerd は、米国ロサンゼルスの multacom データセンター専用の VPS を宣伝して...

Tik Tokマーケティングの9つの原則

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス世界は、最初に主導権を握...

ウェブマスターネットワークニュース:多くの銀行が決済インターフェースの整理を検討中。テンセントが優酷を買収したとの噂も

1. ICBCは文書86号を使用してAlipayに強く反応し、多くの銀行が支払いインターフェースのク...

2021年のクラウドコンピューティング市場レビュー:大手企業間の熾烈な競争、有望な業界展望

2021年、クラウドコンピューティング、ビッグデータ、人工知能に代表されるデジタル技術は、人類の生産...

杭州Siyiou SEO最適化会社紹介

Siyiou は杭州にある SEO 最適化会社です。Siyiou は 2003 年に設立されました。...

Name.com イベント: com/net に登録して $5 + $0.99 を獲得

name.com の最新プロモーション: .com または .net ドメイン名を 0.99 ドルで...

高い志から挫折まで、ブログ運営の本質とは?

ブログを書いている人は、ただ単に友達を作りたいだけではないと思います。オンラインでのつながりは価値の...