Redis分散ロックの正しい実装原理の進化とRedissonの実用的な概要

Redis分散ロックの正しい実装原理の進化とRedissonの実用的な概要

[[437124]]

Redis 分散ロックは SET コマンドを使用して実装できますか? CAP 理論は分散分野では常に存在してきました。

分散ロックのトリックはそれほど単純ではなく、インターネットで見られる分散ロック ソリューションには問題がある可能性があります。

「Code Brother」では、分散ロックが段階的に改善される仕組みと、高同時実行の運用環境で分散ロックを正しく使用する方法を段階的に説明します。

テキストを入力する前に、この質問について考えてみましょう。

  • 分散ロックはいつ必要になりますか?
  • コードを追加またはロック解除するための特定の位置はありますか?
  • ロックを削除できなくなる状況を回避するにはどうすればよいですか?
  • 適切なタイムアウト設定は何ですか?
  • 他のスレッドによってロックが解放されるのを防ぐ方法
  • 再入可能ロックを実装するにはどうすればいいですか?
  • マスタースレーブアーキテクチャはどのようなセキュリティ上の問題をもたらしますか?
  • レッドロックとは
  • Redisson 分散ロックのベストプラクティス
  • ウォッチドッグ実装原則

分散ロックはいつ使用すればよいですか?

マー兄さん、分散ロックがいつ必要になるかを説明する簡単な例を挙げてもらえますか?

クリニックには医師が一人しかおらず、多くの患者が治療に来ます。

医師は一度に一人の患者にしか医療サービスを提供できません。

そうでなければ、医者は腎虚を患っている「小彩季」に薬を処方する準備をしているが、患者は足の臭いを発している「謝八歌」に交代し、薬は謝八歌に奪われてしまうだろう。

足の臭いのする人が腎不全の治療薬を持ち去った。

共有リソースを同時に読み書きする場合、データの正確性を確保するために、同時に 1 つのスレッドだけがアクセスするように制御する必要があります。

分散ロックは、JVM プロセス内の 1 つのスレッドだけが保護されたリソースに同時にアクセスできるように制御するために使用されます。

分散ロックの概要

65 ブラザー:分散ロックはどのような特性を満たす必要がありますか?

相互排他: 一度にロックを保持できるのは 1 つのクライアントだけです。

デッドロックなし: ロックを取得したクライアントがクラッシュした場合でも、いつでもロックを取得できます。

フォールト トレランス: Redis ノードの過半数が稼働している限り、クライアントはロックを取得および解放できます。

コード兄弟、SETNX キー値コマンドを使用して「相互排他」機能を実装できます。

このコマンドは、SET if Not eXists の略語に由来しており、キーが存在しない場合はこのキーに値を設定し、それ以外の場合は何もしないことを意味します。 Redis の公式アドレスには次のように書かれています:

コマンドは次を返します:

  • 1: 設定は成功しました。
  • 0: キーが正常に設定されませんでした。

次のシナリオ:

一日中コードを入力して疲れているので、リラックスして肩と首をマッサージしたいです。

技術者 168 号は最も人気があり、誰もが彼に注文を付けたがるので、同時実行性が高く、分散ロック制御が必要です。

168 人の技術者と同時に予約を取ることができるのは 1 人の「顧客」だけです。

肖彩吉は168人の技術者の募集に合格しました:

  1. > SETNXロック:168 1
  2. (整数) 1 # 168人の技術者を正常に取得

謝覇は後から到着したが、彼の申請は却下された。

  1. > SETNX ロック 2
  2.  
  3. (整数) 0 # 顧客謝八格2の取得に失敗しました

現在、お申し込みに成功したお客様は、168名の技術者による「リソース共有」による肩・首のリラクゼーションサービスをご利用いただけます。

楽しみが終わったら、後から来た人が 168 人の技術者のサービスを受けられるように、ロックを時間通りに解除する必要があります。

Xiao Caijiさん、ロックを解除する方法を教えていただけますか?

とても簡単です。DEL を使用してキーを削除するだけです。

  1. > DELロック:168
  2. (整数) 1

マー兄さん、「ドラゴン」を見たことがありますか?はい、ドラゴンに仕えられたからです。

シャオ・ツァイジ、物事はそんなに単純じゃないよ。

このソリューションには、ロックを解除できないという問題があります。この問題を引き起こすシナリオは次のとおりです。

クライアントが配置されているノードがクラッシュし、ロックを正しく解放できません。

ビジネスロジックが異常であるため、DEL 命令を実行できません。

このようにして、ロックは常に使用され、私の手の中にあります。私が死んだら、他のクライアントはロックを取得できなくなります。

タイムアウト設定

コード兄弟、ロックが正常に取得されたときに「タイムアウト」を設定できます

たとえば、マッサージ サービスを 60 分間設定したい場合は、キーをロックするときに 60 分の有効期限を設定できます。

  1. > SETNX lock:168 1 // ロックを取得
  2.  
  3. (整数) 1
  4.  
  5. > EXPIRE lock:168 60 // 60秒自動削除
  6.  
  7. (整数) 1

