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 をサポートしています

推薦する

マルチクラウドデータストレージのベストプラクティス

複雑なマルチクラウド展開におけるデータストレージのベストプラクティスは何ですか?マルチクラウドのニー...

JVMの構造を理解して、面接で自慢できるようにしましょう

[[286442]] JVM には、基本型と参照型の 2 種類のデータ型が含まれています。基本的な型...

v.psはどうですか?アメリカ西海岸シアトルデータセンターのVPSレビュー

アメリカ西海岸のシアトルとサンノゼのデータセンターも、中国人が集まる人気の場所です。v.psはシアト...

Baidu Webmaster Platform ハイエンドSEOクラブの第一回サロン活動の概要と共有

10月26日、百度ウェブマスタープラットフォームとMADconが共同で主催した百度ウェブマスタープラ...

Zhubajie.com がリニューアルした後、Witkey として収益を得る方法は何ですか?

関連統計によると、中国のWitkeyユーザー数は3500万人を超えています。この巨大な基盤のおかげで...

企業はクラウドコンピューティングの使用に関して災害復旧計画をどのように策定すべきでしょうか?

災害復旧とは、COVID-19 によって引き起こされたビジネスの歪みを含む自然災害やサイバー攻撃の後...

アリババがメイズに5億9000万ドルを投資

2月9日午前、Meizu TechnologyとAlibaba Groupは、Alibaba Gro...

盲目的にウェブサイトを最適化するよりも、ユーザーエクスペリエンスを良くする方が良い

昨日(2013年2月19日)BaiduのGreen Radish Algorithmがリリースされ、...

「フレンズサークル」が住宅購入クラウドファンディングの「軍隊に加わる」クラウドファンディングプラットフォームに

P2Pに続いて、「クラウドファンディング」はインターネット金融業界で2番目に「急成長」している分野に...

石家荘のどのウェブサイト構築会社が最高ですか? 予算に応じてウェブサイト構築会社を選択する

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますBaidu...

クラウド分析とリモート監視が自動化の次の大きなトレンドとなる理由

企業は俊敏性を高め、ビジネス継続性のためにクラウドとリモート監視に重点を置く必要があります。テクノロ...

製造業におけるクラウドコンピューティングの課題と開発動向

クラウド コンピューティングの発展に伴い、製造会社の日常業務にますます多くのクラウド アプリケーショ...

災害復旧サービスが企業をクラウド コンピューティングの世界に導く方法

Zerto テクノロジー エバンジェリストの Chris Rogers が、クラウドでの災害復旧の仕...

ウェブサイトテンプレートの最適化テストの詳細分析

最適化におけるテンプレートの役割: テンプレートは、Web サイトのコンテンツのレイアウトと Web...