7つのオプション! Redis 分散ロックの正しい使用法について議論する

7つのオプション! Redis 分散ロックの正しい使用法について議論する

[[385757]]

序文

日常の開発では、フラッシュセールの注文や紅包の受け取りなどのビジネスシナリオで分散ロックが必要になります。 Redis は分散ロックとして使用するのに非常に適しています。この記事では、Redis 分散ロックの正しい使用方法について 7 つのソリューションに分けて説明します。何か間違っている点がありましたら、ご指摘ください。一緒に学び、成長していきましょう。

公式アカウント:「カタツムリを拾う少年」

  • 分散ロックとは何ですか?
  • 解決策1: SETNX + EXPIRE
  • 解決策2: SETNX + 値は (システム時間 + 有効期限)
  • 解決策3: Luaスクリプトを使用する(SETNX + EXPIRE命令を含む)
  • 解決策4: SET拡張コマンド(SET EX PX NX)
  • 解決策5: SET EX PX NX + 一意のランダム値を検証し、ロックを解除する
  • ソリューション 6: オープンソースフレームワーク ~ Redisson
  • ソリューション7: 複数のマシンに実装された分散ロックRedlock

分散ロックとは何ですか?

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

まず、信頼性の高い分散ロックに必要な機能について見てみましょう。

  • 「相互排他」: 一度にロックを保持できるのは 1 つのクライアントのみです。
  • 「ロック タイムアウトの解除」: ロックがタイムアウトの間保持されている場合、不要なリソースの浪費やデッドロックを防ぐために、ロックを解除できます。
  • 「再入可能」: スレッドがロックを取得すると、再度ロックを要求できます。
  • 「高パフォーマンスと高可用性」: ロックとロック解除のオーバーヘッドを可能な限り低く抑えると同時に、分散ロックの障害を回避するために高可用性を確保する必要があります。
  • 「セキュリティ」: ロックはそれを保持しているクライアントのみが削除でき、他のクライアントは削除できません。

Redis 分散ロックソリューション 1: SETNX + EXPIRE

Redis 分散ロックに関して言えば、多くの人はすぐに setnx+ expire コマンドを思い浮かべるでしょう。つまり、まず setnx を使用してロックを取得します。ロックが取得された場合は、expire を使用してロックの有効期限を設定し、ロックの解除を忘れないようにします。

SETNX は SET IF NOT EXISTS の略です。毎日のコマンドの形式は SETNX キー値です。キーが存在しない場合は、SETNX は正常に 1 を返します。キーが既に存在する場合は、0 を返します。

電子商取引ウェブサイト上の製品がフラッシュセールを行っていると仮定します。キーは key_resource_id に設定でき、値は任意の値に設定できます。疑似コードは次のとおりです。

  1. if (jedis.setnx(key_resource_id,lock_value) == 1) { //ロック
  2. 有効期限が切れます(キーリソースID、100); //有効期限を設定する
  3. 試す {
  4. 何かをする //ビジネスリクエスト
  5. }キャッチ(){
  6. }
  7. ついに {
  8. jedis.del(キーリソースID); //ロックを解除する
  9. }
  10. }

ただし、このソリューションでは、setnx コマンドと expire コマンドが分離されており、アトミック操作ではありません。 setnx を実行してロックを実行した後、有効期限を設定するために expire を実行しようとしたときにプロセスがクラッシュしたり、メンテナンスのために再起動する必要が生じたりした場合、ロックは「不滅」となり、「他のスレッドはロックを取得できなくなります」。

Redis 分散ロック ソリューション 2: SETNX + 値は (システム時間 + 有効期限)

最初の解決策「異常なロックを解除できないシナリオ」を解決するために、一部の友人は、有効期限を setnx の値に入れることができると考えています。ロックに失敗した場合は、値を取り出して再度確認してください。ロックコードは次のとおりです。

  1. 長い有効期限 = System.currentTimeMillis() + expireTime; //システム時間 + 有効期限の設定
  2. 文字列の有効期限Str = String.valueOf(有効期限);
  3.  
  4. // 現在のロックが存在しない場合は、ロック成功を返します
  5. jedis.setnx(key_resource_id, expiresStr) == 1 の場合 {
  6. 戻る 真実;
  7. }
  8. // ロックがすでに存在する場合は、ロックの有効期限を取得します
  9. 文字列 currentValueStr = jedis.get(key_resource_id);
  10.  
  11. // 取得した有効期限が現在のシステム時間より短い場合は、有効期限が切れていることを意味します
  12. currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis() の場合 {
  13.  
  14. // ロックの有効期限が切れました。前回のロックの有効期限を取得し、現在のロックの有効期限を設定します(Redis の getSet コマンドがわからない場合は、公式 Web サイトにアクセスして確認してください)
  15. 文字列 oldValueStr = jedis.getSet(key_resource_id, expiresStr);
  16.      
  17. oldValueStr != null && oldValueStr.equals(currentValueStr) の場合 {
  18. // マルチスレッド同時実行の状況を考慮すると、設定値が現在の値と同じ場合、1つのスレッドのみがロックできます。
  19. 戻る 真実;
  20. }
  21. }
  22.          
  23. // それ以外の場合、ロックは失敗します。
  24. 戻る 間違い;
  25. }