これにより、時間経過後にロックが自動的に解除され、他のお客様は引き続き 168 人の技術者によるマッサージ サービスを受けることができます。

誰かがこのように書いたらひどいことになるでしょう。

「ロック」と「タイムアウトの設定」は 2 つのコマンドであり、アトミック操作ではありません。

最初の項目のみが実行され、2 番目の項目が実行される機会がない場合、「タイムアウト」設定は失敗し、ロックは解除されません。

ママ兄さん、どうしたらいいですか?この問題を解決するワンストップサービスが欲しいです。

Redis 2.6.X 以降、公式は SET コマンドのパラメータを拡張し、キーが存在しない場合に値を設定する、タイムアウト期間を設定する、アトミック性を満たすというセマンティクスを満たすようになりました。

  1. リソース名ランダム値 NX PX 30000を設定する
  • NX: resource_name が存在しない場合にのみ SET が成功することを示し、これにより 1 つのクライアントのみがロックを取得できることが保証されます。
  • PX 30000: このロックの自動有効期限が 30 秒であることを示します。

これだけじゃ十分ではありません。自分たちが追加していないロックの解除も防止する必要があります。価値を活かすことができる。

続きを読む…

自分が追加していないロックを解除しました

ワンストップサービスを安心してご利用いただけますか?

いいえ、他の人のロックを解除する別のシナリオがあります。

クライアント 1 はロックを正常に取得し、30 秒のタイムアウトを設定します。

クライアント 1 の実行は、何らかの理由 (ネットワークの問題、FullGC の発生など) により非常に遅くなり、30 秒経っても実行が完了しませんでしたが、ロックが期限切れになり、「自動的に解放」されました。

クライアント 2 はロックを正常に申請します。

クライアント 1 は実行を完了すると、DEL ロック解除命令を実行し、その時点でクライアント 2 のロックが解除されます。

解決する必要がある重要な問題があります。それは、自分のロックしか解除できないことです。

追加したロックを削除するにはどうすればよいですか?

DEL 命令を実行するときは、削除命令を実行する前に追加したロックであるかどうかを確認する方法を見つける必要があります。

ベルを結んだ人はそれを解かなければならない

コード兄弟、ロックするときに、ロックするクライアントを表す値として「一意の識別子」を設定します。リソース名ランダム値 NX PX 30000 を設定する

ロックを解除するとき、クライアントは自身の「一意の識別子」とロックの「識別子」を比較して、それらが等しいかどうかを確認します。一致した場合は削除されます。それ以外の場合は、ロックを解除する権利はありません。

疑似コードは次のとおりです。

  1. // 値を一意の識別子と比較する
  2. redis.get( "lock:168" ).equals(random_value) の場合{
  3. redis.del( "ロック:168" ); //比較が成功したら削除する
  4. }

これは GET + DEL 命令の組み合わせであり、アトミック性の問題が関係していると考えたことがありますか。

これを Lua スクリプトを通じて実装すると、判断と削除のプロセスがアトミック操作になります。

  1. // ロック値を取得してARGV[1]と一致するかどうかを確認します。存在する場合は、delを実行します
  2. redis.call( "get" ,KEYS[1]) == ARGV[1]の場合 
  3. redis.call( "del" ,KEYS[1])を返す
  4. それ以外 
  5. 0を返す
  6. 終わり 

一意の値を value に設定して、ロックをロックしたクライアントを識別することが重要です。あるクライアントが別のクライアントのロックを削除する可能性があるため、DEL のみを使用することは安全ではありません。

上記のスクリプトを使用すると、各ロックはランダムな文字列で「署名」され、ロックを削除するクライアントの「署名」がロックの値と一致する場合にのみ削除されます。

公式ドキュメントにも次のように書かれています: https://redis.io/topics/distlock

この解決策は比較的完璧であり、おそらく最も頻繁に使用される解決策です。

ロックタイムアウトを正しく設定する

ロックタイムアウトを計算するにはどうすればいいですか?

今回はランダムに書くことはできません。これは通常、テスト環境での複数のテストと複数ラウンドのストレス テストに基づいており、たとえば、平均実行時間は 200 ミリ秒と計算されます。

すると、ロック タイムアウト期間は平均実行時間の 3 ~ 5 倍に拡大されます。

なぜ拡大するのですか?

ロック操作ロジックにネットワーク IO 操作、JVM FullGC などが含まれている場合、オンライン ネットワークが常にスムーズに動作するとは限らず、ネットワーク ジッタのためにバッファ時間を残す必要があるためです。

1時間など、もっと大きな値に設定したほうが安全ではないでしょうか?

これにこだわらないでください。どれくらい大きいのが「大きい」のでしょうか?

設定時間が長すぎると、ダウンタイムの再起動が発生すると、分散ロック サービスのすべてのノードが 1 時間以内に使用できなくなります。

