Redis 分散ロックを使用して完全性を確保するにはどうすればよいでしょうか?

Redis 分散ロックを使用して完全性を確保するにはどうすればよいでしょうか?

[[375093]]

著者について

Leng Zhenglei は2018 年 2 月に Qunar.com DBA チームに加わりました。彼は主に、航空券業務の MySQL および Redis データベースの運用と保守管理、およびデータベース自動化運用および保守プラットフォームの一部の機能の開発を担当しています。彼はデータベース技術に強い関心を持ち、MySQL と Redis の運用・保守管理、パフォーマンス最適化において長年の経験を持っています。

1. 背景

電子商取引のウェブサイトで買い物をするとき、電子商取引アプリによく見られるフラッシュセールや限定版クーポン購入、Qunar.com の列車チケット取得システムなど、同時実行性の高いシナリオに遭遇することがよくあります。これらのシナリオには、訪問者数の急増という共通の特徴があります。システム設計時に電流制限、非同期、キューイングなどによって最適化されますが、全体的な同時実行性は通常よりも数倍高くなります。同時実行の問題を回避し、在庫の過剰販売を防ぎ、ユーザーに快適なショッピング体験を提供するために、これらのシステムではロック メカニズムが使用されます。

単一プロセスの同時実行シナリオでは、同時実行の問題を回避するために、プログラミング言語と対応するクラス ライブラリ (Java の synchronized 構文や ReentrantLock クラスなど) によって提供されるロックを使用できます。

分散シナリオで異なるクライアントのスレッドによるコードとリソースへの同期アクセスを実現し、複数のスレッドで共有データを処理するセキュリティを確保するには、分散ロック テクノロジを使用する必要があります。

では、分散ロックとは何でしょうか?分散ロックは、分散システム間または異なるシステム間の共有リソースへのアクセスを制御するロック実装です。リソースが異なるシステム間または同じシステムの異なるホスト間で共有される場合、相互の干渉を防ぎ一貫性を確保するために、相互排他制御が必要になることがよくあります。

比較的安全な分散ロックには、通常、次の特性が必要です。

  • 相互排他性。相互排除はロックの基本的な特性です。クリティカル セクション操作を実行するために、ロックを保持できるのは一度に 1 つのスレッドだけです。
  • タイムアウト解除。タイムアウト後にスレッドを解放することで、デッドロック、不要なスレッド待機、リソースの浪費を回避できます。これは、MySQL の InnoDB エンジンの innodblockwait_timeout パラメータ構成に似ています。
  • 再入可能性。スレッドは、クリティカル セクション操作を完了する前にロックが解放されるのを防ぐために、ロックを保持したまま再度ロックするように要求できます。
  • 高いパフォーマンスと高い可用性。ロックおよび解放プロセスのパフォーマンス オーバーヘッドは可能な限り低く抑える必要があり、同時に分散ロックの偶発的な障害を防ぐために高可用性を確保する必要があります。

分散ロックを実装するには、リソースをロックするだけでなく、デッドロックやロックの失敗などの問題を回避するためにいくつかの追加機能も満たす必要があることがわかります。

2. 分散ロックの実装

分散ロックを実装する方法は多数あります。最も一般的なものは次のとおりです。

  • Memcached 分散ロック

Memcached の add コマンドを使用します。このコマンドはアトミック操作です。キーが存在しない場合にのみ追加が成功します。これは、スレッドがロックを取得したことを意味します。

  • Zookeeper 分散ロック

Zookeeper の連続した一時ノードを使用して、分散ロックと待機キューを実装します。 ZooKeeper は分散アプリケーション向けに特別に設計されたフレームワークとして、一時的な znode の自動削除など、非常に優れた機能を提供します。 ZooKeeper は、分散ロックをローカル ロックのようにクライアントで使用できるようにする監視メカニズムも提供します。ロックが失敗した場合、ロックが取得されるまでブロックされます。

  • ぽっちゃり

Google が実装した粗粒度の分散ロック サービスは、ZooKeeper と多少似ていますが、違いも多くあります。 Chubby は、シーケンサー メカニズムを通じて、要求の遅延によって発生するロック障害の問題を解決します。

  • Redis 分散ロック

単一の Redis マシンに基づいて実装された分散ロックは、やはりアトミック操作である Redis SETNX コマンドを使用する Memcached の実装に似ています。キーが存在しない場合にのみ正常に設定できます。 Redis マルチマシン実装に基づく分散ロック Redlock は、Redis 分散ロックの実装を標準化するために Redis の作者 antirez によって提案された、より安全で効果的な実装メカニズムです。

この記事では主に、Redis に基づく分散ロックのいくつかの実装方法と既存の問題について説明し、分析します。

3. Redis分散ロック

