Redis分散ロックの実装

Redis分散ロックの実装

[[335642]]

序文

日々の開発では、ロックが必要な状況に必然的に遭遇します。たとえば、製品在庫を減算するには、まずデータベースから在庫を取得し、在庫決定を実行してから在庫を減算する必要があります。この一連の操作は明らかにアトミック性に準拠していません。コード ブロックがロックされていない場合、同時実行による過剰販売の問題が発生する可能性が高くなります。システムがモノリシック アーキテクチャである場合は、ローカル ロックを使用することで問題を解決できます。分散アーキテクチャの場合は、分散ロックが必要です。

プラン

SETNXコマンドとEXPIREコマンドの使用

  1. SETNXキー値 
  2. EXPIRE キー秒数 
  3. DELキー 
  4. (setnx("item_1_lock", 1)) の場合 {  
  5. 有効期限が切れます("item_1_lock", 30);  
  6. 試す {  
  7. ... ロジック 
  8. } キャッチ {  
  9. ...  
  10. ついに 
  11. del("item_1_lock");  
  12. }  
  13. }

この方法は問題を解決するように見えますが、SETNX および EXPIRE 操作は非アトミックであるため、一定のリスクがあります。 SETNX が成功した後にエラーが発生した場合、ロックにタイムアウト期間がないため、EXPIRE は実行されず、デッドロックが発生します。

この場合、Lua スクリプトを使用して操作のアトミック性を維持し、SETNX 操作と EXPIRE 操作の両方が成功または失敗することを確認できます。

  1. if (redis.call('setnx', KEYS[1], ARGV[1]) <   1 )  
  2. 0 を返します。  
  3. 終わり;  
  4. redis.call('expire', KEYS[1], tonumber(ARGV[2]));  
  5. 1 を返します。

この方法により、競合するロックの原子性の問題を暫定的に解決しました。他の機能はまだ実装されていませんが、デッドロックは発生しないはずです🤪🤪🤪。

Redis 2.6.12以降ではSETコマンドを柔軟に使用できます

  1. キー値の設定 NX EX 30  
  2. DELキー 
  3. if (set("item_1_lock", 1, "NX", "EX", 30)) {  
  4. 試す {  
  5. ... ロジック 
  6. } キャッチ {  
  7. ...  
  8. ついに 
  9. del("item_1_lock");  
  10. }  
  11. }

改良された方法は、Lua スクリプトを使用せずに SETNX および EXPIRE の原子性問題を解決します。では、よく考えてみましょう。 A がロックを取得し、コード ブロックに正常に入力してロジックを実行したが、さまざまな理由によりタイムアウトになり、ロックが自動的に解放された場合。その後、B はロックを正常に取得し、コード ブロックに入ってロジックを実行します。ただし、A がロジック実行を完了した後にロックを解除すると、B が取得したばかりのロックも解除されます。それは自分の鍵を使って他人のドアを開けるようなもので、許されることではありません。

この問題を解決するには、SET 時にロック フラグを設定し、DEL 時に現在のロックが自分のロックであるかどうかを確認します。

  1. 文字列= UUID .randomUUID().toString().replaceAll("-", "");  
  2. if (set("item_1_lock", 値, "NX", "EX", 30)) {  
  3. 試す {  
  4. ... ロジック 
  5. } キャッチ {  
  6. ...  
  7. ついに 
  8. ... lua スクリプトはアトミック性を保証します 
  9. }  
  10. }  
  11. redis.call('get', KEYS[1]) == ARGV[1]の場合 
  12. その後、redis.call('del', KEYS[1]) を返します。  
  13. それ以外の場合は0を返す 
  14. 終わり

この時点で、競合するロックの原子性の問題と、誤ってロックを削除する問題がようやく解決されました。ただし、ロックは通常、再入、循環待機、タイムアウト時の自動更新などの機能もサポートする必要があります。次に、これらの問題を解決するために非常に便利なパッケージの使用方法を学びます。

Redissonを使い始める

Redission のロックは、再入可能およびタイムアウトの自動更新機能を実装しており、これらはすべてカプセル化されています。上記のいくつかの機能ポイントを簡単に実装するには、必要に応じて API を呼び出すだけです。詳細な機能については、Redissonのドキュメントを参照してください。

プロジェクトにRedissonをインストールする

  1. <依存関係>    
  2. <グループID> org.redisson</グループID>    
  3. <artifactId>再配布</artifactId>    
  4. <バージョン> 3.13.2</バージョン>    
  5. </依存関係>  
  1. 実装 'org.redisson:redisson:3.13.2'

Maven または Gradle を使用してビルドします。最新バージョンは3.13.2です。必要なバージョンは、Redisson でも見つかります。