オペレーターが手動でロックを削除しますか?

運用とメンテナンスに本当に負担がかからない限りは。

完璧な解決策はあるのでしょうか?どのように時間を設定しても、適切ではないようです。

ロックを取得したスレッドにデーモン スレッドを開始させて、期限切れになりそうなロックを「延長」させることができます。

ロック時に有効期限が設定され、クライアントは「デーモン スレッド」を開始して、ロックの有効期限を定期的に検出します。

ロックの有効期限が近づいているがビジネス ロジックが実行されていない場合は、ロックが自動的に更新され、有効期限がリセットされます。

意味は通じますが、書けません。

慌てる必要はありません。これらすべてのタスクをカプセル化するライブラリがすでに存在します。それはレディソンと呼ばれています。

分散ロックを使用する場合、ロックの有効期限切れを回避するために「自動更新」ソリューションを採用します。このデーモン スレッドは一般に「ウォッチドッグ」スレッドと呼ばれます。

最後まで最適化すると、ソリューションはより「厳密」になり、対応するモデルは次のように抽象化されます。

  1. SET lock_resource_name random_value NX PX expire_time を通じて、期限が切れそうでまだ実行が完了していないクライアントのロックを延長するデーモン スレッドが開始されます。
  2. クライアントはビジネス ロジックを実行して共有リソースを操作します。
  3. Lua スクリプトを通じてロックを解除します。まず、ロックが自分で追加されたかどうかを確認し、その後 DEL を実行します。

この解決策は実のところ非常に完璧です。このステップまで記述できることは、すでに 90% のプログラマーの能力を超えています。

しかし、完璧さを追求するプログラマーにとって、これでは十分ではありません。

  1. 再入可能ロックを実装するにはどうすればいいですか?
  2. マスタースレーブアーキテクチャのクラッシュ回復によって発生するロック損失の問題を解決するにはどうすればよいですか?
  3. クライアントをロックする方法はありますか?

ロック解除コードの場所は重要です

前回の分析に基づくと、すでに「比較的厳密な」分散ロックが存在します。

そこで、「Xie Ba Ge」はプロジェクトに分散ロックを適用するために次のコードを作成しました。疑似コードのロジックは次のとおりです。

  1. パブリックvoid doSomething() {
  2. redisLock() を呼び出します。 // ロック
  3. 試す {
  4. // プロセスビジネス
  5. .....
  6. redisLock のロックを解除します。 // ロックを解除する
  7. } キャッチ (例外 e) {
  8. e.printStackTrace();
  9. }
  10. }

これについて考えたことはありますか: ビジネス ロジックの実行中に例外がスローされると、プログラムはロックを解除するプロセスを実行できなくなります。

したがって、ロックを解除するコードは finally{} ブロックに配置する必要があります。

ロック位置も問題あり。 try メソッドの外側に配置すると、redisLock.lock() ロック例外が発生しても、実際の命令はサーバーに送信され実行されていても、クライアントが応答の読み取りでタイムアウトした場合、ロック解除コードを実行する機会がなくなります。

したがって、ロック解除ロジックが確実に実行されるように、 redisLock.lock() を try コード ブロックに記述する必要があります。

まとめると、正しいコードの場所は次のとおりです。

  1. パブリックvoid doSomething() {
  2. 試す {
  3. // ロック
  4. redisLock() を呼び出します。
  5. // プロセスビジネス
  6. ...
  7. } キャッチ (例外 e) {
  8. e.printStackTrace();
  9. ついに
  10. // ロックを解除する
  11. redisLock のロックを解除します。
  12. }
  13. }

再入可能ロックの実装

65 ブラザー: 再入可能ロックを実装するにはどうすればいいですか?

スレッドがコードの一部を実行し、ロックを正常に取得して実行を続行すると、ロックされたコードに再び遭遇します。再入可能性により、スレッドは実行を継続できますが、再入可能性がない場合は、実行を継続する前にロックが解放されるのを待機し、再度ロックを正常に取得する必要があります。

再入可能性をコードで説明してください:

  1. パブリック同期void a() {
  2. b();
  3. }
  4. パブリック同期void b(){
  5. // 合格
  6. }

スレッド X がメソッド a でロックを取得した後、メソッド b の実行を継続すると仮定します。この時点で再入が不可能な場合、スレッドはロックが解放されるまで待機し、再度ロックを競合する必要があります。

ロックは明らかにスレッド X によって所有されていますが、ロックを取得する前に、スレッド X 自身がロックを解放するのを待つ必要があります。これはとても奇妙に見えます。自分を解放するよ〜

Redis ハッシュ再入可能ロック

RedissonライブラリはRedisハッシュを通じて再入可能ロックを実装します

スレッドがロックを取得した後、将来ロック メソッドに遭遇すると、ロックの数を直接 1 増やしてからメソッド ロジックを実行します。

