今日は、Redis ベースの分散ロックをプロジェクトに統合します。分散ロックは常に皆の注目を集めてきました。仕事でも面接でも、分散ロックは常に話題になります。誰もがこのスキルを習得して、将来昇進したりお金を稼いだりできるようになることを願っています。 分散ロック 背景 分散ロックはなぜ必要なのでしょうか? synchronized、ReantrantLock、その他の関連ロックはすでに存在していませんか? はい、アプリケーションを開発するときに、共有変数へのマルチスレッド アクセスを同期する必要がある場合、学習したロックを使用してそれを処理することができ、バグなしで完璧に実行できます。 注: これはスタンドアロン アプリケーションです。ビジネスが発展するにつれて、クラスタリングが必要になります。アプリケーションを複数のマシンにデプロイし、負荷分散する必要があります。 上図からわかるように、変数 A は 3 つのサーバーのメモリ内に存在します (この変数 A は主にクラス内のメンバー変数として反映され、ステートフル オブジェクトです)。制御が追加されない場合、変数 A は同時にメモリの一部を割り当てます。この変数を操作するために 3 つのリクエストが同時に送信されます。明らかに、結果は間違っています!同時に送信されない場合でも、3 つの要求はそれぞれ 3 つの異なるメモリ領域のデータに対して操作を行います。変数A間で共有がなく、可視性もないので、処理結果も間違っています! 私たちのビジネスにこのようなシナリオが存在する場合、この問題を解決する方法が必要です。 同時実行性の高い状況でメソッドまたはプロパティが同じスレッドによってのみ同時に実行されるようにするために、単一アプリケーションの従来の単一マシン展開の場合、同時処理関連の関数を使用して相互排他制御を行うことができます。しかし、ビジネス ニーズの進化に伴い、当初の単一マシン展開システムは分散クラスター システムへと進化しました。分散システムはマルチスレッド、マルチプロセスであり、異なるマシンに分散されているため、元の単一マシン展開の同時実行制御ロック戦略は無効になります。単純なアプリケーションでは分散ロック機能を提供できません。この問題を解決するには、共有リソースへのアクセスを制御するマシン間の排他制御メカニズムが必要です。これは分散ロックが解決する必要がある問題です。 分散ロックの基本原理 分散環境において、複数のマシン上の複数のプロセスが同じ共有リソース(データ、ファイルなど)を操作する場合、相互排他制御が行われないと、残高がマイナスになったり、商品が売れ過ぎたりすることがあります。 この問題を解決するには、分散ロック サービスが必要です。まず、分散ロックに必要な条件を見てみましょう。
現在、市場には 3 種類の分散ロック実装ソリューションが存在します。
プロジェクトでは、後者の 2 つが最もよく使用されます。実際、それぞれのソリューションには独自の長所と短所があります。 デッドロック防止 次の典型的なデッドロックのシナリオを見てみましょう。 クライアントはロックを正常に取得しましたが、ロックを解放する前にクラッシュしました。この時点で、クライアントは実際にパブリック リソースを操作する権限を失っていますが、ロック解除 (キーと値のペアの削除) を要求する方法はありません。すると、常にロックが保持され、他のクライアントはロックを取得できなくなります。 私たちの解決策は、ロックするときにロックの有効期限を設定することです。有効期限に達すると、Redis はデッドロックを回避するために、対応するキー値を自動的に削除します。この有効期限は、ロック保有者が有効期限内に関連する操作を完了し、ロックを解除できることを保証するために、特定のビジネスの包括的な評価と組み合わせて設定する必要があることに注意してください。 また、前述の通り、実際のプロジェクトでは後者の 2 つのソリューションを使用しているため、後者の 2 つのソリューションに重点を置いています。この記事は、私たちが以前実施したプロジェクトに基づいていることに注意してください。同時に、Redis がプロジェクトに統合されたため、この記事の分散ロックは Redis の実装に基づいています。 Redis ベースの分散ロックの実装 まず、Redis を使用してソリューションを実装するというアイデアについて説明します。
シンプルバージョン 分散ロックの作成
このようにして、単純な分散ロックが実装されますが、ここで問題が発生します。問題は必ずしも発生するとは限りませんが、ある瞬間には発生します。 次に、この分散ロックをユーザーアカウント残高を差し引く機能に適用します。 主に userId と balance を含むユーザー アカウント テーブルを作成しましょう。
次に、UserAccountRepository インターフェースを作成します。
UserAccountService と実装クラスを作成し、その推論メソッドを実装します。
この時点で、分散ロックの簡易バージョンとその使用方法は完了です。以下のアイデアを整理してみましょう。 1. setnx (set not existing) を使用します。これは、設定されたキーが Redis に存在しない場合は true を返し、存在する場合は false を返すことを意味します。 2. 設定したキーの有効期間を設定し、expire を使用して有効期間を設定します。 3. 控除に利用可能な残高が十分かどうかを確認します。そうでない場合は、プロセスを直接終了し、delete を使用して Redis 内のキーを削除します。 4. 残高を差し引いて、データベースの残高値を更新します。 5. キーを削除します。Redis のキーを削除します。 それで、ここで疑問が生じます。最初のステップと 2 番目のステップは両方とも成功しました。しかし、残高を照会して残高を差し引く 3 番目のステップに 20 秒かかる場合、上記の Redis のキーの有効期間として設定した 10 秒は、タイムアウトになり、キーの有効期限が切れ、10 秒から 20 秒の間に他のスレッドがロックを取得したことを意味します。その後、他のスレッドによって取得されたロックは削除され、他のスレッドのロックは解放されます。今は混乱していませんか? これは面接でもよく聞かれる質問です。分散ロックに Redis を使用しているときにビジネスがタイムアウトになった場合はどうすればいいでしょうか? アップグレード版 各キーに対応する値を返し、ロックが解除されたときに、現在のキーに対応する値が現在保持されている値であるかどうかを判断できます。 その後、上記のバージョンを修正しました。
ここでは、ロックを保持しているスレッドが現在のスレッドであるかどうかを判断するという、単純なバージョンよりももう 1 つの判断があります。現在のスレッドであるかどうかを判断するためにランダム文字列の値が使用されますが、ロックを解除するときにアトミック操作を実行することは依然として不可能です (delete メソッド)。たとえば、プロセス A がビジネス ロジックの実行を終了し、ロックを解放しようとすると、プロセス A のロックは自動的に期限切れになり、別のプロセス B がロックを正常に取得します。 B が実行する前に、プロセス A が delete(key) を実行し、プロセス B のロックを解除します。そのため、ロックを解除するには Lua スクリプトと連携する必要があります。 setnx+Lua スクリプト Lua は、標準 C 言語で記述され、ソース コード形式で公開されている軽量でコンパクトなスクリプト言語です。アプリケーションに組み込むように設計されており、アプリケーションの柔軟な拡張性とカスタマイズ機能を提供します。 Lua は対話型プログラミング モードを提供します。コマンドラインでプログラムを入力すると、その効果がすぐにわかります。 Lua スクリプトの利点:
リソース ディレクトリに redis-lock.lua ファイルを作成します。内容を入力してください:
このコードは何を意味するのでしょうか。つまり、キーを通じて Redis の値を取得し、それを現在のスレッドの値と比較します。同じ場合は、Redis 内のキーを削除します。削除された場合は 1 を返し、それ以外の場合は何も行われなかったことを示す 0 を返します。 Redis ロック コード ブロックは次のとおりです。
最後に、罰金の控除を再度実行すると、ログ出力は次のようになります。
これまで、setnx+Lua ソリューションを実装してきました。これについて疑問があるなら、それは良いことです。問題が発生しないかどうかを確認するために、罰金控除サービス メソッドを呼び出す複数のスレッドを作成することをお勧めします。 "知らせ" setnx は redis の以前のバージョンには存在せず、後で導入されました。実際、set コマンドを使用して setnx 問題を解決することもできます。さらに、有効期限を追加することもできます。全体的なコマンドは
値はランダムな文字列であることが望ましいです。これにより、ビジネス コードの実行時間が設定されたロックの自動有効期限を超え、ロックが再度解除されたときに他のプロセス ロックが解除されることを防ぐことができます。 setnx lock の最大の欠点は、ロック時に 1 つの Redis ノードでのみ機能することです。 Redis が Sentinel を使用して高可用性を確保している場合でも、何らかの理由でマスター ノードがマスターからスレーブに切り替わると、ロックは失われます。次に例を示します。
時々、単に鍵を紛失するだけという単純な問題ではないことがあります。新しく選出されたマスター ノードは同じロックを再度取得できるため、ロックが 2 回取得されるシナリオが発生します。 このことから、ロックが 2 回取得されると、セキュリティが確実に満たされないことがわかります。 最初の 2 つのソリューションはあまり満足できるものではなく、常に何らかの問題を抱えていますが、一部の企業では採用されています。次に、Redis に基づく分散ロックのより高度なバージョンを見てみましょう。 高度なRedisson + RedLock Redisson は Java 用の Redis クライアントの 1 つであり、Java で分散ロックを実装するために Redis 公式 Web サイトによって推奨されているプロジェクトです。 Redisson は、Redis の操作を容易にするためのいくつかの API を提供します。この記事は主にロックについて書かれているため、ロック関連のクラスに焦点を当てます。 Redisson が提供するさまざまなロックは次のとおりです。
つまり、理解しているかどうかにかかわらず、Redisson は一連のロックを提供します...これは、ほとんどの企業が Redis 分散ロックを使用する最も一般的な方法でもあります。 ロックおよびロック解除コードの全体的な構造は次のとおりです。
実際、磁気層のロックとロック解除も Lua スクリプトを使用して実装されています。興味のある友人は、デバイスの基盤となるソースコードを調べることができます。 スペースの制約により、Redis クラスターも関係するため、ロックとロック解除のフローチャートは参考としてのみここに示します。 「ロック処理」 ロック解除プロセス 要約する Redis の高可用性を実現するために、通常は、Redis ノードに 1 つ以上のスレーブ ノードをマウントし、マスターとスレーブの切り替えにセンチネル モードを使用するなど、Redis クラスター モードを構築します。ただし、Redis のマスター スレーブ モードは非同期であるため、データ同期中にマスター ノードがクラッシュし、データ同期に時間をかける前にスレーブ ノードがマスター ノードとして選出され、データが失われる可能性があります。一般的なプロセスは次のとおりです。
さて、この問題を解決するために、Redis の作者は RedLock アルゴリズムを提案しました。手順は次のとおりです (5 つのステップ)。 次の例では、同時にクラッシュしないように、それぞれ 5 台のサーバー上で実行される 5 つの完全に独立した Redis マスター ノードがあると想定しています。 現在の Unix 時間をミリ秒単位で取得します。 同じキーとランダム値を使用して、N 個のインスタンスから順番にロックを取得しようとします。ステップ 2 で、Redis にロックを設定する場合、クライアントは、ロックの有効期限よりも短いネットワーク接続と応答のタイムアウトを設定する必要があります。たとえば、ロックの自動有効期限が 10 秒の場合、タイムアウトは 5 ~ 50 ミリ秒にする必要があります。これにより、サーバー側の Redis がすでにクラッシュしているにもかかわらず、クライアントが応答結果を待機している状況を回避できます。サーバーが指定された時間内に応答しない場合、クライアントはできるだけ早く別の Redis インスタンスを試す必要があります。 クライアントは、現在の時刻からロックが取得された時刻 (手順 1 で記録された時刻) を引いて、ロックを取得するために使用された時刻を取得します。ロックが Redis ノードの過半数 (ここでは 3 つのノード) から取得され、使用された時間がロックの有効期限よりも短い場合にのみ、ロックが正常に取得されたと見なされます。 ロックが取得された場合、キーの実際の有効期間は、有効期間からロックの取得に使用された時間を差し引いたものになります (手順 3 で計算された結果)。 何らかの理由でロックの取得に失敗した場合 (少なくとも N/2+1 個の Redis インスタンスでロックが取得されていないか、ロックの取得時間が有効時間を超えている場合)、クライアントはすべての Redis インスタンスのロックを解除する必要があります (一部の Redis インスタンスが正常にロックされていない場合でも)。 この時点で、基本的に、ほとんどの Redis ノードが正常に動作できる限り、Redlock の正常な動作が保証されることがわかります。これにより、これまでのシングルポイント Redis の場合に発生していた非同期通信によるノード障害やロック障害の問題を解決できます。 しかし、慎重に検討した結果、Redlock には依然として以下の問題が残っています。 合計 5 つの Redis ノード (A、B、C、D、E) があると仮定します。次の一連のイベントを想像してください。
残念ながら、障害再起動によって引き起こされるロック セキュリティの問題はまだ解決できません... ノードの再起動によって発生するロック失敗の問題に対応して、Redis の作者は遅延再起動の概念を提案しました。基本的に、ノードがクラッシュした後はすぐに再起動せず、一定時間待ってから再起動します。待機時間はロックの有効期限よりも長くする必要があります。この方法では、再起動前にノードに関係するすべてのロックが期限切れになっていることが保証されます。再起動を遅らせるとこの問題は解決するようです... しかし、まだ問題が残っています。ノードが再起動された後、待機期間中は外部的には動作しません。その後、ほとんどのノードがダウンして待機状態になると、有効期限内にロックを正常にロックできないため、システムは使用できなくなります。 つまり、Redis 分散ロックの実装には解決すべき問題がまだ多く残っています。これらの問題を認識し、Redis 分散ロックを正しく実装する方法を理解した上で、作業時に分散ロックを合理的に選択して正しく使用する必要があります。 しかし、なぜ一部の企業は分散ロックを実装するために Redis を使用するのでしょうか?実際、分散ロックを実装する場合、ミドルウェアから Zookeeper を選択することもできます。Zookeeper は Redis よりもはるかに信頼性が高いですが、効率は少し低くなります。同時実行性がそれほど大きくなく、信頼性が重要な場合は、Zookeeper が間違いなく第一の選択肢です。 分散ロックの実装に関しては、絶対的に良いとか悪いとかはなく、最善の解決策はなく、ビジネスに最適な解決策があるだけです。 次の 2 つの解決策は参考用です。
この記事はWeChat公開アカウント「Java Backend Technology Full Stack」から転載したものです。以下のQRコードからフォローできます。この記事を転載する場合は、Java Backend Technology Full Stack パブリック アカウントにお問い合わせください。 |
<<: IBM Cloud Pak for Automation が ENN グループのハイパーオートメーション化を支援
>>: エッジ コンピューティングとクラウド コンピューティング: どちらがより効率的ですか?
2012年8月23日はウェブマスターにとって哀悼の日です。この日、Baiduが検索エンジンを調整し、...
オランダの老舗「LiteServer BV」が創業15周年を迎えました。これを記念して、NVMe S...
virmach は、OVZ と Windows、純粋な SSD ハード ドライブを含む、通常価格の ...
企業が何を構築しているかに関係なく、ある時点で、デバイスは重要な計算をクラウドで実行するべきか、それ...
2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています最近、中国...
安価なアメリカVPSや高速なアメリカVPSなど、アメリカVPS業者は数多く存在しますが、長期的に運用...
[[273746]] 1 はじめに近年、国際的な情報セキュリティ情勢が厳しさを増す中、国家の情報セ...
世の中にはさまざまなタイプのウェブサイトがあります。どのようなタイプのウェブサイトであっても、オンサ...
raksmartは8月から今月15日まで、西海岸(サンノゼ)の独立データセンターで、253IPクラス...
セグメント化された業界ウェブサイトは、ビジネスを始めたり、独自のウェブサイトを構築したりするための方...
クラウド コンピューティング テクノロジーがデジタル変革に利用されていることは周知の事実です。実際、...
9月11日、第6回CSSインターネットセキュリティリーダーサミット業界特別セッションが正式にオンライ...
2021年6月23日、徐州市人民政府が主催する「2021年中国(徐州)第5回人工知能会議及びデジタル...
10g.bizは現在、大規模なディスカウントキャンペーンを実施しています:(1)香港と米国の自社デー...
この記事は、ゲームの種類と特徴を分析することで、ユーザーのモチベーションを高めるゲーミフィケーション...