導入 なぜこの記事を書くのですか? 現在、インターネット上の Zookeeper と Redis に基づく分散ロックに関する記事のほとんどは、十分に包括的ではありません。彼らは意図的にクラスター化の状況を避けているか、あるいはすべてを考慮しておらず、読者を混乱させています。率直に言って、この古いテーマについて新しいアイデアを思いつくのは難しいです。ブロガーは非常に神経質で用心深い。記事に不正確な点がありましたら、遠慮なくご指摘ください。
このブロガーによる記事にはコードは含まれておらず、分析のみが含まれています。
(1)redisの場合、使用できるオープンソースのredisson jarパッケージがあります。 (2)ZooKeeperに関しては、オープンソースのキュレーターjarパッケージが利用できる。
すでに使用できるオープンソースの jar パッケージが存在するため、自分でカプセル化する必要はありません。外出時に API を Baidu するだけで、実装コードを大量にリストする必要はありません。
Google には Chubby と呼ばれる粗粒度の分散ロック サービスがあることに留意してください。ただし、Google Chubby はオープンソースではないため、具体的な詳細は論文やその他の関連文書を通じてのみ知ることができます。ありがたいことに、Yahoo! Zookeeper は Chubby の設計思想に基づいて開発され、オープンソース化されたため、この記事では Chubby については説明しません。 Tair は、Alibaba のオープンソース分散 KV ストレージ ソリューションです。私たちの仕事では基本的に Redis を使用することが多いため、Tair によって実装された分散ロックについて説明することは代表的ではありません。
したがって、主な分析は依然として、Redis と Zookeeper によって実装された分散ロックです。 [[236745]] 記事の構成 この記事は、外国の巨匠による2つの記事を参考にしています。 Redlock は安全ですか? 》と分散システムの専門家マーティンの 分散ロックのやり方 この記事は、”」と私自身のささやかな意見を組み合わせて作成されました。記事のディレクトリ構造は次のとおりです。
(1)なぜ分散ロックを使用するのか? (2)単一マシンシナリオの比較 (3)クラスター状況の比較 (4)その他のロック機能の比較 文章 まず結論から:
Zookeeper は Redis よりもはるかに信頼性が高いですが、効率は少し低くなります。同時実行性がそれほど大きくなく、信頼性が必要な場合は、Zookeeper が最適な選択肢です。効率性のために、redis が使用されます。
分散ロックを使用する理由は何ですか?
分散ロックを使用する目的は、共有リソースに対して同時に 1 つのクライアントのみが操作できるようにすることです。
しかし、マーティン氏は、ロックは目的に応じて次の 2 つのカテゴリに分類できると指摘しました。
(1)複数のクライアントが共有リソースを操作できるようにする。この場合、共有リソースに対する操作はべき等操作である必要があり、何度操作しても結果は変わりません。ここでロックを使用する目的は、共有リソースに対する繰り返し操作を回避し、効率を向上させることです。 (2)共有リソースを操作できるクライアントは1つだけです。この場合、共有リソースに対する操作は一般に非べき等操作になります。この場合、複数のクライアントが共有リソースを操作すると、データの不整合やデータ損失が発生する可能性があります。
*** ラウンド、シングルプレイヤーの比較
(1)レディス まずロックについてお話しましょう。 Redisの公式サイトのドキュメントの説明によると、次のコマンドを使用してロックします リソース名をランダム値に設定 NX PX 30000 - my_random_valueはクライアントによって生成されたランダムな文字列であり、クライアントがロックを保持していることを示すサインに相当します。
- NX は、resource_name に対応するキー値が存在しない場合にのみ SET が成功することを意味し、つまり、最初の要求クライアントのみがロックを取得できることを意味します。
- PX 30000 は、このロックの自動有効期限が 30 秒であることを意味します。
ロック解除に関しては、クライアント1が取得したロックがクライアント2によって解放されるのを防ぐために、次のLuaスクリプトを使用してロックを解除します。 - redis.call( "get" ,KEYS[1]) == ARGV[1]の場合
- redis.call( "del" ,KEYS[1])を返す
- それ以外
- 0を返す
このLUAスクリプトを実行すると、KEYS[1]の値はresource_nameになり、ARGV[1]の値はmy_random_valueになります。原則としては、まずロックに対応する値を取得し、それがクライアントから渡された my_random_value と等しいことを確認して、他のユーザーによってロックが解除されるのを防ぎます。さらに、Lua スクリプト操作によりアトミック性が保証されます。アトミック操作でない場合は、次の状況が発生します。 分析: この Redis のロック解除メカニズムは非常に優れているように見えますが、有効期限の設定方法という避けられない欠陥があります。クライアントが共有リソースを操作しているときに、長期間のブロックによりロックが期限切れになった場合、次回共有リソースにアクセスすることは安全ではなくなります。
しかし、一部の人はこう言うだろう
クライアントが共有リソースの操作を完了した後、ロックがまだクライアントに属しているかどうかを判断できます。まだクライアントに属している場合は、リソースが送信され、ロックが解除されます。クライアントに属していない場合、リソースは送信されません。
確かに、これを行うと、複数のクライアントが共有リソースを操作する可能性は低くなりますが、問題は解決されません。
読者が理解しやすいように、ブロガーはビジネスシナリオの例を挙げています。
ビジネスシナリオ: コンテンツ変更ページがあります。複数のクライアントが同じページの変更を要求するのを避けるために、分散ロックを使用します。ロックを取得したクライアントのみがページを変更できます。ページを変更する通常のプロセスは次のようになります。 上記のステップ(3)→ステップ(4.1)はアトミック操作ではないことに注意してください。つまり、ステップ(3)で有効フラグを返すことはできますが、送信プロセス中に遅延などの理由により、ロックがタイムアウトし、ステップ(4.1)で失敗します。そして、この時点で、ロックは別のクライアント ロックによって取得されます。 2 つのクライアントが共有リソースを共同で操作する状況があります。
考えてみてください。どのような補償策を講じても、複数のクライアントが共有リソースを操作する可能性を減らすことはできますが、回避することはできません。たとえば、ステップ (4.1) で長い GC 一時停止が発生し、一時停止中にロックがタイムアウトして無効になるため、他のクライアントによってロックが取得される可能性があります。これらについては自分で考えてみてください。 (2)動物園の飼育係
まずは原理を簡単に説明しましょう。オンラインドキュメントによると、Zookeeper の分散ロックの原則は、一時ノード (EPHEMERAL) の特性を活用します。 - znode が EPHEMERAL として宣言されている場合、znode を作成したクライアントがクラッシュすると、対応する znode は自動的に削除されます。これにより、有効期限を設定する問題を回避できます。
- クライアントは、/lock などの znode を作成しようとします。その後、最初のクライアントが正常に作成され、これはロックを取得することと同等です。他のクライアントは作成に失敗し (znode が既に存在する)、ロックの取得に失敗します。
分析: この場合、有効時間の設定の問題は回避されますが、複数のクライアントが共有リソースを操作する可能性は依然としてあります。
Zookeeper が長時間 (セッション時間) クライアントのハートビートを検出できない場合、セッションが期限切れであるとみなされ、このセッションによって作成されたすべての一時的な znode ノードが自動的に削除されることを誰もが知っておく必要があります。
このとき、次のような状況が発生します 上図のように、クライアント 1 で GC 一時停止が発生すると、Zookeeper はハートビートを検出できず、複数のクライアントが同時に共有リソースを操作する可能性もあります。もちろん、JVM をチューニングすることで GC の一時停止を回避できると言えます。ただし、私たちにできることは、複数のクライアントが共有リソースを操作することを可能な限り避けることであり、完全に排除することはできないことに注意してください。
第2ラウンド、クラスター比較
運用では通常、クラスター シナリオを使用するため、最初のラウンドでは単一マシンのシナリオについて説明しました。みんなを温めるためだよ。
(1)レディス
Redis の高可用性を確保するために、通常は Redis ノードにスレーブを接続し、マスターとスレーブの切り替えにセンチネル モードを使用します。ただし、Redis のマスター スレーブ レプリケーションは非同期であるため、データ同期プロセス中にこれが発生する可能性があります。マスターに障害が発生し、データを同期する前にスレーブがマスターとして選択され、データが失われます。具体的なプロセスは以下のとおりです。
(1)クライアント1はマスターからロックを取得する。 (2)マスターに障害が発生し、ストレージロックキーがスレーブにまだ同期されていません。 (3)スレーブがマスターにアップグレードされます。 (4)クライアント2は、新しいマスターから同じリソースに対応するロックを取得する。
この状況に対処するために、Redis の作者 antirez はRedLock アルゴリズムを提案しました。手順は次のとおりです(プロセスは公式ドキュメントから引用されています)。 N 個のマスターノードがあると仮定します (公式ドキュメントでは N は 5 に設定されていますが、3 以上になることもあります)。
(1)現在の時刻(ミリ秒単位)を取得します。 (2)同じキーとランダム値を使用して、N個のノードに順番にロックを要求します。このステップでは、クライアントが各マスターのロック要求時に、合計ロック解放時間よりもはるかに短いタイムアウト期間が発生します。たとえば、自動ロック解除時間が 10 秒の場合、各ノード ロック要求のタイムアウトは 5 ~ 50 ミリ秒の範囲になります。これにより、ダウンしたマスター ノード上でクライアントが長時間ブロックされることを防ぐことができます。マスターノードが利用できない場合は、できるだけ早く次のマスターノードを試す必要があります。 (3)クライアントは2番目のステップでロックを取得するのにかかる時間を計算します。クライアントがほとんどのマスター ノード (この場合は 3 つ) でロックを正常に取得し、消費された合計時間がロック解放時間を超えない場合にのみ、ロックが正常に取得されたと見なされます。 (4)ロックが正常に取得された場合、自動ロック解除時間は、初期ロック解除時間からロックの取得に費やされた時間を差し引いた時間になります。 (5)ロックの取得が失敗した場合、正常に取得されたロックの数が半分(N/2+1)を超えなかったか、消費された合計時間がロック解放時間を超えたため、クライアントは、正常に取得されなかったと思われるロックであっても、各マスターノード上のロックを解放します。
分析: よく考えてみると、RedLock アルゴリズムには次のような問題が残っています。
ノードがクラッシュして再起動すると、複数のクライアントがロックを保持します。
合計 5 つの Redis ノード (A、B、C、D、E) があると仮定します。次の一連のイベントを想像してください。
(1)クライアント1はA、B、Cを正常にロックし、ロックを正常に取得しました(ただし、DとEはロックされませんでした)。 (2)ノードCがクラッシュして再起動しますが、クライアント1がCに追加したロックは保持されずに失われます。 (3)ノードCが再起動した後、クライアント2はC、D、Eをロックし、ロックを正常に取得します。 このようにして、クライアント 1 とクライアント 2 は同時に (同じリソースに対して) ロックを取得します。
ノードの再起動によって引き起こされるロック障害の問題に対処するために、Redis の作者 Antirez は、遅延再起動の概念を提案しました。つまり、ノードがクラッシュした後、すぐに再起動されるのではなく、再起動する前にしばらく待機し、待機時間はロックの有効時間よりも長くなります。この方法では、再起動前にこのノードが参加していたすべてのロックが期限切れになり、再起動後の既存のロックには影響しません。これは実際には、人工的な補償措置を通じて矛盾の可能性を減らす方法でもあります。
時間ジャンプ問題
(1)Redisノードが5つあると仮定します:A、B、C、D、E。次の一連のイベントを想像してください。 (2)クライアント1はRedisノードA、B、C(ノードの過半数)からロックを正常に取得します。ネットワークの問題により、D および E との通信に失敗しました。 (3)ノードCのクロックが進み、そこで保持されていたロックがすぐに期限切れになる。 クライアント 2 は、Redis ノード C、D、E (ノードの過半数) から同じリソースのロック取得に成功しました。 これで、クライアント 1 とクライアント 2 は両方ともロックを保持していると認識します。
一定のジャンプによって引き起こされるロック失敗の問題に対処するために、Redis の作者 antirez は、システム時間の手動変更を禁止し、システム クロックを調整するために「ジャンプ」しない ntpd プログラムを使用することを提案しました。これは、人為的な補償措置による不一致の可能性を減らすためでもあります。
タイムアウトによりロックが失敗する
RedLock アルゴリズムは、共有リソース操作のタイムアウトによって発生するロック障害の問題を解決しません。次の図に示すように、RedLock アルゴリズムのプロセスを思い出してください。 図のように上部と下部の2つの部分に分けます。図の上部の手順では、遅延の理由が何であっても、RedLock アルゴリズムがそれを処理できるため、クライアントは有効だと思っていたが実際には無効なロックを取得することはありません。ただし、図の下部の手順では、遅延が発生してロックが失敗した場合、クライアント 2 がロックを取得する可能性があります。したがって、RedLock アルゴリズムではこの問題は解決されません。 (2)動物園の飼育係
クラスター展開では、Zookeeper ノードの数は一般に奇数で、3 以上である必要があります。まず、Zookeeper がデータを書き込む原理を思い出してみましょう。
写真の通り、この絵を描くのが面倒だったので、他の記事からコピーしただけです。 データの書き込み手順は次のようになります。
1. クライアントはフォロワーに書き込み要求を送信します 2. フォロワーがリーダーにリクエストを送信する 3. リーダーはメッセージを受信後、投票を開始し、フォロワーに投票するよう通知します。 4. 投票者は投票結果をリーダーに送信します。半数以上が ACK メッセージを返せば合格とみなされます。 5. リーダーは結果を要約した後、書き込みが必要な場合は書き込みを開始し、書き込み操作をリーダーに通知してコミットします。 6. フォロワーはリクエスト結果をクライアントに返す
もう 1 つのポイントは、Zookeeper がグローバル シリアル化操作を採用していることです。
さて、それでは分析を始めましょう。
クラスター同期
クライアントは Follwer にデータを書き込みますが、Follwer がクラッシュします。データの不整合は発生しますか?不可能。この時点で、クライアントはノードを確立できず、ロックをまったく取得できません。 クライアントはフォロワーにデータを書き込み、フォロワーはリーダーにリクエストを転送します。リーダーがクラッシュした場合、不整合の問題が発生しますか?不可能。この時点で、Zookeeper は新しいリーダーを選択し、上記の書き込みプロセスを続行します。
つまり、分散ロックとして Zookeeper を使用する場合、ロックを取得できないか、ロックを取得した後はノードのデータが一貫している必要があり、Redis のように非同期同期によってデータが失われる問題は発生しません。
時間ジャンプ問題
地球時間に頼らずに、どうしてそのような問題が存在するのでしょうか?
タイムアウトによりロックが失敗する
有効時間に依存せずに、なぜこのような問題が発生するのでしょうか?
第3ラウンド: その他のロック機能の比較
(1)Redisの読み取りおよび書き込みパフォーマンスはZookeeperよりもはるかに優れています。高同時実行シナリオで Zookeeper を分散ロックとして使用すると、ロックの取得が失敗し、パフォーマンスのボトルネックが発生する可能性があります。 (2)Zookeeperは読み取り書き込みロックを実装できますが、Redisは実装できません。 (3)Zookeeperの監視メカニズム:クライアントがznodeを作成しようとすると、それがすでに存在していることがわかります。この場合、作成は失敗し、クライアントは待機状態になります。 znode が削除されると、Zookeeper は監視メカニズムを通じてそれを通知し、作成操作の完了 (ロックの取得) を続行できるようにします。これにより、分散ロックをローカル ロックと同じようにクライアントが使用できるようになります。ロックが失敗した場合は、ロックが取得されるまでブロックされます。 Redis はこのメカニズムを実装できません。 要約する さて、本文は意味不明な内容が満載です。実のところ、私は2つの点を指摘したいだけです。 Redis であれ Zookeeper であれ、信頼性に関しては実際にいくつか問題があります。ただし、Zookeeper の分散ロックの信頼性は Redis よりもはるかに強力です。しかし、Zookeeper の読み取りおよび書き込みパフォーマンスは Redis ほど良くなく、パフォーマンスのボトルネックがあります。実際に本番環境で使用して、ご自身で評価することができます。 |