ロックメソッドを終了した後、ロック回数が 1 減少します。ロック回数が 0 になると、ロックは完全に解除されます。

再入可能ロックの最大の特徴は、ロックが追加された回数を計算するカウント機能であることがわかります。

したがって、分散環境で再入可能ロックを実装する必要がある場合は、ロックの数もカウントする必要があります。

ロックロジック

これを実装するには、Redis ハッシュ構造を使用できます。キーはロックされた共有リソースを表し、ハッシュ構造のフィールドキーの値にはロックの数が格納されます。

KEYS1 = "lock"、ARGV "1000, uuid" と仮定して、Lua スクリプトを通じてアトミック性を実装します。

  1. ---- 1は真を表す 
  2. ---- 0は偽を意味します 
  3. (redis.call( 'exists' , KEYS[1]) == 0)の場合 
  4. redis.call( 'hincrby' , キー[1], ARGV[2], 1);
  5. redis.call( 'pexpire' , KEYS[1], ARGV[1]);
  6. 1 を返します
  7. 終わり;
  8. (redis.call( 'hexists' , KEYS[1], ARGV[2]) == 1)場合 
  9. redis.call( 'hincrby' , キー[1], ARGV[2], 1);
  10. redis.call( 'pexpire' , KEYS[1], ARGV[1]);
  11. 1 を返します
  12. 終わり;
  13. 0を返します

ロック コードは、まず Redis の exists コマンドを使用して、現在のロックが存在するかどうかを判断します。

ロックが存在しない場合は、hincrby を直接使用してキー uuid を持つロック ハッシュ テーブルを作成し、ハッシュ テーブル内のキー uuid を 0 に初期化してから、再度 1 を追加し、最後に有効期限を設定します。

現在のロックが存在する場合は、hexists を使用して、キー uuid が現在のロックに対応するハッシュ テーブルに存在するかどうかを判断します。存在する場合は、hincrby を使用して再度 1 を追加し、最後に有効期限を再度設定します。

最後に、上記の 2 つのロジックが一致しない場合は、直接戻ります。

ロジックをアンロック

-- ハッシュセットの再入可能キーの値が0に等しいかどうかを判定します

-- 0の場合は、再入可能キーが存在しないことを意味します

  1. -- ハッシュセットの再入可能キーの値が0に等しいかどうかを判定します 
  2. -- 0の場合は、再入可能キーが存在しないことを意味します 
  3. (redis.call( 'hexists' , KEYS[1], ARGV[1]) == 0)場合 
  4. nilを返します
  5. 終わり;
  6. -- 現在の再入可能回数を計算する 
  7. ローカルカウンター = redis.call( 'hincrby' , KEYS[1], ARGV[1], -1);
  8. -- 0以下の場合はロック解除可能 
  9. (カウンタ>0)ならば 
  10. 0を返します
  11. それ以外 
  12. redis.call( 'del' , キー[1]);
  13. 1 を返します
  14. 終わり;
  15. nilを返します

まず、hexists を使用して、Redis ハッシュ テーブルに特定のフィールドが含まれているかどうかを判断します。

ロックに対応するハッシュ テーブルが存在しない場合、またはキー uuid がハッシュ テーブルに存在しない場合は、直接 nil が返されます。

存在する場合、現在のロックはそれによって保持されていることを意味します。まず、hincrby を使用して再入回数を 1 減らし、計算後に再入回数を決定します。 0 以下の場合は、del を使用してロックを削除します。

ロック解除コードの実行方法はロックの場合と似ていますが、ロック解除の実行結果の戻り値の型が Long である点が異なります。ここでロックと同様にブール値が使用されていない理由は、ロック解除の Lua スクリプトでは、3 つの戻り値が次の意味を持つためです。

  • 1はロック解除が成功し、ロックが解除されたことを意味します
  • 0は再入回数が1減少することを意味する
  • null は他のスレッドがロック解除を試み失敗したことを意味します。

マスタースレーブアーキテクチャによって引き起こされる問題

コード兄弟、分散ロックはここでは「完璧」ですよね?分散ロックにこれほど多くのトリックがあるとは思いませんでした。

まだ長い道のりが残っています。これまで分析したシナリオはすべて、「単一の」Redis インスタンスをロックすることによって発生する可能性のある問題であり、Redis マスター/スレーブ モードによって発生する問題は含まれていませんでした。

高可用性を確保するために、通常は「Cluster Cluster」または「Sentinel Cluster」モードを使用します。

どちらのモードも「マスタースレーブアーキテクチャデータ同期レプリケーション」に基づいてデータ同期を実装しており、Redis のマスタースレーブレプリケーションはデフォルトで非同期です。

以下の内容は公式ドキュメント https://redis.io/topics/distlock から引用したものです。

次のシナリオで何が起こるか想像してみましょう。

クライアント A はマスター ノードのロックを正常に取得します。

ロック取得情報がスレーブに同期される前にマスターがクラッシュしました。