Redis を分散ロックとして使用する場合、重要な目標は、プロセスが Redis 内の唯一の「トイレ」を占有することです。他のプロセスがトイレを占有しようとすると、すでに誰かがそこにしゃがんでいることがわかり、諦めるか、待ってからもう一度試す必要があります。

現在、Redis に基づく分散ロックには主に 2 つの種類があります。 1 つは単一のマシンに基づいており、もう 1 つは複数の Redis マシンに基づいています。実装方法に関係なく、分散ロックの 3 つのコア要素、つまりロック、ロック解除、ロック タイムアウトを実装する必要があります。

1. Redisスタンドアロンベースの分散ロック

1) SETNX命令を使用する

ロックする最も簡単な方法は、Redis SETNX 命令を直接使用することです。この命令は、キーが存在しない場合にのみ、キーの値を value に設定します。キーがすでに存在する場合、SETNX コマンドは何もアクションを実行しません。キーはロックの一意の識別子であり、ビジネス ニーズに応じてロックされるリソースに応じて名前を付けることができます。

たとえば、特定のショッピングモールのフラッシュセールイベントで商品をロックする場合、キーを lock_resource_id に設定し、値を任意の値に設定できます。リソースが使用された後は、DEL を使用してキーを削除し、ロックを解除します。全体のプロセスは次のとおりです。

明らかに、ロックを取得するこの方法は非常に簡単ですが、前述の分散ロックの 3 つのコア要素の 1 つであるロック タイムアウトの問題という問題もあります。つまり、ビジネスロジック処理中にロックを取得するプロセスで例外が発生すると、DEL 命令が実行されず、ロックを解除できず、リソースが永久にロックされたままになる可能性があります。

そのため、SETNX を使用してロックを取得した後は、キーに有効期限を設定し、明示的に解放しなくても、一定期間後に自動的に解放されるようにして、リソースが長時間独占されるのを防ぐ必要があります。 SETNX は有効期限の設定をサポートしていないため、追加の EXPIRE 命令が必要です。全体のプロセスは次のとおりです。

このように実装された分散ロックには依然として重大な問題が残っています。 SETNX と EXPIRE の 2 つの操作は非アトミックであるため、SETNX と EXPIRE の実行の間に例外が発生すると、SETNX は正常に実行されますが、EXPIRE は実行されず、ロックが「不滅」になります。この場合、前述のロックタイムアウトの問題が発生し、他のプロセスが正常にロックを取得できなくなります。

2) SET拡張コマンドを使用する

SETNX および EXPIRE 操作の非アトミック問題を解決するために、Redis SET 命令の拡張パラメータを使用して、SETNX および EXPIRE 操作をアトミックにすることができます。全体のプロセスは次のとおりです。

このSET命令では、

  • NX は、lock_resource_id に対応するキー値が存在しない場合にのみ SET が成功できることを意味します。これにより、最初に要求したクライアントのみがロックを取得でき、ロックが解除されるまで他のクライアントはロックを取得できなくなります。
  • EX 10 は、ロックが 10 秒後に自動的に期限切れになることを意味します。この時間は事業者が実際の状況に応じて設定できます。

ただし、このアプローチでは、分散ロックのタイムアウトの問題を完全に解決することはできません。

  • ロックが予定より早く解除されました。スレッド A がロックとロック解除の間のロジックを実行するのに時間がかかりすぎる場合 (またはスレッド A が実行中にブロックされている場合)、有効期限が切れた後にロックが解除されても、スレッド A がクリティカル セクションのロジックをまだ完了していない場合は、スレッド B が事前にロックを再取得できるため、クリティカル セクションのコードが厳密にシリアルに実行されなくなります。
  • ロックが誤って削除されました。上記の状況でスレッド A が実行を終了した場合、その時点でロック保持者がスレッド B であるかどうかはわかりません。スレッド A はロックを解除するために DEL 命令の実行を継続します。スレッド B がクリティカル セクションのロジックを完了していない場合、スレッド A は実際にスレッド B のロック解除を行います。

上記の状況を回避するには、実行時間が長すぎるシナリオでは Redis 分散ロックを使用しないことをお勧めします。同時に、より安全な方法は、DEL を実行してロックを解除する前にロックを判断し、現在のロック保持者が自分自身であるかどうかを確認することです。

具体的な実装としては、ロック時に値を一意の乱数(またはスレッド ID)に設定し、ロックを解除する際には乱数が一貫しているかどうかをまず判断し、その後、ロックの有効期限が切れてサーバーによって自動的に解除されない限り、他のスレッドによって保持されているロックが誤って解除されないように解除操作を実行します。全体のプロセスは次のとおりです。

ただし、値の決定とキーの削除は 2 つの独立した操作であり、アトミックではないため、Lua スクリプトを使用して処理する必要があります。これは、Lua スクリプトを使用すると、複数の連続した命令のアトミック実行を保証できるためです。

