Redis 分散ロック

Redis 分散ロック

[[405785]]

分散ロックは「スレッド同期」の延長です

最近、「分散ロック」が初めて適用されました。考えてみてください。分散ロックは孤立したスキルポイントではなく、実際にはホスト間のスレッド同期です。

進行中クロスプロセスクロスホスト
ロック/モニター、SemaphoreSlim メトゥクス、セマフォ分散ロック
ユーザーモードのスレッドセーフティカーネルスレッドの安全性

スタンドアロン サーバーは、特定のメモリ ヒープを共有することで、ロック/ロック解除をマークできます。スレッドの同期は、最終的にはスタンドアロン オペレーティング システムのユーザー状態/カーネル状態における共有メモリのアクセス制御に基づいています。

分散サーバーは同じマシン上にはなく、複数のホストにまたがっているため、ロック タグはすべてのマシン プロセスが参照できる場所に保存する必要があります。

ロックは、在庫管理や宝くじなど、多くのビジネス シナリオの開発に使用されます。

たとえば、在庫が 1 つしか残っておらず、3 人のユーザーが同時に購入する予定の場合、最初に購入したユーザーの在庫はすぐにクリアされ、他の 2 人のユーザーは購入できなくなります。

分散ロックの理解

この分散ロックを含め、私たちがよく話題にするスレッドセーフティとスレッド同期のソリューションはすべて、

「複数のスレッド/プロセスが同時に特定のリソースを更新しています。」

基本的な考慮事項

1. 分散システムでは、ロックは一度に 1 つのサーバーによってのみ取得できます (これが分散ロックの基礎です)

2. デッドロックを防止するロック解除機構を装備(特定の事故を防止するため、ロックが解除されず、他の人がロックを取得できない)

Redis SET リソース名 任意の文字列 NX EX 最大ロック時間

これは最もシンプルな分散ロック実装ソリューションです。

SET コマンドはいくつかのパラメータをサポートしています:

  • EX 秒 - 有効期限を設定します (秒)
  • NX -- キーが存在しない場合は設定します...

SET コマンド パラメータは SETNX、SETEX、および GETSET を置き換えることができるため、これらのコマンドは将来的に廃止される可能性があります。

上記のコマンドが OK を返す場合 (または再試行される場合)、クライアントはロックを取得します。

ロックを解除するには、DEL コマンドを使用します。タイムアウト期間に達すると、ロックは自動的に解除されます。

ロックを解除するときに、システムをより堅牢にするためにいくつかのデザインを追加します。

3. ロック タグ値として固定の文字列値を使用せず、業界ではトークンと呼ばれる、推測しにくいランダムな値を使用します。

4. ロックを解除するためにDELコマンドを使用せず、キーを削除するスクリプトを送信します。

ポイント 3 と 4 は、「ロックの期限が早く切れ、クライアント A が実行を完了していないため、クライアント B がロックを取得します。この時点で、クライアント A は実行を完了しています。ロックが削除されると、クライアント B のロックも削除されますか?」という問題を解決することを目的としています。 - 4 は 3 つのテクノロジの推奨実装です。

スクリプトは次のとおりです。

  1. redis.call( "get" ,KEYS1] ==ARGV[1])の場合
  2. それから 
  3. redis.call( "DEL" ,KEYS[1])を返す
  4. それ以外 
  5. 0を返す
  6. 終わり 

以下は、StackExchange.Redis を使用した上記の考慮事項に基づいたコード例です。

  1. /// <要約>
  2. /// ロックを取得します。
  3. /// </要約>
  4. /// <param name = "キー" ></param>
  5. /// <param name = "token" >ランダム値</param>
  6. /// <param name = "expireSecond" ></param>
  7. /// <param name = "waitLockSeconds" >非ブロッキング ロック</param>
  8. static bool Lock(文字列キー、文字列トークン、 int expireSecond=10、 double waitLockSeconds = 0)
  9. {
  10. var 待機間隔 = 50;
  11. ブール値はロックです。
  12.              
  13. DateTimeの開始= DateTime.Now;
  14. する
  15. {
  16. isLock = Connection .GetDatabase().StringSet(キー、トークン、TimeSpan.FromSeconds(expireSecond)、 When .NotExists);
  17. if (ロック)
  18. 戻る 真実;
  19. //ロックを待たずに戻る
  20. waitLockSeconds == 0 の場合、中断します。
  21. //待機時間が経過すると、それ以上の待機は行われません
  22. if ((DateTime.Now - begin ).TotalSeconds >= waitLockSeconds) break;
  23. スレッドスリープ(待機間隔Ms);
  24. } while (!isLock);
  25. 戻る 間違い;
  26. }
  27.         
  28. /// <要約>
  29. /// ロックを解除します。
  30. /// </要約>
  31. /// < ロックが解除された場合は><c> true </c>を返し、それ以外の場合は <c> false </c> を返します。</を返します>
  32. /// <param name = "key" >キー。</param>
  33. /// <param name = "value" >値</param>
  34. static bool UnLock(文字列キー、文字列値)
  35. {
  36. 文字列 lua_script = @"
  37. (redis.call( 'GET' , KEYS[1]) == ARGV[1])場合   
  38. redis.call( 'DEL' , キー[1])
  39. 戻る 真実   
  40. それ以外   
  41. 戻る 間違い   
  42. 終わり   
  43. ";
  44. 試す
  45. {
  46. var res =接続.GetDatabase().ScriptEvaluate(lua_script,
  47. 新しいRedisKey[] {キー},
  48. 新しいRedisValue[] { 値 });
  49. (bool)resを返します
  50. }
  51. catch (例外例)
  52. {
  53. Console.WriteLine($ "ReleaseLock ロック失敗...{ex.Message}" );
  54. 戻る 間違い;
  55. }
  56. }
  57.          
  58. プライベート静的Lazy<ConnectionMultiplexer> lazyConnection = 新しい Lazy<ConnectionMultiplexer>(() =>
  59. {
  60. ConfigurationOptions 構成 = 新しい ConfigurationOptions
  61. {
  62. 接続失敗時に中止 = false
  63. 接続タイムアウト = 5000、
  64. };
  65. configuration.EndPoints.Add ( "10.100.219.9" 6379);
  66. ConnectionMultiplexer.Connect ( configuration.ToString () )を返します
  67. });
  68. 公共 静的ConnectionMultiplexer Connection => lazyConnection.Value;