スレーブが新しい​​マスターとして選出され、この時点ではロックされたデータを取得するクライアント A は存在しません。

クライアント B は、分散ロックによって定義された相互排他ルールに違反して、クライアント A が保持しているロックを正常に取得できます。

確率は極めて低いですが、このリスクの存在を認識しなければなりません。

Redisの作者はRedlockと呼ばれるソリューションを提案した。

分散ロックの標準を統一するために、Redis の作者は Redlock を作成しました。これは、分散ロックを実装するための公式の Redis ガイドと見なされています (https://redis.io/topics/distlock)。しかし、このレッドロックは海外の分散専門家からも批判されている。

なぜなら、完璧ではなく、「穴」があるからです。

レッドロックとは

これは赤い鍵ですか?

[[437127]]

インスタントラーメンを食べ過ぎると、Redlock は、マスタースレーブ アーキテクチャでマスターとスレーブの切り替えが発生したときに、複数のクライアントが同じロックを保持する問題を解決するために提案されたアルゴリズムです。

公式ドキュメント (https://redis.io/topics/distlock) を読むことができます。以下は公式ドキュメントからの翻訳です。

Redlock を使用する場合、公式の推奨事項は、異なるマシンに 5 つの Redis マスター ノードをデプロイすることです。ノードは完全に独立しており、マスタースレーブレプリケーションは使用しません。フォールトトレランスのために複数のノードが使用されます。

クライアントがロックを取得するには、次の 5 つの手順があります。

クライアントは現在の時刻 T1 (ミリ秒レベル) を取得します。

同じキーと値の順序を使用して、N 個の Redis インスタンスからロックを取得しようとします。

各リクエストにはタイムアウト (ミリ秒単位) が設定されています。このタイムアウトはロックの有効期間よりもはるかに短くする必要があります。これにより、次のインスタンスにリクエストをすばやく送信できるようになります。

たとえば、ロックの自動解除時間が 10 秒の場合、リクエストのタイムアウトを 5 ~ 50 ミリ秒以内に設定することができ、クライアントが長時間ブロックされることを防ぐことができます。

クライアントは現在の時刻 T2 を取得し、ステップ 1 から T1 を減算して、ロックの取得にかかる時間を計算します (T3 = T2 - T1)。ロックは、クライアントがほとんどのインスタンス (N/2 + 1) でロックの取得に成功し、ロックの取得に使用された合計時間 T3 がロックの有効時間よりも短い場合にのみ成功したと見なされます。そうでない場合、ロックは失敗します。

ステップ 3 でロックが正常に取得されると、リソースを共有するためのビジネス ロジック操作が実行されます。キーの実際の有効時間は、有効時間からロックを取得するために使用された時間を引いた値 (手順 3 で計算された結果) に等しくなります。

何らかの理由でロックの取得に失敗した場合 (少なくとも N/2+1 個の Redis インスタンスでロックが取得されていないか、ロックの取得時間が有効時間を超えている場合)、クライアントはすべての Redis インスタンスのロックを解除する必要があります (一部の Redis インスタンスが正常にロックされていない場合でも)。

また、デプロイされるインスタンスの数は奇数である必要があります。多数決ルールを満たすには、インスタンスが 6 個ある場合、4 つのインスタンスが正常にロックを取得して初めて成功と見なされるため、奇数の方が合理的です。

物事はそんなに単純ではありません。 Redis の作者がこのソリューションを提案した後、業界の著名な分散システムの専門家から疑問が投げかけられました。

二人はまるで神々が戦うかのように、一つの問題に対して多くの結論を出すのに十分な証拠をもって、何度も議論を交わしました...

この問題を提起した Martin Kleppmann のブログ投稿: https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

Redlock デザイナーの回答: http://antirez.com/news/101

レッドロックはい、いいえ

Martin Kleppmann 氏は、ロックの目的は共有リソースの読み取りと書き込みを保護することであり、分散ロックは「効率的」かつ「正確」であるべきだと考えています。

効率: 分散ロックは高パフォーマンスの要件を満たす必要があります。 Redlock アルゴリズムは、5 つのノードでロックを取得するロジックを実行する際のパフォーマンスが低く、コストと複雑さが増加します。

正確性: 分散ロックは、同時実行プロセスで共有データを同時に読み書きできるスレッドが 1 つだけになることを防ぐ必要があります。

これら 2 つの理由により、5 つの Redis インスタンスを実行し、ロックが成功するために大多数の要件を満たすかどうかを判断するという、Redlock のコストと複雑さを負担する必要がありません。

マスタースレーブアーキテクチャのクラッシュ回復が発生する可能性は極めて低いため、大きな問題にはなりません。スタンドアロン版を使用すれば十分です。Redlock は重すぎて不要です。

Martin は、Redlock がセキュリティ要件をまったく満たしておらず、ロック障害の問題が依然として残っていると考えています。

マーティンの結論

Redlock は、良い面も悪い面もあります。つまり、設定効率の点では、Redlock は重すぎて不要であり、設定の正確さの点では、Redlock は十分に安全ではありません。

不合理なクロックの仮定: アルゴリズムは、システム クロックについて危険な仮定を行います (複数のノード マシンのクロックが一貫していると仮定します)。これらの仮定が満たされない場合、ロックは失敗します。

正確性を保証できない: Redlock はフェンシング トークンと同様のソリューションを提供できないため、正確性の問題を解決できません。正確性を保つには、Zookeeper などの「コンセンサス システム」を備えたソフトウェアを使用してください。

Redis作者Antirezの反論

Redis の作者による反論記事には、3 つの重要なポイントがあります。

クロックの問題: Redlock では完全に一貫性のあるクロックは必要なく、ほぼ一貫性のあるクロックで十分です。 「エラー」は、ロックのリース期間を超えない限り許可されます。このクロック精度の要件はそれほど高くなく、実際の環境にも合致しています。

ネットワーク遅延とプロセス停止の問題:

ロックを取得する前にクライアントがどのような時間のかかる問題に遭遇したとしても、Redlock はステップ 3 でそれを検出できます。

クライアントがロックを取得した後、NPCが発生し、RedlockとZookeeperは無力になります。

フェンシング トークンのメカニズムについて質問します。

次回はRedlockに関する討論でお会いしましょう。それでは、Redisson を使用して分散ロックを実装する実践的な部分に移りましょう。

Redisson 分散ロック

SpringBoot スターター メソッドに基づいてスターターを追加します。

  1. <依存関係>
  2. <グループID>org.redisson</グループID>
  3. <artifactId>redisson-spring-boot-starter</artifactId>
  4. <バージョン>3.16.4</バージョン>
  5. </依存関係>

ただし、公式の推奨では springboot バージョンと一緒に redisson バージョンを使用することになっているため、ここでは springboot と redisson のバージョンに注意する必要があります。

Redisson と Spring Boot ライブラリの統合も Spring Data Redis モジュールに依存します。

「Code Brother」は SpringBoot 2.5.x バージョンを使用するため、redisson-spring-data-25 を追加する必要があります。

  1. <依存関係>
  2. <グループID>org.redisson</グループID>
  3. <! -- Spring Data Redis v.2.5.x の場合 -->  
  4. <artifactId>redisson-spring-data-25</artifactId>
  5. <バージョン>3.16.4</バージョン>
  6. </依存関係>

プロフィールを追加する

  1. 春:
  2. レディス:
  3. データベース:
  4. ホスト:
  5. ポート:
  6. パスワード:
  7. SSL:
  8. タイムアウト:
  9. # 実際の状況に応じてクラスターまたはセンチネルを構成する
  10. クラスタ:
  11. ノード:
  12. センチネル:
  13. マスター:
  14. ノード:

したがって、Spring コンテナでは次の Bean が使用可能になります。

  • Redissonクライアント
  • RedissonRxクライアント
  • RedissonReactiveClient
  • Redisテンプレート
  • リアクティブRedisテンプレート

失敗時の無限再試行

  1. RLock lock = redisson.getLock( "コードバイト" );
  2. 試す {
  3.  
  4. // 1. 最も一般的に使用される最初の書き方
  5. ロック。ロック();
  6.  
  7. // ビジネスロジックを実行する
  8. .....
  9.  
  10. ついに
  11. ロックを解除します。
  12. }

ロックが失敗すると、継続的に再試行されます。 Watch Dog 自動延長メカニズムがあり、デフォルトで 30 秒に設定され、30/3 = 10 秒ごとに 30 秒に延長されます。

タイムアウト再試行に失敗し、自動的に寿命が延長される

  1. // ロックの取得を 10 秒間試行した後、再試行を停止します。取得に失敗した場合はfalseを返します。 Watch Dog 自動延長機構を備えており、デフォルトの延長時間は 30 秒です。
  2. ブールフラグ = lock.tryLock(10, TimeUnit.SECONDS);

タイムアウト時に自動的にロックを解除する

  1. // Watch Dog がない場合、ロックは 10 秒後に自動的に解除されるため、ロックを解除するために unlock を呼び出す必要はありません。
  2.  
  3. ロック。ロック(10、時間単位はSECONDS)。

タイムアウト再試行、自動ロック解除

  1. // ロックを試み、最大 100 秒待機し、10 秒のロック後に自動的にロック解除します。ウォッチ ドッグはありません。
  2. ブール値 res = lock.tryLock(100, 10, TimeUnit.SECONDS);
  3. もし(res){
  4. 試す {
  5. ...
  6. ついに
  7. ロックを解除します。
  8. }
  9. }

ウォッチドッグ自動遅延

分散ロックを取得したノードがクラッシュし、ロックがまだロックされている場合、デッドロックが発生します。

この状況を回避するために、ロックのタイムアウト自動解除時間を設定します。

しかし、まだ問題が残っています。

スレッドがロックを正常に取得し、30 秒のタイムアウトを設定したとします。ただし、タスクが 30 秒以内に完了せず、タイムアウトによりロックが解放された場合、他のスレッドが取得すべきでないロックを取得する可能性があります。

そのため、Redisson はウォッチドッグ自動遅延メカニズムとロックを監視するためのウォッチドッグを提供します。その機能は、Redisson インスタンスが閉じられる前にロックの有効期間を継続的に延長することです。

つまり、ロックを取得したスレッドがロジックを完了していない場合、ウォッチドッグはスレッドがロック タイムアウトを継続的に延長するのを支援し、タイムアウトによってロックが解放されることはありません。

デフォルトでは、ウォッチドッグの更新時間は 30 秒ですが、Config.lockWatchdogTimeout を変更することで指定することもできます。

さらに、Redisson は、leaseTime パラメータを指定してロック時間を指定できるロック メソッドも提供します。

この時間が経過すると、ロックは自動的に解除され、ロックの有効期間は延長されません。

原則は次のとおりです。

注意すべき点が 2 つあります。

  • watchDog は、指定されたロック タイムアウト (leaseTime) が明示的に指定されていない場合にのみ有効になります。
  • lockWatchdogTimeout に設定する時間は短すぎないようにしてください。たとえば、100 ミリ秒に設定すると、ネットワークの問題によりロックが追加された後、ウォッチドッグが拡張されると、Redis でキーが削除されます。

ソースコードガイド

lock メソッドが呼び出されると、最終的に tryAcquireAsync が呼び出されます。

呼び出しチェーンは lock()->tryAcquire->tryAcquireAsync です。詳細な説明は次のとおりです。

  1. プライベート <T> RFuture<Long> tryAcquireAsync(long waitTime, longleasingTime, TimeUnit unit, long threadId) {
  2. RFuture<Long> ttlRemainingFuture;
  3. //ロック時間が指定されている場合は、直接ロックされます
  4. リースタイムが -1 の場合
  5. ttlRemainingFuture = tryLockInnerAsync(waitTime、leaseTime、unit、threadId、RedisCommands.EVAL_LONG);
  6. }それ以外{
  7. //ロック時間が指定されていない場合は、最初にロックが実行され、デフォルトの時間はLockWatchdogTimeout時間になります
  8. //これは非同期操作です。 Nettyのfutureに似たRFutureを返します。
  9. ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
  10. TimeUnit.MILLISECONDS、threadId、RedisCommands.EVAL_LONG);
  11. }
  12.  
  13. //これは、将来のコンテンツが実行された後に実行されるnetty FutureのaddListenerにも似ています。
  14. ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
  15. e != null の場合
  16. 戻る;
  17. }
  18.  
  19. // ロックを取得しました
  20. 残り時間 == null の場合
  21. // リースタイムが -1 でない場合、自動的に延長されません
  22. リースタイムが -1 の場合
  23. 内部ロックリース時間 = unit.toMillis(リース時間);
  24. }それ以外{
  25. //これは、現在のロック自動拡張アクションの時間指定実行です。リースタイムが -1 の場合、自動的に延長されます。
  26. スケジュール有効期限更新(スレッドID)
  27. }
  28. }
  29. });
  30. ttlRemainingFutureを返します
  31. }