Redis 単一ノードに基づく分散ロックは基本的に完成していますが、他のスレッドが現在のスレッド実行タイムアウトロックを事前に解放して利用してしまう問題を完全に解決していないため、完全なソリューションではなく、相対的に完成しているにすぎません。

3) Redissonの分散ロックを使用する

ロックが早期に解除される問題をどのように解決できますか?

ロックの再入可能機能を使用すると、ロックを取得したスレッドがタイマー付きのデーモン スレッドを開始し、expireTime/3 ごとに実行してスレッドのロックが存在するかどうかをチェックすることができます。存在する場合、ロックの有効期限は expireTime にリセットされます。つまり、デーモン スレッドは、ロックの有効期限が切れてロックが早期に解放されるのを防ぐために、ロックの「寿命を延ばす」ために使用されます。

もちろん、このデーモン プロセスのロジックをビジネスで実装するのは比較的複雑であり、未知の問題が発生する可能性があります。

現在、インターネット企業の実稼働環境で広く使用されているオープンソース フレームワークである Redisson は、この問題を非常にうまく解決します。非常にシンプルで使いやすく、Redis シングルインスタンス、Redis MS、Redis Sentinel、Redis Cluster などの複数のデプロイメント アーキテクチャをサポートしています。

興味のある方は、公式ドキュメントまたはソースコードを参照してください。

https://github.com/redisson/redisson/wiki

実装原理を図に示します (図では Redis クラスターを例にしています)。

2. Redisマルチマシン実装に基づく分散ロック、Redlock

上記の Redis スタンドアロン実装に基づく分散ロックには、実際には問題があります。つまり、ロックは 1 つの Redis ノードでのみ機能します。 Redis は Sentinel を通じて高可用性を確保していますが、Redis のレプリケーションは非同期であるため、マスターノードがロックを取得した後、データ同期が完了せずにフェイルオーバーが発生します。この時点では、他のクライアントのスレッドは引き続きロックを取得できるため、ロックのセキュリティが失われます。

全体のプロセスは次のとおりです。

  • クライアント A はマスター ノードからロックを取得します。
  • マスター ノードに障害が発生し、マスター スレーブ レプリケーション プロセス中に、ロックに対応するキーがスレーブ ノードに同期されません。
  • スレーブ ノードはマスター ノードにアップグレードされますが、現時点ではマスターにロック データはありません。
  • クライアント B は新しいマスター ノードを要求し、同じリソースに対応するロックを取得します。
  • 複数のクライアントが同時に同じリソースのロックを保持するため、ロックの相互排他性が満たされません。

このため、Redis の分散環境では、Redis の作者 antirez が分散ロックを実装するための RedLock アルゴリズムを提供しています。アルゴリズムはおおよそ次のようになります。

互いに完全に独立した N (N>=5) 個の Redis ノードがあると仮定します。マスター/スレーブ レプリケーションやその他のクラスター調整メカニズムはありません。これらの N 個のノードでロックを取得および解放するには、単一の Redis インスタンスの場合と同じ方法を使用するようにしてください。

ロックを取得するには、クライアントは次の操作を実行する必要があります。

  • 現在の Unix 時間をミリ秒単位で取得します。
  • 同じキーと一意の値 (UUID など) を使用して、5 つのインスタンスから順番にロックを取得してみます。 Redis からロックを要求する場合、クライアントはネットワーク接続と応答のタイムアウトを設定する必要があります。これはロックの有効期限よりも短くする必要があります。たとえば、ロックが 10 秒後に自動的に期限切れになる場合、タイムアウトは 5 ~ 50 ミリ秒にする必要があります。これにより、サーバー側の Redis がクラッシュしたときにクライアントが応答結果を待機している状況を回避できます。サーバーが指定された時間内に応答しない場合、クライアントはできるだけ早く別の Redis インスタンスからロックを取得しようとする必要があります。
  • クライアントは、現在の時刻からロックが取得された時刻 (手順 1 で記録された時刻) を引いて、ロックを取得するために使用された時刻を取得します。ロックが Redis ノードの過半数 (N/2+1、ここでは 3 つのノード) から取得され、使用された時間がロックの有効期限よりも短い場合にのみ、ロックが正常に取得されたと見なされます。
  • ロックが取得された場合、キーの実際の有効期間は、有効期間からロックの取得に使用された時間を差し引いたものになります (手順 3 で計算された結果)。
  • 何らかの理由でロックの取得に失敗した場合 (少なくとも N/2+1 個の Redis インスタンスでロックが取得されていないか、ロックの取得時間が有効時間を超えている場合)、クライアントはすべての Redis インスタンスのロックを解除する必要があります (Redis Lua スクリプトを使用)。