このソリューションの利点は、expire で有効期限を個別に設定する操作を巧みに削除し、「有効期限」を setnx の値に入れることです。ソリューション 1 で例外が発生したときにロックを解除できないという問題を解決しました。ただし、このソリューションには他にも欠点があります。

  • 有効期限はクライアント自体によって生成されます (System.currentTimeMillis() は現在のシステム時刻です)。分散環境では、各クライアントの時刻を同期する必要があります。
  • ロックの有効期限が切れると、複数のクライアントが同時にロックを要求し、jedis.getSet() を実行します。最終的に、ロックを正常にロックできるのは 1 つのクライアントだけですが、クライアント ロックの有効期限は他のクライアントによって上書きされる可能性があります。
  • ロックは所有者の一意の識別子を保存しないため、他のクライアントによって解除/ロック解除される可能性があります。

Redis 分散ロック ソリューション 3: Lua スクリプトの使用 (SETNX + EXPIRE 命令を含む)

実際、Lua スクリプトを使用してアトミック性を確保することもできます (setnx および expire 命令を含む)。 Lua スクリプトは次のとおりです。

  1. redis.call( 'setnx' ,KEYS[1],ARGV[1]) == 1の場合 
  2. redis.call( 'expire' ,KEYS[1],ARGV[2])
  3. それ以外 
  4. 0を返す
  5. 終わり;

ロックコードは次のとおりです。

  1. 文字列 lua_scripts = "redis.call('setnx',KEYS[1],ARGV[1]) == 1 の場合" +
  2. " redis.call('expire',KEYS[1],ARGV[2]) 1を返す、そうでなければ0を返す 終了" ;
  3. オブジェクト結果 = jedis.eval(lua_scripts、Collections.singletonList(key_resource_id)、Collections.singletonList( values ​​));
  4. //成功したかどうか判断する
  5. 結果を返します。equals(1L);

この計画と計画 2 を比較すると、どちらが良いと思いますか?

Redis 分散ロック ソリューション 4: SET 拡張コマンド (SET EX PX NX)

Lua スクリプトを使用して 2 つの命令 SETNX + EXPIRE のアトミック性を保証することに加えて、Redis の SET 命令を巧みに使用してパラメータを拡張することもできます。 (SET キー値 [EX 秒] [PX ミリ秒] [NX|XX]) これもアトミックです。

キー値の設定[EX 秒][PX ミリ秒][NX|XX]

  • NX: キーが存在しない場合にのみセットが成功できることを示します。これにより、最初のクライアント要求のみがロックを取得でき、他のクライアント要求はロックが解放された後にのみロックを取得できるようになります。
  • EX 秒: キーの有効期限を設定します。時間単位は秒です。
  • PXミリ秒: キーの有効期限をミリ秒単位で設定します
  • XX: キーが存在する場合にのみ値を設定する

疑似コードデモは次のとおりです。

  1. if (jedis.set ( key_resource_id, lock_value, "NX" , "EX" , 100s) == 1) { // ロック
  2. 試す {
  3. 何かをする //ビジネス処理
  4. }キャッチ(){
  5. }
  6. ついに {
  7. jedis.del(キーリソースID); //ロックを解除する
  8. }
  9. }

ただし、この解決策にはまだ問題がある可能性があります。

  • 質問1:「ロックの期限が切れて解除されましたが、業務が完了しませんでした。」スレッド a がロックを正常に取得し、クリティカル セクション内のコードを実行していると仮定します。しかし、100 秒経っても実行が完了しません。ただし、この時点でロックの有効期限が切れており、スレッド B が再度ロックを要求します。明らかに、スレッド b はロックを正常に取得し、クリティカル セクション コードの実行を開始できます。すると、クリティカル セクション内のビジネス コードが厳密にシリアルに実行されないという問題が発生します。
  • 質問 2: 「ロックは別のスレッドによって誤って削除されました。」スレッド a が実行後にロックを解除すると仮定します。ただし、現在のロックがスレッド b によって保持されている可能性があることはわかりません (スレッド a がロックを解放したときに有効期限が到来し、スレッド b が入り込んでロックを占有する可能性があります)。その後、スレッド a はスレッド b のロックを解除しますが、スレッド b のクリティカル セクションのビジネス コードはまだ実行されていない可能性があります。