scheduleExpirationRenewal は renewExpiration を呼び出し、タイムアウトを有効にして拡張アクションを実行します。

  1. プライベートvoid renewExpiration() {
  2. 有効期限エントリ ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
  3. ee == nullの場合{
  4. 戻る;
  5. }
  6.  
  7. タイムアウトタスク = commandExecutor.getConnectionManager()
  8. .newTimeout(新しいTimerTask() {
  9. @オーバーライド
  10. パブリックvoid run(Timeout timeout) 例外をスローします {
  11. // 一部のコードを省略
  12. ....
  13.  
  14. RFuture<ブール値> future = renewExpirationAsync(threadId);
  15. future.onComplete((res, e) -> {
  16. ....
  17.  
  18. もし(res){
  19. //エラーが報告されない場合は、タイマーは再度延長されます
  20. // スケジュールを再設定する
  21. 有効期限を更新します。
  22. }それ以外{
  23. 有効期限更新をキャンセルします( null );
  24. }
  25. });
  26. }
  27. // ここで、スケジュールされたタスクは、renewExpirationAsync を実行するのに lockWatchdogTimeout の 1/3 の時間であることがわかります。
  28. }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
  29.  
  30. タスクのタイムアウトを設定します。
  31. }

scheduleExpirationRenewal は renewExpirationAsync を呼び出し、次の Lua スクリプトを実行します。

主に、Redis にロックが存在するかどうかを判断します。そうなった場合、有効期限が延長されます。

  1. 保護された RFuture<Boolean> renewExpirationAsync(long threadId) {
  2. evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,を返します
  3. 「(redis.call('hexists', KEYS[1], ARGV[2]) == 1) の場合」 +
  4. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  5. "1を返す; " +
  6. 「終了;」 +
  7. "0を返します。"
  8. コレクション.singletonList(getRawName())、
  9. 内部LockLeaseTime、getLockName(threadId));
  10. }
  • 現在のノードでウォッチドッグがまだ有効であり、タスクが完了していない場合、ロックは 10 秒ごとに 30 秒間更新されます。
  • プログラムがロック操作を解放すると、例外が実行されないためロックを解放できないため、ロック解放操作を finally {} に配置する必要があります。
  • watchLog メカニズムを有効にするには、ロック時に有効期限を設定しないでください。
  • ウォッチログの遅延時間は lockWatchdogTimeout で指定できます。ただし、あまり小さく設定しないでください。
  • ウォッチドッグは lockWatchdogTimeout/3 時間ごとに遅延します。
  • 遅延は lua スクリプトによって実現されます。

