Redis は関連する分散ロックを実装します。

分散ロックが必要な理由
複数のスレッドがオブジェクトを同時に操作する場合、synchronized を使用すると、1 つのスレッドだけが同時にオブジェクト ロックを取得し、synchronized キーワードによって変更されたコード ブロックまたはメソッドを処理できるようになります。すでに同期ロックがあるのに、なぜここで分散ロックを導入する必要があるのでしょうか?

現在のシステムは基本的に分散方式でデプロイされ、アプリケーションは複数のサーバーにデプロイされるため、同期は現在のサーバー自体のスレッド セーフを制御することしかできず、サーバー間の同時実行の安全性を制御することはできません。たとえば、下の図では、同時に同じ商品を追加する 4 つのスレッドがあります。そのうちの 2 つのスレッドはサーバー A によって処理され、他の 2 つのスレッドはサーバー B によって処理されます。 2 つのサーバーが新しいアクションを実行します。これは明らかに期待されたものではありません。
ここに画像の説明を挿入します

この記事で紹介する分散ロックは、この問題を解決するように設計されています。

分散ロックとは何ですか
? 分散ロックは、分散システム内のさまざまなプロセスを制御して、同じ共有リソースに共同でアクセスするロックの実装です。

いわゆる当局はそれに執着していますが、傍観者は明らかです。現実の例を考えてみましょう。高速鉄道を例に挙げてみましょう。各高速鉄道には独自の運行ルートがありますが、これらのルートは路線と重複する可能性があります。他の高速鉄道のルートを管理する場合、高速鉄道内の運転士のみがルートの制御を許可されている場合、運転士は他の高速鉄道のルートを知らないため、衝突事故が発生する可能性があります。そこで登場するのが中央制御室で、各高速列車を監視するのは中央制御室で、高速列車がどのような時刻にどのルートを通るかはすべて中央制御室が指示する。

分散ロックは、この考えに基づいて実装されています。グローバル ロックを監視するには、分散アプリケーションの外部にあるサードパーティ コンポーネント (データベース、Redis、Zookeeper など) を使用する必要があります。このコンポーネントは、いつロックするかを決定します。ロック。
ここに画像の説明を挿入します

Redis が分散ロックを実装する方法.
Redis が分散ロックを実装する方法について説明する前に、まず redis のコマンド setnx key value について説明する必要があります。Redis でキーを設定するために最も一般的に使用されるコマンドは次のとおりです: set key value. このコマンドは、キーが存在するかどうかに関係なく、キーの値を value に設定し、成功を返します。
ここに画像の説明を挿入します

setnx key value もキーの値を value に設定しますが、最初にキーがすでに存在するかどうかが判断されます。キーが存在しない場合は、キーの値を value に設定して 1 を返します。キーがすでに存在する場合は、キーは更新されません。値、0 を直接返します:
** 大胆なスタイル
**

● 最も単純なバージョン: setnx キーの値

setnx コマンドの特性に基づいて、最も単純な分散ロックを実装できます。setnx コマンドを Redis に送信し、Redis から返された結果が 1 であるかどうかを判断します。結果が 1 の場合、setnx が成功したことを意味し、今度はロックが取得され、ビジネス ロジックは引き続き実行できます。結果が 0 の場合は、setnx が失敗したことを意味し、今回はロックが取得されませんでした。他のクライアントがロックを解放 (キーを削除) するまでループでロックの取得を試行し続けることができ、その後、通常は setnx コマンドを使用してロックを取得します。プロセスは次のとおりです。
ここに画像の説明を挿入します

この方法は分散ロックの機能を実現していますが、キーに有効期限が設定されていないため、ロックを解除するための削除コマンドを送信する前にプログラムがクラッシュした場合、キーは Redis に永続的に保存されるという明らかな問題があります。ヒットすると、他のクライアントはこのロックを取得できなくなります。

● アップグレード バージョン: キーの有効期限を設定します。

上記の問題を解決するには、setnx キーの値に基づいてキーの有効期限を設定します。Redis はすでにそのようなコマンドを提供しています: set key value ex 秒 nx。このうち、ex 秒はキーの有効期限を秒単位で設定することを意味し、nx は set コマンドに setnx の機能があることを意味します。効果は次のとおりです。
ここに画像の説明を挿入します

name の有効期限を 60 秒に設定しており、60 秒以内に set コマンドを実行すると直接 nil が返されます。60 秒後に set コマンドを再度実行すると、正常に実行できるようになり、次のような効果が得られます。
ここに画像の説明を挿入します

この機能に基づいて、アップグレードされた分散ロック プロセスは次のようになります。
ここに画像の説明を挿入します