ロックを解除するプロセスは比較的簡単です。クライアントは、ロックに失敗したノードを含むすべての Redis ノードに対してロック解除操作を開始し、ロック解除操作も実行する必要があります。 Antirez はアルゴリズムの説明でこの点を特に強調しています。何故ですか?

その理由は、ノードが正常にロックされた後にクライアントに返される応答パケットが失われる可能性があるためです。この状況は非同期通信モデルで発生する可能性があります。クライアントはサーバーと正常に通信できますが、逆方向に問題が発生します。クライアントの応答タイムアウトによりロックは失敗しますが、Redis ノードに対して SET 命令が正常に実行され、ロックは成功したことを意味します。したがって、ロックを解放するときに、クライアントは、その時点でロックを取得できなかった Redis ノードに対してもリクエストを開始する必要があります。

さらに、Redis ノードがクラッシュして再起動した後にロックが失われ、ロックのセキュリティに影響が及ぶのを避けるために、antirez は遅延再起動の概念も提案しました。つまり、クラッシュ後すぐにノードを再起動せず、再起動する前に一定時間待機します。この期間は、ロックの有効期間よりも長くする必要があります。

Redlock についてさらに詳しく知りたい方は、公式ドキュメントを参照してください: https://redis.io/topics/distlock

IV.結論

分散システムの設計では、複雑さと利点のバランスをとることが重要です。過剰な設計を避けながら、可能な限り安全で信頼できるものにする必要があります。 Redlock は確かにより安全な分散ロックを提供できますが、それにはコストがかかり、より多くの Redis ノードが必要になります。実際のビジネスでは、単一ポイントの Redis を使用して分散ロックを実装すると、通常、ほとんどのニーズを満たすことができます。場合によっては、手動で介入してデータを入力することで、データの不整合を解決できることがあります。諺にもあるように、「テクノロジーが不十分なら、人間の介入で補うことができます!」 。

<<:  クラウドネイティブアプリケーションを構築するために知っておくべきこと

>>:  PaaS、IaaS、SaaS、Bass、Fass、サーバーレスの理解と違い

推薦する

個人ウェブサイトが生き残りたいなら、次の5つの点に注意する必要があります

大手検索エンジンがインターネットのスパムサイトを一掃するにつれ、特に一部の個人サイトにとって、ランキ...

Ctrip.comは同城旅行に2億ドル以上を投資し、同社の第2位の株主となった。

Ctrip.comは同城旅行に2億ドル以上を投資し、同社の第2位の株主となった。 4月28日、Ctr...

マルチクラウドを管理する5つの方法

IBM Institute for Business Value のデータによると、実際には大多数の...

#blackfriday# ultravps - 年間 20 ユーロ/KVM/1G メモリ/30g SSD/1T トラフィック/4 コンピュータ ルーム

11月24日から、10年以上の歴史を持つドイツの老舗ホスティング会社が所有するVPSブランドであるu...

優れたオンライン h5 ゲームを設計するにはどうすればよいでしょうか?

本稿では、主にTiantian Ptuの「小学校卒業写真」、「みんなで呉美娘のコスプレ」、「神経質な...

最速のシンガポールVPSの推奨、シンガポールVPS

高速シンガポール VPS、最速シンガポール VPS、最速シンガポール VPS。シンガポールの VPS...

成功か失敗かは細部によって決まります。ブログのタグを最適化するにはどうすればよいでしょうか?

ブログにタグを追加することは、ブログ記事を書くときに最も基本的な習慣の 1 つになっています。これに...

Iniz-4Gメモリ/SSD/月額6.78ドル/オランダデータセンター

iniz は英国で正式に登録された会社です: 会社番号 08199520、登録事務所住所 45-15...

公安機関がオンライン犯罪を取り締まり、3,500以上の違法ウェブサイトを是正

ウェブマスターネットワークが10月15日に報じたところによると、今年8月に公安部が全国に公安機関を派...

IDC: 中国のクラウド市場規模は2024年までに1,000億ドルを超える見込み

最近、市場調査会社IDCは、中国のクラウド市場全体の規模が2024年までに1,000億米ドルを超える...

クラウド戦略を再検討し、調整する時期が来ています

私たちの生活は、個人的にも職業的にも、COVID-19パンデミックの影響を受けています。レストランな...

広東省電子商取引業界のCXOがファーウェイを訪問し、クラウドネイティブ2.0時代の電子商取引業界の革新動向を探る

4月25日午後、「ファーウェイクラウド広東専属月例電子商取引業界ハイエンドCXOプライベートミーティ...

検索エンジン、ユーザー、ウェブマスターの潜在的な関係は止められない

SEO 担当者なら誰でも、この文を知っています。「人々が情報を入手し、探しているものを見つけやすくす...