解決策5: SET EX PX NX + 一意のランダム値を確認してから削除する

ロックは他のスレッドによって誤って削除される可能性があるため、値に現在のスレッドに固有の乱数を設定し、削除時にそれをチェックします。疑似コードは次のとおりです。

  1. if (jedis.set ( key_resource_id, uni_request_id, "NX" , "EX" , 100s) == 1) { //ロック
  2. 試す {
  3. 何かをする //ビジネス処理
  4. }キャッチ(){
  5. }
  6. ついに {
  7. //現在のスレッドによってロックが追加されたかどうかを判断し、追加された場合は解放します
  8. uni_request_id.equals(jedis.get(key_resource_id)) の場合 {
  9. jedis.del(ロックキー); //ロックを解除する
  10. }
  11. }
  12. }

ここで、「現在のスレッドによってロックが追加されたかどうかを判断する」ことと「ロックを解放する」ことはアトミック操作ではありません。ロックを解除するために jedis.del() が呼び出されると、ロックは現在のクライアントに属しなくなり、他のクライアントによって追加されたロックが解除されます。

より厳密にするために、代わりに Lua スクリプトが一般的に使用されます。 lua スクリプトは次のとおりです。

  1. redis.call( 'get' ,KEYS[1]) == ARGV[1]の場合  
  2. redis.call( 'del' ,KEYS[1])を返す
  3. それ以外 
  4. 0を返す
  5. 終わり;

Redis 分散ロック ソリューション 6: Redisson フレームワーク

ソリューション 5 では、「ロックの有効期限が切れて解除され、業務が完了しない」という問題が依然として残る可能性があります。ロックの有効期限をもう少し長く設定すれば十分だと考える友人もいます。実際に、ロックを取得したスレッドに対して時間指定のデーモン スレッドを起動し、一定の間隔でロックがまだ存在するかどうかを確認できるかどうかを想像してみましょう。そうなる場合、ロックの有効期限が延長され、期限切れによりロックが早期に解除されるのを防ぎます。

現在のオープンソースフレームワークRedissonはこの問題を解決します。 Redisson の基本的な概略を見てみましょう。

スレッドが正常にロックされるとすぐに、ウォッチドッグが開始されます。 10 秒ごとにチェックするバックグラウンド スレッドです。スレッド 1 がまだロックを保持している場合、ロック キーの有効期間は継続的に延長されます。そのため、Redisson は Redisson を使用して、「ロックの有効期限が切れて解除されても業務が完了しない」という問題を解決します。

Redis 分散ロックソリューション 7: 複数のマシンで実装された分散ロック Redlock+Redisson

前の 6 つのソリューションは、スタンドアロン バージョンに関する議論のみに基づいており、まだ完璧ではありません。実際、Redis は一般的にクラスターでデプロイされます。

スレッド 1 が Redis マスター ノードのロックを取得したが、ロックされたキーがスレーブ ノードに同期されていない場合。ちょうどこのとき、マスターノードに障害が発生し、スレーブノードがマスターノードにアップグレードされます。スレッド 2 は同じキーでロックを取得できますが、スレッド 1 がすでにロックを取得しているため、ロックのセキュリティは失われます。

この問題を解決するために、Redis の作者 antirez は、高度な分散ロック アルゴリズムである Redlock を提案しました。 Redlock の中心的なアイデアは次のとおりです。

複数の Redis マスターをデプロイして、同時に障害が発生しないようにします。これらのマスターノードは互いに完全に独立しており、マスターノード間でデータの同期は行われません。同時に、これらの複数のマスター インスタンスでロックを取得および解放するには、単一の Redis インスタンスの場合と同じ方法が使用されるようにする必要があります。

現在 5 つの Redis マスター ノードがあり、これらの Redis インスタンスが 5 台のサーバー上で実行されていると想定します。

RedLock の実装手順は次のとおりです。

1. 現在の時刻をミリ秒単位で取得します。

2. 5 つのマスターノードに順番にロックを要求します。クライアントはネットワーク接続と応答のタイムアウトを設定します。タイムアウトはロックの有効期限よりも短くする必要があります。 (ロックが 10 秒後に自動的に期限切れになると仮定すると、タイムアウトは通常 5 ~ 50 ミリ秒の間になります。タイムアウトが 50 ミリ秒であると仮定します)。タイムアウトが発生した場合は、マスターノードをスキップし、できるだけ早く次のマスターノードを試してください。