この方法はいくつかの問題を解決しますが、別の問題を引き起こします。つまり、ロックが誤って削除される、つまり他の人が追加したロックが解放されるという状況が発生するということです。例えば、client1はロックを取得した後に業務処理を開始しますが、業務処理に時間がかかりロックの有効期限を超えたため、業務処理が完了する前にロックの有効期限が切れて自動的に削除されてしまいます( client1 に属するロックが解放されるのと同等))、この時点で client2 はロックを取得し、独自の業務処理を実行します。この時点で client1 の業務処理は終了し、次に Redis に delete key コマンドを送信します。コマンドの後、Redis はキーを直接削除しますが、この時点ではキーは client2 に属しているため、client1 が client2 のロックを解放するのと同等です。
ここに画像の説明を挿入します

● 2 番目のアップグレード バージョン: value に一意の値を使用し、ロックを削除するときに値が現在のスレッドに属するかどうかを判断します。

上記の問題を解決する最も簡単な方法は、ロックの有効期限を業務処理時間よりも長く設定することですが、これはシステムのパフォーマンスに重大な影響を及ぼします。ロックは 1 時間に設定され、この 1 時間以内にサービスにアクセスする他のスレッドはそこでブロックされます。したがって、この方法は一般的に推奨されません。

別の解決策は、キー値 ex 秒 nx を設定するときに値を一意の値に設定することです。各スレッドの値は異なります。キーを削除する前に、まず get key コマンドで値を取得し、次に値が生成されたかどうかを判断します。独自のスレッドによって、存在する場合はキーが削除されてロックが解除されますが、存在しない場合はキーは削除されません。通常のプロセスは次のとおりです。
ここに画像の説明を挿入します

ビジネス処理が終了していない場合、キーは自動的に期限切れになり、他のスレッドに影響を与えることなく通常どおり自分のロックを解放できます。

ここに画像の説明を挿入します

2回目のアップグレード以降の計画には問題がないかのように見えますが、そうではありません。プロセスを注意深く分析した結果、ロックが現在のスレッドに属しているかどうかを判断し、ロックを解放するという 2 つのステップがアトミックな操作ではないことがわかりました。通常、スレッド 1 が Redis から get 操作で取得した値が 123 であれば、ロックは削除されますが、ロックが削除されるまでに数秒間システムが停止した場合、たまたまその数秒の間にロックが削除されます。数秒以内に、キーは自動的に期限切れになり、スレッド 2 がロックを正常に取得し、独自のロジックの実行を開始します。この時点で、スレッド 1 は遅れから回復し、ロックを削除するアクションの実行を続行します。時間はスレッド 2 です。ロックします。
ここに画像の説明を挿入します

● 最終版: Lua スクリプト

独自の Redis コマンドでは一部のビジネスのアトミックな操作を満足できないという前述の問題に対応して、Redis は Lua スクリプトのサポートを提供します。Lua スクリプトは、アトミック操作をサポートする軽量でコンパクトなスクリプト言語です。Redis は、他のリクエストによって挿入されることなく、Lua スクリプト全体を全体として実行します。したがって、Redis による Lua スクリプトの実行はアトミック操作です。

上記の処理では、キー値の取得、その値が現在のスレッドに属するかどうかの判断、ロックの削除の 3 つのステップを Lua スクリプトに記述し、それらをまとめて Redis に渡して実行します。プロセスは次のとおりです。
ここに画像の説明を挿入します

この変換により、ロック解放時の値の取得、判定値、ロックの削除といった複数段階でのアトミックな動作が保証できない問題が解決される。Lua スクリプトの構文は自分で学ぶことができ、複雑ではなく非常にシンプルなので、ここではあまり詳しく説明しません。

Lua スクリプトはロックを解除するときに使用できるので、ロックするときにも確実に使用できます。また、上記の set key value ex 秒 nx コマンドを使用してロックする場合は、繰り返し実行できないため、一般的には Lua スクリプトを使用することをお勧めします。ロックの影響は、スレッドがロックを取得すると、現在のスレッドはロックを解放するまで再度ロックを取得できなくなることです。これは明らかにシステムのパフォーマンスに影響します。この問題は、Lua スクリプトを使用して解決できます。まず、Lua スクリプトにロック (キー) が存在するかどうかを判断できます。存在する場合は、ロックを保持しているスレッドが現在のスレッドであるかどうかを判断します。存在しない場合は、ロックが失敗します。存在しない場合は、ロックが失敗します。現在のスレッド スレッドは再びロックを保持し、ロックの再エントリ数に 1 を加えます。ロックを解放する際には、まずロックを保持しているスレッドが現在のスレッドであるかどうかが判断され、ロックの再エントリ数が 0 になるまでロックの再エントリ数が -1 され、ロック (キー) が解放されます。削除することができます。
ここに画像の説明を挿入します

実際のプロジェクト開発では、基本的に上記の分散ロックの実装ロジックを自分で記述する必要はありません。代わりに、非常に成熟したサードパーティ製ツールを使用します。現時点でより人気のあるツールは Redisson です。 Redis の基本コマンドだけでなく、Redis 分散ロックのカプセル化も提供します。使用は非常に簡単で、対応するメソッドを直接呼び出すだけです。ただし、ツールは使いやすいですが、基本的な原理を理解する必要があります。これがこの記事の目的です。

おすすめ

転載: blog.csdn.net/qq_36644198/article/details/131673954