上記のコードでは、5 番目の考慮事項が追加されます。

5. 無制限のロック取得を避けるために、非ブロッキングロックが追加されます。poll_sはロックを待機し、待機されない場合はロックを取得しなくなります。

方向:

3 つのタスクを並行して開始し、同時に在庫を削減してみましょう。

  1. 静的void Main(文字列[] 引数)
  2. {
  3. // 3つのタスクを並列に実行してみる
  4. 平行。 (0, 3, x =>の場合
  5. {
  6. 文字列トークン = $ "loki:{x}" ;
  7. bool isLocked = Lock( "loki" , トークン, 5, 10);
  8.              
  9. (ロックされている場合)
  10. {
  11. Console.WriteLine($ "{token} は {DateTime.Now} に株式の削減 (ロック付き) を開始します。" );
  12. スレッドスリープ(1000);
  13. Console.WriteLine($ "{token}は {DateTime.Now} にロック {UnLock(" loki ", token)} を解除します。" );
  14. }
  15. それ以外 
  16. {
  17. Console.WriteLine($ "{token} は {DateTime.Now} に在庫の削減を開始します。" );
  18. }
  19. });
  20. }

3つの並列タスクが順番にロックを取得/解放しているのがわかります。

出力の概要

この記事では、スレッド セーフティとスレッド同期の基礎から始め、分散ロックがホスト間のリソース スレッド/プロセス同期ソリューションであることを認識します。また、RedisSET コマンドを分散ロックとして使用する場合の設計上の考慮事項についても段階的に説明します。記憶力が良いことは文章力が悪いことほど良くありません。

<<:  IDC:中国の産業用クラウド市場規模は2020年後半に23億ドルに達する見込み

>>:  分散トランザクション - 信頼性の高いメッセージ最終一貫性ソリューション

推薦する

nuxtcloud: 安価なロシア + ドイツの VPS、月額 139 ルーブル (11.3 元)、1G メモリ/1 コア/15g NVMe/100M 帯域幅 (トラフィック無制限)

2017年に設立されたロシアのVPS業者nuxt.cloudは、主にロシアのモスクワとドイツのデータ...

初心者マーケター必読:ウォール チャネルの 34 のポイントの概要とよくある質問への回答!

この記事の内容は次のとおりです。 概要:ポイントウォールとは何か? 流通チャネル 流通価格 効果評価...

Alibaba Cloud 上の複数のアカウントを一元管理

1. マルチアカウントアーキテクチャ設計Alibaba Cloud リソースディレクトリ (RD) ...

【AWSコミュニティディスカッション活動まとめ2】IoTクラウドプラットフォームについて語ろう

[51CTO.com からのオリジナル記事] スマートホーム、コネクテッドカー、スマートヘルスケア....

田鳳林:コンテンツに加えて構造も重要です

「コンテンツは王様」は、SEO で最も頻繁に議論されるトピックの 1 つです。しかし、比較的優れたコ...

簡単な分析: 高品質の外部リンクを作成するためのいくつかの方法

主に技術記事と製品という異なるタイプのサイトに分かれています。理由は、サイトの種類によって、直面する...

gcorelabs: ブラジルの VPS、ブラジルのサーバー、月額 4.49 ユーロから

gcorelabsは9月21日、ブラジルのサンパウロデータセンターで、ブラジルVPS、ブラジルサーバ...

ウェブサイトの微調整は不可欠であり、コンバージョンの向上が鍵となる

ご存知のとおり、企業ウェブサイトではコンバージョン率が鍵となります。トラフィックは非常に重要ですが、...

IDCはクラウド支出が2025年までに1.3兆ドルに達すると予測

[[424292]] IDC は、2025 年までに世界の「クラウド」支出総額が 1.3 兆ドルに達...

Baidu Shareが突然消えたが、それは一時的なものではない

最近ウェブマスター界隈で話題になっていた百度シェアが、急に効力が弱くなった。このウェブサイトの背後に...

hostyunの香港荃湾データセンターAMD高性能シリーズVPSの簡単なレビュー

Hostyunは香港の荃湾データセンターに新しいAMD高性能シリーズを追加しました。このシリーズは「...