Redis ベースの分散ロックは誰もが知っているものですが、分散ロックが失敗したことはありますか?障害が発生したとき、使用している分散ロックが本当に信頼できるかどうか疑問に思ったことはありませんか?以下は私自身の経験に基づいてまとめたものです。
画像はPexelsより 分散ロックは本当に必要ですか? 分散ロックの使用は、複数のプロセスが同じリソースにアクセスするという問題があることを示しています。 一般的に、同じリソースへの繰り返しアクセスは、次の 2 つのシナリオで防止されます。
分散ロックの導入には、必然的に MySQL、Redis、Zookeeper などのサードパーティ インフラストラクチャの導入が必要になります。 分散ロックを実装するためのインフラに問題があれば業務にも影響が出るため、分散ロックを使用する前に、ロックなしで実装できるかどうかを検討してみてはいかがでしょうか。 ただし、これはこの記事の範囲外です。この記事では、ロックの必要性が合理的であると想定し、上記の 2 番目のケースを優先する傾向があります。なぜ? 100% 信頼できる分散ロックは存在しないからです。以下の内容を読めば理解できるでしょう。 まずは簡単な分散ロックの実装から始めましょう 分散ロックの Redis 実装は非常に一般的です。自分で実装したり、サードパーティのライブラリを使用したりするのは、少なくともそう思えるほど、非常に簡単です。ここでは、最もシンプルで信頼性の高い Redis 実装を紹介します。 最もシンプルな実装 実装は非常に古典的ですが、重要なポイントは 2 つあります。
コピー&ペースト可能な実装は次のとおりです。 ロック:
ここで、SET キー値 PX milliseoncds NX が呼び出されます。このコマンドがわからない場合は、SET キー値 [EX 秒|PX ミリ秒] [NX|XX] [KEEPTTL] を参照してください。
ロック解除:
この実装の本質は、まず一意の ID が等しいかどうかを判断し、次に操作を実行する単純な Lua スクリプトにあります。 それは信頼できるでしょうか? この実装の問題は何でしょうか?
これら2つの問題をどのように解決すればよいでしょうか?もっと複雑な実装を試してみましょう。 レッドロックアルゴリズム 最初の単一点の問題について、Redis のアイデアに続いて、次に思い浮かぶのは間違いなく Redlock です。 単一マシン問題を解決するために、Redlock には複数 (2 つ以上) の Redis マスター ノードが必要です。複数のマスターノードは互いに独立しており、データの同期はありません。 Redlock の実装は次のとおりです。 ①現在の時刻を取得します。 ② N個のノードのロックを順に取得する。各ノードをロックする実装方法は上記と同様です。ここで細かい点として、ロックを取得するたびに有効期限が異なり、前回のロック取得操作で消費された時間を差し引く必要があるという点があります。 たとえば、渡されたロックの有効期限が 500 ミリ秒で、最初のノードのロックを取得するのに 1 ミリ秒かかる場合、最初のノードのロック有効期限は 499 ミリ秒になります。 2 番目のノードのロックを取得するのに 2 ミリ秒かかる場合、2 番目のノードのロックの有効期限は 497 ミリ秒になります。 ロックの有効期限が 0 以下の場合、ロックを取得する操作全体がタイムアウトし、操作全体が失敗したことを意味します。 ③ロックが正常に取得できたかどうかを判定します。上記の手順でクライアントが (N/2+1) 個のノード ロックを取得し、各ロックの有効期限が 0 より大きい場合、ロックの取得は成功し、それ以外の場合は失敗します。失敗した場合はロックを解除します。 ④ロックを解除します。ロック解除指示をすべてのノードに送信します。各ノードの実装ロジックは上記の単純な実装と同じです。 なぜすべてのノードで操作する必要があるのですか?分散シナリオでは、ノードからロックを取得できなかったとしても、そのノードでアクセラレーションが失敗したことを意味するわけではありません。ロックは実際には正常に取得された可能性がありますが、ネットワーク ジッタにより戻りタイムアウトが発生しました。 上記は一般的な Redlock 実装の説明です。一見すると、マルチマスター版の簡易版のように見えます。もしこれが本当なら、それはあまりにも単純すぎるでしょう。次に、さまざまなシナリオでこのアルゴリズムがどのように破られるかを分析してみましょう。 分散ロックの落とし穴 同時実行性の高いシナリオにおける問題 以下の問題は、同時実行性の低いシナリオでは発生しない可能性は低いですが、同時実行性が高いシナリオでは発生する可能性が高くなります。 パフォーマンスの問題は、次の 2 つの側面から発生します。 ① ロックを取得する時間。 Redlock が高同時実行シナリオで使用される場合、N 個のマスター ノードが存在します。 1 つずつリクエストすると時間がかかり、パフォーマンスに影響します。 この問題は簡単に解決できます。以上の説明から、複数のノードからロックを取得する操作は同期操作ではなく、非同期操作であり、複数のノードが同時にロックを取得できることが容易にわかります。 並列処理される場合でも、ロック TTL > ロック取得時間 + タスク処理時間となるように、ロック取得時間を見積もる必要があります。 ②ロックされたリソースが大きすぎます。ロック ソリューション自体は、正確性のために同時実行性を犠牲にし、その犠牲はリソースのサイズに比例します。この時点で、リソースを分割することを検討できます。 分割方法は 2 つあります。 ① ビジネスの観点から、ロックされたリソースを複数のセグメントに分割し、各セグメントを個別にロックします。たとえば、マーチャントに対して複数の操作を実行し、操作の前にマーチャントをロックする必要がある場合、操作を複数の独立したステップに分割し、個別にロックして同時実行性を向上させることができます。 ② バケット化のアイデアを使用して、リソースを複数のバケットに分割します。 1 つのロックが失敗した場合は、すぐに次のロックを試してください。たとえば、バッチタスク処理のシナリオでは、200 万の加盟店のタスクを処理するために、処理速度を上げるために複数のスレッドが使用され、各スレッドで 100 の加盟店を処理し、これらの 100 の加盟店をロックする必要があります。 これを処理しないと、2 つのスレッドによって同時にロックされるマーチャントが重複していないことを確認することが難しくなります。この場合、1 つの次元を使用できます。 たとえば、あるタグに対して、マーチャントがバケットに分割され、1 つのタスクが 1 つのバケットを処理します。このバケットを処理した後、競合を減らすために次のバケットが処理されます。 再試行の問題: 単純な実装でも Redlock 実装でも、再試行ロジックが存在します。 上記のアルゴリズムを直接実装すると、ほぼ同時に同じロックを取得するクライアントが複数存在し、各クライアントは一部のノードをロックしますが、大多数のノードを取得するクライアントは存在しません。 解決策も非常に一般的で、再試行時間にランダムな時間を追加して、再試行中に複数のノードをずらすというものです。これによって問題が完全に解決されるわけではありませんが、問題を効果的に軽減することができます。試してみて効果があることが証明されました。 ノードのダウンタイム 永続性のない単一のマスターノードの場合、ダウンするとシステムがクラッシュします。この場合、実装では繰り返し操作をサポートし、冪等性を確保する必要があります。 Redlock などのマルチマスター シナリオでは、次のようなシナリオを見てみましょう。
どうすれば解決できるでしょうか?最も簡単な解決策は、永続性をオンにすることです。永続性により、すべての Redis コマンドを永続化できますが、これはパフォーマンスに大きな影響を与えるため、通常は使用されません。この方法を使用しないと、ノードがハングしたときに少量のデータが確実に失われ、その中にロックが含まれる可能性があります。 もう一つの選択肢は開始を遅らせることです。つまり、ノードがクラッシュして修復された後、すぐに追加されるのではなく、参加する前にしばらく待機します。待機時間は、クラッシュの瞬間のすべてのロックの最大 TTL よりも大きくする必要があります。 しかし、この解決策でも問題は解決できません。上記のステップ 3 で B と C の両方が失敗した場合はどうなりますか?すると、残るノードは A、D、E の 3 つだけになります。D と E からロックを正常に取得できれば十分ですが、それでも問題は発生します。 この問題を緩和する唯一の方法は、マスターノードの総数を増やすことです。マスターノードを追加すると安定性は向上しますが、コストも増加するため、両者のバランスを取る必要があります。 タスク実行時間がロックTTLを超える 以前は、ネットワークの遅延、ロックの期限切れ、タスクが複数のスレッドによって実行されることなどにより、生産ラインでタスクの実行時間が予想を超える状況がありました。 この問題は、Zookeeper や DB に基づくものも含め、すべての分散ロックで発生します。これは、ロックの有効期限と、クライアントがロックの有効期限が切れたことを認識していないこととの間の矛盾です。 セッションをロックする場合、通常は、クライアントがクラッシュしてロックが解除されるのを防ぐために、ロックの TTL を設定します。 ただし、このような使用法はすべて同じ問題に直面します。つまり、クライアントの実行時間がロックの TTL よりも短くなるという保証がないということです。 ほとんどのプログラマーは楽観的で、このような状況はあり得ないと考えていますが、私もかつてはそう思っていましたが、現実に何度も打ちのめされるまではそう思っていました。 Martin Kleppmann 氏もこれに疑問を呈していたので、彼の写真をそのまま使用しましょう。
Martin Kleppmann 氏は GC の例を挙げましたが、ネットワーク遅延の状況に遭遇しました。どのような状況であっても、この状況は避けられないことは否定できず、一度発生すると混乱しやすくなります。 この問題を解決するにはどうすればいいでしょうか? 1 つの解決策は、TTL を設定するのではなく、ロックを正常に取得した後にロックにウォッチドッグを追加することです。ウォッチドッグはタイマー タスクを開始し、ロックが解放されずに期限切れになりそうなときにロックを更新します。 これは少し抽象的なので、Redisson のソースコードと合わせて説明しましょう。
Redisson でよく使用されるロック API は上記の 2 つです。 1つはTTLで渡さないことです。この場合、Redisson が自らメンテナンスを行い、自動的に更新します。 もう 1 つの方法は、TTL を自分で渡すことです。この場合、Redisson はリースを自動的に更新しません。または、leaseTime の値を -1 として渡すことができます。ただし、この方法はお勧めできません。すでに API が存在するのに、なぜこのような奇妙な書き方を使用するのでしょうか? 次に、パラメータを渡さないメソッドのロック ロジックを分析します。
ご覧のとおり、最終的なロック ロジックは org.redisson.RedissonLock#tryAcquireAsync に入ります。ロックの取得に成功すると、scheduleExpirationRenewal に入ります。 ここでタイマーが初期化され、遅延時間は internalLockLeaseTime/3 になります。 Redisson では、internalLockLeaseTime は 30 秒です。つまり、10 秒ごとに 30 秒間更新されます。 分散ロックが Zookeeper に基づいて実装されている場合、Zookeeper を使用してノードが生きているかどうかを確認し、更新を実現できます。私は Zookeeper 分散ロックを使用したことがないので、詳細には触れません。 ただし、この方法では、同時に 1 つのクライアントだけがロックを取得することを保証することはできません。更新が失敗した場合、たとえば Martin Kleppmann が言及した STW GC が発生したり、クライアントが Redis クラスターとの接続を失ったりすると、更新が失敗する限り、複数のクライアントが同時にロックを取得します。 私のシナリオでは、ロックの粒度を下げましたが、Redisson の更新メカニズムで十分でした。より厳密にしたい場合は、更新が失敗した場合にタスクを終了するロジックを追加する必要があります。 このアプローチは以前 Python コードで実装されていましたが、Java ではこのような厳しい状況に遭遇したことはありませんでした。 ここで Martin Kleppmann の解決策についても触れておきたいと思います。個人的には、この解決策は信頼できないと思いますが、その理由については後で説明します。 彼の解決策は、ロックされたリソースに独自の保証セットを維持させ、ロックの失敗により複数のクライアントが同じリソースに同時にアクセスするのを防ぐことです。 クライアントがロックを取得すると、リソース トークンも取得します。このトークンは単調に増加します。リソースが書き込まれるたびに、現在のトークンが古いトークンであるかどうかがチェックされます。そうであれば、書き込みは許可されません。 上記のシナリオでは、クライアント 1 がロックを取得するとトークン 33 が割り当てられ、クライアント 2 がロックを取得するとトークン 34 が割り当てられます。 クライアント 1 の GC 中に、クライアント 2 はすでにリソースを書き込み済みです。この時点で、最大のトークンは 34 です。クライアント 1 が GC から戻ってトークン 33 を使用してリソースを書き込もうとすると、トークンの有効期限が切れているため拒否されます。 このアプローチでは、リソースがトークン ジェネレーターを提供する必要があります。このフェンスソリューションについていくつか質問があります。 ①取引を保証するものではありません。図ではストレージへのアクセスは 34 回のみ示されていますが、実際のシナリオでは、タスク内でストレージに複数回アクセスされる可能性があり、これらのアクセスはアトミックである必要があります。 GC の前にクライアント 1 がトークン 33 を使用してストレージに 1 回アクセスすると、GC が発生します。 クライアント 2 はロックを取得し、トークン 34 を使用してストレージにアクセスします。この時点で、2 つのクライアントによって書き込まれたデータが正しいことが保証されますか? そうでない場合、ストレージ自体にトランザクション メカニズムなど、それを保証する他のメカニズムがない限り、このソリューションには欠陥があります。可能であれば、ここでのトークンは冗長であり、フェンシング ソリューションは不要です。 ②同時実行性の高いシナリオでは実用的ではありません。毎回書き込めるのは最大のトークンだけなので、ストレージへのアクセスは線形です。同時実行性の高いシナリオでは、このアプローチによりスループットが大幅に制限されます。分散ロックは主にこのようなシナリオで使用されますが、これは非常に矛盾した設計です。 ③これはすべての分散ロックに共通する問題です。このソリューションは一般的なソリューションであり、Redlock または他のロックで使用できます。したがって、これは Redlock とはまったく関係のない単なる解決策であると理解しています。 システムクロックのドリフト この問題は検討されただけで、実際のプロジェクトでは発生していません。理論的には可能なので、ここでもお話しします。 Redis の有効期限はシステム クロックに依存します。クロックのドリフトが大きすぎると、有効期限の計算に影響します。 システム クロックがドリフトするのはなぜですか?システム時間について簡単に説明しましょう。 Linux では、clock realtime と clock monotonic の 2 つのシステム時間を提供しています。 クロックリアルタイムは、xtime/ウォールタイムとも呼ばれます。この時間はユーザーと NTP によって変更できます。 gettimeofday はこの時間を使用し、Redis も有効期限の計算にこの時間を使用します。 クロック モノトニック (文字通り単調な時間として翻訳されます) はユーザーによって変更されませんが、NTP によって変更されます。 理想的なケースでは、すべてのシステム クロックが常に NTP サーバーと同期されますが、これは明らかに不可能です。 システム クロックのドリフトが発生する原因は 2 つあります。
残念ながら、Redis はこの時間を使用します。 Redis 5.0 のソースコードを調べたところ、依然としてクロックリアルタイムが使用されていることがわかりました。 Antirez はクロック モノトニックに変更するように言いましたが、ボスはまだ変更していません。つまり、Redis サーバーの時間を手動で変更すると、Redis に問題が発生する可能性があります。 要約する この記事では、シンプルな Redis ベースの分散ロックから、より複雑な Redlock 実装までを説明し、分散ロックの使用過程で遭遇する落とし穴と解決策をいくつか紹介します。 著者: 陳漢里 紹介: 本業を欠かさないプログラマー。私は物流財務グループ、物流ターミナル事業グループ、圧力バランスグループで便利屋として働いてきました。私は Python から Java までのテクノロジー スタックを学習しましたが、ビジネス コードをうまく記述する方法をまだ学習していません。私は抽象モデルを使用してビジネスを災害から救うことを夢見ています。 編集者:タオ・ジアロン 出典: Ele.me 物流技術チーム |
>>: QingCloudはCITIC NetworkおよびIntone Technologyと戦略的提携を結び、新たなインフラの波に乗り出す
2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますA5 St...
1. リンクを追加する自社のウェブサイトへのリンクを含めることで、効果的な外部リンクが増え、検索エン...
10月31日午前、杭州雲棲鎮で2023年雲棲会議が開幕した。アリババグループのジョセフ・ツァイ会長は...
budgetvm(アメリカの会社、2004年~)は、10Gbpsの帯域幅にアクセスできる、月額99ド...
hosthatch からの最新ニュース: [1] メモリが「言葉では言い表せないほど」倍増 + 20...
26日の夜、Baiduが大混乱に陥りました。皆さんもご存知のとおり、大小さまざまなウェブサイトがさま...
疫病との闘いは終わっていないが、教育は始まった。 UCloudは、感染予防・抑制期間中に「授業は中止...
1. HPA PaaSとは注: HP APaaS には、ゼロ コード、ロー コード プラットフォーム...
Hostyunは、香港の最新のVPS、香港のT3レベルのコンピュータルーム、鶏のための直接10G帯域...
ultravps.com は fiberhub のサイトであることは、おそらく多くの人が知っているで...
NetEase Technology News、6月2日、ロイター通信によると、スウェーデン警察は土...
2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますインターネ...
Dignusdataは、ウェブサイト証明書(Dignus Data DOOEL[MK])から判断する...
百度と今日頭条が再び主要メディアの見出しを飾った。彼らが最後に一緒にいたのは4月26日だった。両者は...
【はじめに】垂直電子商取引は100メートル短距離走というよりマラソンのようなもので、規模を追求するゲ...