単純な試み

  1. RedissonClient redissonClient = Redisson .create();  
  2. RLockロック= redissonClient .getLock("lock");  
  3. ブール値res = lock .lock();  
  4. もし(res){  
  5. 試す {  
  6. ... ロジック 
  7. ついに 
  8. ロックを解除します。  
  9. }  
  10. }

Redisson は基礎となるロジックをすべてカプセル化します。具体的な実装については気にする必要はありません。ほんの数行のコードで完璧なロックを使用できます。次に、ソースコードを簡単にいじってみましょう🤔🤔🤔。

ロック

  1. private void lock(longleasingTime, TimeUnit unit, boolean interruptibly) は InterruptedException をスローします {  
  2. 長いthreadId =スレッド.currentThread().getId();  
  3. Long ttl = tryAcquire (leaseTime、unit、threadId);  
  4. ttl == nullの場合{
  5.   戻る;  
  6. }  
  7. RFuture < RedissonLockEntry >  将来=サブスクライブ(スレッド ID);  
  8. if (割り込み可能) {  
  9. コマンドExecutor.syncSubscriptionInterrupted(将来);  
  10. } それ以外 {  
  11. コマンドExecutor.syncSubscription(将来);
  12. }  
  13. 試す {  
  14. (真)の間{  
  15. ttl = tryAcquire (リース時間、ユニット、スレッド ID);  
  16. ttl == nullの場合{  
  17. 壊す;  
  18. }  
  19. ttl > = 0 の場合 
  20. 試す {  
  21. future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  
  22. } キャッチ (InterruptedException e) {  
  23. if (割り込み可能) {  
  24. eを投げる;  
  25. }  
  26. future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);  
  27. }  
  28. } それ以外 {  
  29. if (割り込み可能) {  
  30. 将来。getNow()。getLatch()。取得();  
  31. } それ以外 {  
  32. future.getNow().getLatch().acquireUninterruptibly();  
  33. }  
  34. }
  35. }  
  36. ついに 
  37. 購読を解除します(将来、スレッドID);  
  38. }  
  39. }

ロックを取得

  1. プライベート< T > RFuture < Long > tryAcquireAsync(long リースタイム、TimeUnit ユニット、long スレッド ID) {  
  2. リースタイムが -1 の場合 
  3. tryLockInnerAsync(leaseTime、ユニット、スレッドId、RedisCommands.EVAL_LONG) を返します。  
  4. }  
  5. RFuture <長い>   ttlRemainingFuture = tryLockInnerAsync (commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
  6.   ttlRemainingFuture.onComplete((ttlRemaining, e) - > {  
  7. e != null の場合 
  8. 戻る;  
  9. }  
  10. 残り時間== null の場合 
  11. スケジュール有効期限更新(スレッドID)  
  12. }  
  13. });  
  14. ttlRemainingFuture を返します。  
  15. }  
  16. < T > RFuture < T > tryLockInnerAsync(long リースタイム、TimeUnit ユニット、long スレッド ID、RedisStrictCommand < T >コマンド) {  
  17. 内部ロックリース時間= unit .toMillis(リース時間);
  18. evalWriteAsync(getName(), LongCodec.INSTANCE, コマンド, を返します。  
  19. 「(redis.call('exists', KEYS[1]) == 0) の場合」 +  
  20. "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  
  21. "redis.call('pexpire', KEYS[1], ARGV[1]); " +  
  22. "nil を返す; " +  
  23. 「終了;」+  
  24. 「(redis.call('hexists', KEYS[1], ARGV[2]) == 1) の場合」 +  
  25. "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  
  26. "redis.call('pexpire', KEYS[1], ARGV[1]); " +  
  27. "nil を返す; " +  
  28. 「終了;」+  
  29. "redis.call('pttl', KEYS[1]); を返します",  
  30. Collections.singletonList(getName())、internalLockLeaseTime、getLockName(threadId));  
  31. }