3. クライアントは、現在の時刻からロックの取得が開始された時刻 (つまり、手順 1 で記録された時刻) を引いて、ロックの取得に使用された時刻を取得します。ロックが正常に取得されるのは、Redis マスター ノードの半分以上 (N/2+1、ここでは 5/2+1=3 ノード) がロックを取得し、使用された時間がロックの有効期限よりも短い場合のみです。 (上図のように、10秒>30ms+40ms+50ms+4m0s+50ms)

ロックが取得されると、キーの実際の有効期間が変わるため、ロックの取得に使用された時間を減算する必要があります。

ロックの取得に失敗した場合 (少なくとも N/2+1 個のマスター インスタンスでロックが取得されていないか、ロックの取得時間が有効時間を超えている場合)、クライアントはすべてのマスター ノードでロックを解除する必要があります (一部のマスター ノードが正常にロックされていない場合でも、ロックが網をすり抜けるのを防ぐためにロックを解除する必要があります)。

簡略化された手順は次のとおりです。

  • 5つのマスターノードから順番にロックを要求する
  • 設定されたタイムアウトに基づいてマスターノードをスキップするかどうかを決定します。
  • 3 つ以上のノードが正常にロックされ、使用時間がロックの有効期間未満の場合、ロックは成功したと見なすことができます。
  • ロックの取得に失敗した場合は、ロックを解除してください。

Redisson はロックの redLock バージョンを実装します。興味があればぜひ行って学んでみてくださいね〜

参考文献と謝辞

Redisシリーズ: 分散ロック[1]

Redis分散ロックソリューションの簡単な分析[2]

Redis分散ロックの詳細な説明[3]

Redlock: Redis分散ロックの最も強力な実装

この記事はWeChatの公開アカウント「カタツムリを拾う少年」から転載したもので、著者はカタツムリを拾う少年です。この記事を転載する場合は、カタツムリを採る少年の公式アカウントまでご連絡ください。

<<:  分散トランザクション2PCおよび3PCモデルを徹底的に習得する

>>:  VMware は仮想化を加速するために SmartNIC をサポートしています

推薦する

メガレイヤーはどうですか?米国サンノゼ標準ネットワーク回線評価

メガレイヤーはどうですか?メガレイヤーUSAはどうですか?米国サンノゼの標準ネットワーク回線を備えた...

Web ページの類似性問題を解決するにはどうすればよいでしょうか?

ここでは、それほど深い意味はないが、いくつかの小さな方法をまとめます。これが皆さんのお役に立てば幸い...

クラウド コンピューティングがネイティブの需要に向かって進むとき、IT インフラストラクチャはどのようにアップグレードされるべきでしょうか?

クラウド ネイティブは、企業がビジネスを展開する際の主な選択肢の 1 つになっています。利点は、アプ...

実践:WeChatミニプログラムを活用して教育業界を促進する3つの方法!

ミニプログラムの台頭に直面して、教育業界はどのようにその波に乗るべきでしょうか?この記事では、教育業...

記事を更新する際のウェブマスター向けのヒント

毎日ウェブサイトに数記事を更新することは、ウェブマスターがしなければならないことですが、更新した記事...

DIV切り替え機能を不適切に設定すると、ホームページの権限がダウングレードされる可能性があるので注意してください。

多くの人がウェブサイトを最適化する際、コンテンツと外部リンクに重点を置いており、基本的にこの2つの側...

Shi Yuzhuがウェブサイトのコンテンツを最適化する方法をガイドします

マーケティング業界の伝説的人物である石玉竹は、「巨人」、「美百品」、「黄金パートナー」などを通じて、...

SEOを理想化しない

批判しているのではなく、自分の意見を述べているだけです数日前、私は Shi Xiaorui 氏が書い...

中国のトップ5ドメイン名は1月第1週に32,000増加したが、米国は19,000減少した。

中国IDCレビューネットワークは1月10日に次のように報じた。「WebHosting.infoが発表...

仕事の効率を2倍にする: Gitの実用的なコマンド集

1. バージョン管理を理解するバージョン管理とは何ですか?本当に必要ですか?バージョン管理は、特定の...

微信の公開アカウントが是正の焦点となる。テンセントは報告者に25万元の報奨金を支給したと述べた。

業界関係者は、今回の是正措置はWeChatのユーザー活動などの指標に影響を及ぼすとみているが、WeC...

投資か投機か、WeChatパブリックアカウントのプロモーション戦略をどう実現するか

WeChatパブリックアカウントを0から1、そしてNにまで成長させた運営者として、 WeChatパブ...

ブランドマーケティングプログラム運用マニュアル

私自身の業務経験や情報をもとにまとめたブランドマーケティング運用マニュアルです。ブランド マーケティ...

myrsk-2g メモリ KVM VPS 月額支払い $7/アトランタ

Myrsk のアトランタ データ センターでは、驚きの価格で KVM プロモーションを実施しています...