要約する

終わったら、画面を閉じて、自分が何をしているのか、なぜそれをしているのか、どんな問題を解決しようとしているのかを考えながら、もう一度各ステップを頭の中で確認することをお勧めします。

一緒に、Redis 分散ロックのさまざまなトリックを最初から最後まで確認しました。実際、これらの点の多くは、分散ロックに何を使用しても存在する問題です。大切なのは思考のプロセスです。

システム設計に関しては、誰もが異なる出発点を持っています。完璧な建築、普遍的な建築というものは存在しませんが、完璧さと普遍性の間で良いバランスをとった建築は良い建築です。

この記事はWeChatの公開アカウント「MaGeByte」から転載したもので、以下のQRコードからフォローできます。この記事を転載する場合は、Code Byte の公開アカウントにご連絡ください。

<<:  マルチクラウド戦略の 4 つの潜在的な問題: どうすれば解決できるでしょうか?

>>:  ガートナー:クラウドは新たなデジタル体験の中心となる

推薦する

ウェブサイトのスパムを識別して対処する方法

間違いなく、ウェブサイトの内部最適化にとって、スパムコンテンツはSEOに深刻な影響を与える要素です。...

画像ショッピング検索Taotaosou:ゼロから1億円を儲けた負け組スタートアップ

文/Jincuodao(WeChat公式アカウント:ijincuodao)以前も似たような商品を手掛...