ロックの削除

  1. パブリック RFuture < Void > unlockAsync(long threadId) {  
  2. RPromise <無効>  結果=新しいRedissonPromise < Void > ();  
  3. RFuture <ブール値>  将来= unlockInnerAsync (スレッドID);  
  4. future.onComplete((opStatus, e) - > {  
  5. 有効期限の更新をキャンセルします(スレッドID);  
  6. e != null の場合
  7.   結果.tryFailure(e);  
  8. 戻る;  
  9. }  
  10. opStatusが null の場合
  11.   IllegalMonitorStateException原因= new IllegalMonitorStateException("ロックを解除しようとしましたが、ノード ID によって現在のスレッドによってロックされていません: "  
  12. + id + " スレッドID: " + threadId);  
  13. 結果.tryFailure(原因);  
  14. 戻る;
  15.   }  
  16. 結果.trySuccess(null);  
  17. });  
  18. 結果を返します。  
  19. }  
  20. 保護された RFuture <ブール値> unlockInnerAsync(long threadId) {  
  21. evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, を返します。  
  22. 「(redis.call('hexists', KEYS[1], ARGV[3]) == 0) の場合」 +  
  23. "nil を返します。" +  
  24. 「終了;」+  
  25. "ローカルカウンタ= redis .call('hincrby', KEYS[1], ARGV[3], -1); " +  
  26. 「もし(カウンタ 0)ならば」+  
  27. "redis.call('pexpire', KEYS[1], ARGV[2]); " +  
  28. "0を返す; " +  
  29. 「その他」+  
  30. "redis.call('del', KEYS[1]); " +  
  31. "redis.call('publish', KEYS[2], ARGV[1]); " +  
  32. "1を返す; " +  
  33. 「終了;」+  
  34. "nil を返す;",  
  35. Arrays.asList(getName(), getChannelName())、LockPubSub.UNLOCK_MESSAGE、internalLockLeaseTime、getLockName(threadId));  
  36. }

要約する

同時実行性の問題を解決するために Redis を分散ロックとして使用するには、まだいくつかの困難があり、注意が必要な点が多数あります。システムの規模を正しく評価し、特定の技術を使用するためだけにシステムを使用するべきではありません。同時実行の問題を完全に解決するには、データベース レベルで作業する必要があります。

<<:  Kubernetes の他に、重要なコンテナ オーケストレーション ツールは何ですか?

>>:  クラウドコンピューティングの8つの主な特徴

推薦する

卵と 30 セントではどちらがユーザー エクスペリエンスが向上するでしょうか?

生鮮食品スーパーは、これまでも一部の電子商取引企業の夢であり、一部の電子商取引大手もこの分野に参入し...

Baidu は検索エンジンのユーザー エクスペリエンス アルゴリズムを判断するためにどのようなデータを使用していますか?

検索エンジンが新しいアルゴリズムの時代に入って以来、検索エンジンのウェブサイトのスコアは、基本エクス...

クラウドコンピューティングの収益は2028年までに6,290億ドルに達する

従来のシステムからクラウドベースのソリューションに部分的または完全に移行しようとする企業がますます増...

北京のウェブサイト60件がポルノコンテンツに関する調査を受けて閉鎖され、8件のウェブサイトが登録抹消された。

文化資本を構築し、文化市場を浄化することが急務です。記者が昨日知ったところによると、12月20日現在...

SEO ウェブサイト最適化 ウェブサイト構造最適化 スキル共有

SEO ウェブサイトの最適化に関しては、オンサイトとオフサイトだけが重要です。諺にあるように、経済基...

世界のエッジコンピューティング市場 2020-2026: IoT アプリケーションの急速な成長

ResearchAndMarkets が発表したエッジ コンピューティング分析レポートによると、世界...

検索エンジン最適化: Baidu がウェブサイトをインデックスしない理由

Baidu は長い間、新しいサイトを組み込んでいません。この間、私たちは Baidu に新しいサイト...

#黒5# お問い合わせ: 全製品の2か月目は無料(VPS、VDS、専用サーバー)、監視およびバックアップスペースは50%オフ!

11月27日より、contaboはブラックフライデーセールを開始します。米国およびドイツのデータセン...

レスポンシブ Web デザインで考慮すべき 5 つの予算要素

[編集者注] この記事の著者である Brad Frost は、ニューヨークのデジタル インタラクティ...

chicagovps-2g メモリ/50g ハードディスク/2IP/2T トラフィック/月額 2.66 ドル

私のメールの中に、Chicagovps からのプロモーション メールが届いていました。基本的には、「...

AIとクラウドコンピューティングが相互に利益をもたらし、ビジネス効率を向上させる方法

長年にわたり、クラウド コンピューティングは現代のビジネスに欠かせないツールとなり、2020 年には...

友情関係において無視できないいくつかのポイントについて話す

また、a5ウェブマスターのウェブサイトでも、フレンドリーリンクに関する注意事項に関する同様の記事をた...

企業がハイブリッドおよびマルチクラウドシステムを構築するにつれて、セキュリティの問題は悪化する可能性が高い

[[410724]]調査によると、クラウド コンピューティング サービスの導入を加速させるプレッシャ...

3分レビュー! 11月のクラウドコンピューティング分野の重要な動向を簡単に見てみましょう

今年に入ってから、クラウドライブストリーミング、クラウド教室、クラウドフィットネスなどの人気が高まり...

戴志康:中小規模のウェブマスターにとってのチャンスは垂直分割産業にある

最近のニュースによると、2012年中国ウェブマスターカンファレンスの重慶大会が4月21日に重慶南平国...