電子商取引の第一陣から脱落? Dangdangのユーザー減少は疎外のせいだと非難される

Dangdang.com の収益構造 (Tianxia.com からの画像)テンセントテクノロジーの...

ウェブサイトの掲載や外部リンクの最適化をまだ行っていますか?

Baidu 検索エンジンはユーザー エクスペリエンスをターゲットにしており、多数の Web サイトが...

#11.11# cloudcone: 1G メモリ/1 コア/40g SSD/2T トラフィックの年間 11.11 ドルからという安価な米国 VPS

Cloudcone は今年の独身の日 (11.11) に 2 つの安価な VPS を導入しました。こ...

AWS が新しい Amazon EC2 インスタンスを発表

[51CTO.com からのオリジナル記事] 本日の re:Invent カンファレンスで、AWS ...

初心者向けのヒント - 新しいサイトが検索エンジンにインデックスされない理由

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますはじめに:...

Bilibiliはどのようにして損益分岐点を達成できるのでしょうか?

ビリビリは3月3日、第4四半期および通期の業績発表を行った。第4四半期のMAUは2億7,170万人に...

BandwagonHost: すべての VPS が 10% オフ、10Gbps (米国 cn2 gia + 日本 Softbank) + 1Gbps 香港 cn2 gia

今年、Banwagong はブラックフライデーのプロモーションも実施し、全商品が 10% オフになり...

最新の百度ランキングアルゴリズム調整結果についての私の意見

検索エンジンのランキングによってもたらされる無限のビジネスチャンスにより、SEO 専門職が誕生しまし...

FilecoinがNFT分散ストレージサービスを正式に開始、セキュリティ上の脅威がさらに増大する可能性

最近、分散ストレージプロトコル Filecoin は、新しい無料サービス NFT.Storage の...

アリババクラウド、中国の農産物の「三段跳び」達成を支援するためET農業ブレインをリリース

[[232050]]天候に頼って生計を立ててきた伝統的な農業は、静かに変化しつつある。四川省の特別養...

ウェブサイトを最適化する過程で、SEO担当者は徐々に独自の最適化思考システムを確立する必要がある。

ご存知のとおり、ウェブサイトの運用とメンテナンスの最適化は体系的なプロセスです。業界やウェブサイトの...

SEOが理解すべき考え方

最近、忙しいウェブマスターや SEO 担当者の中には、Baidu の最新の青大根アルゴリズムを学び始...

AI 10,000ワードランキングシステム?? 各種検索エンジンのホームページで数百万のキーワードのランキングを実現

月給5,000~50,000のこれらのプロジェクトはあなたの将来ですキーワードの優位性は、企業のウェ...