SpringBootとデータベーステーブルレコードに基づく分散ロックの実装

SpringBootとデータベーステーブルレコードに基づく分散ロックの実装

[[386855]]

同じプロセス内の異なるスレッドが共有リソースを操作する場合、操作の正確性を確保するために、JUC のツールを使用するなどしてリソースをロックするだけで済みます。 JUC についてよく知らない方は、以下の記事をお読みください。

  • 同期の簡単な紹介
  • 同期最適化
  • JUC の礎 - 安全でないクラス

ただし、高可用性を実現するために、システムでは常に複数のコピーが別のマシンに分散されているため、同じプロセス内での上記のロック メカニズムは機能しなくなります。マルチコピーシステムによる共有リソースへのアクセスを保証するために、分散ロックを導入しました。

分散ロックの主な実装方法は次のとおりです。

  • データベースベース。これはさらに、データベースベースのテーブルレコード、悲観的ロック、楽観的ロックに分類されます。
  • Redisなどのキャッシュベース
  • Zookeeperに基づく

今日は、最もシンプルな分散ロックソリューションである、データベーステーブルレコードに基づく分散ロックを紹介します。

主な原則は、データベースの一意のインデックスを使用することです(データベースのインデックスについて知らない人は、私の別の記事「MySQLインデックスに関する簡単な説明」を参照してください)。

たとえば、次の表があります。

  1. 作成する テーブル`test`.`Untitled` (
  2. `id` int (11)はない  NULL AUTO_INCREMENT COMMENT 'シリアル番号の自動増分' ,
  3. ` name ` varchar (255) NOT   NULL COMMENT 'ロック名'
  4. `survival_time` int (11)はない  NULL COMMENT '生存時間(ミリ秒)'
  5. `create_time`タイムスタンプ(3) NOT   NULL  デフォルト  CURRENT_TIMESTAMP (3) COMMENT '作成時刻'
  6. `thread_name` varchar (255) NOT   NULL COMMENT 'スレッド名'
  7. 主要な  KEY (`id`) BTREEの使用、
  8. 個性的 インデックス`uk_name`(` name `) BTREE 使用
  9. ) ENGINE = InnoDB ROW_FORMAT =動的;

名前フィールドに一意のインデックスが追加されます。同じ名前値を持つ複数の新しい操作の場合、データベースは 1 つの操作のみが成功することだけを保証できます。その他の操作は拒否され、「重複キー」エラーがスローされます。

次に、システム 1 が分散ロックを取得する準備ができたら、name="key" のレコードをデータベースに挿入しようとします。挿入が成功した場合、ロックが正常に取得されたことを意味します。他のシステムが分散ロックを取得する場合は、同じ名前のレコードをデータベースに挿入する必要もあります。もちろん、データベースはエラーを報告し、挿入は失敗します。これは、これらのシステムがロックを取得できなかったことを意味します。システム 1 がロックを解除したい場合、このレコードを削除できます。 thread_name 列を使用すると、スレッドによって作成されたロックのみがアクティブに解放されることを確認できます。

私たちが実現したい分散ロックには、次のような効果があります。

  1. ロックを取得することはブロックであり、取得できない場合はブロックされます。
  2. ロックの有効期間が終了すると、ロックは無効になり、自動的に解除されます。これにより、ダウンタイムのために一部のシステムがロックを積極的に解放できないという問題を回避できます。

一般的なフローチャートは次のとおりです。

次の依存関係が使用されます。

  • スプリングブート
  • MyBatisプラス
  • ロンボク

プロジェクトディレクトリは次のとおりです。

pom ファイルで使用される依存関係は次のとおりです。

  1. <依存関係>
  2. <依存関係>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </依存関係>
  6.  
  7. <依存関係>
  8. <groupId>org.projectlombok</groupId>
  9. <artifactId>ロンボク</artifactId>
  10. <バージョン>1.18.6</バージョン>
  11. </依存関係>
  12.  
  13. <依存関係>
  14. <グループID>mysql</グループID>
  15. <artifactId>mysql-コネクタ-java</artifactId>
  16. </依存関係>
  17.  
  18. <依存関係>
  19. <groupId>com.baomidou</groupId>
  20. <artifactId>mybatis-plus-boot-starter</artifactId>
  21. <バージョン>3.3.1</バージョン>
  22. </依存関係>
  23.  
  24. <依存関係>
  25. <groupId>com.baomidou</groupId>
  26. <artifactId>mybatis-plus-extension</artifactId>
  27. <バージョン>3.3.1</バージョン>
  28. </依存関係>
  29.  
  30. <依存関係>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-test</artifactId>
  33. <scope>テスト</scope>
  34. </依存関係>
  35. </依存関係>

構成項目は次のとおりです。

  1. サーバ:
  2. ポート: 9091
  3.  
  4.  
  5. 春:
  6. データソース:
  7. ドライバークラス: com.mysql.cj.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/test?useUnicode= true &characterEncoding=UTF-8&useSSL= false &serverTimezone=Asia/Shanghai
  9. ユーザー名: root
  10. パスワード: a123
  11.  
  12. ログ記録:
  13. レベル
  14. ルート:情報

データベース フィールドをマップするために使用されるエンティティ クラスは次のとおりです。

  1. パッケージ com.yang.lock1.entity;
  2.  
  3. com.baomidou.mybatisplus.annotation.IdType をインポートします。
  4. com.baomidou.mybatisplus.annotation.TableField をインポートします。
  5. com.baomidou.mybatisplus.annotation.TableId をインポートします。
  6. com.baomidou.mybatisplus.annotation.TableName をインポートします。
  7. インポート lombok.AllArgsConstructor;
  8. lombok.Data をインポートします。
  9. lombok.NoArgsConstructor をインポートします。
  10.  
  11. java.util.Dateをインポートします
  12.  
  13. /**
  14. * @著者 qcy
  15. *@作成2020/08/25 15:03:47
  16. */
  17. @データ
  18. @NoArgsコンストラクタ
  19. @テーブル名(値 = "t_lock" )
  20. パブリッククラスLock {
  21.  
  22. /**
  23. * 自己増加シーケンス番号
  24. */
  25. @TableId(値 = "id" 、タイプ = IdType.AUTO)
  26. プライベート整数ID;
  27.  
  28. /**
  29. * ロック名
  30. */
  31. プライベート文字列;
  32.  
  33. /**
  34. * 生存時間(ミリ秒)
  35. */
  36. プライベートint生存時間;
  37.  
  38. /**
  39. * ロックが作成された時刻
  40. */
  41. プライベート日付createTime;
  42.  
  43. /**
  44. * スレッド名
  45. */
  46. プライベート文字列スレッド名;
  47. }

道層:

  1. パッケージ com.yang.lock1.dao;
  2.  
  3. com.baomidou.mybatisplus.core.mapper.BaseMapper をインポートします。
  4. com.yang.lock1.entity.Lock をインポートします。
  5. org.apache.ibatis.annotations.Mapper をインポートします。
  6.  
  7. /**
  8. * @著者 qcy
  9. *@作成2020/08/25 15:06:24
  10. */
  11. @マッパー
  12. パブリックインターフェースLockDaoはBaseMapper<Lock>を拡張します。
  13. }

サービスインターフェース層:

  1. パッケージ com.yang.lock1.service;
  2.  
  3. com.baomidou.mybatisplus.extension.service.IService をインポートします。
  4. com.yang.lock1.entity.Lock をインポートします。
  5.  
  6. /**
  7. * @著者 qcy
  8. *@作成2020/08/25 15:07:44
  9. */
  10. パブリックインターフェースLockServiceはIService<Lock>を拡張します。
  11.  
  12. /**
  13. * 分散ロックのブロック取得
  14. *
  15. * @param nameロック名
  16. * @param survivalTime 生存時間
  17. */
  18. void lock(String name , int survivalTime);
  19.  
  20. /**
  21. * ロックを解除する
  22. *
  23. * @param nameロック名
  24. */
  25. パブリックvoid unLock(文字列);
  26. }

サービス実装層:

  1. パッケージ com.yang.lock1.service.impl;
  2.  
  3. com.baomidou.mybatisplus.extension.service.impl.ServiceImpl をインポートします。
  4. com.yang.lock1.dao.LockDao をインポートします。
  5. com.yang.lock1.entity.Lock をインポートします。
  6. com.yang.lock1.service.LockService をインポートします。
  7. lombok.extern.slf4j.Slf4j をインポートします。
  8. org.springframework.dao.DuplicateKeyException をインポートします。
  9. org.springframework.stereotype.Service をインポートします。
  10.  
  11. java.util.Dateをインポートします
  12.  
  13. /**
  14. * @著者 qcy
  15. *@作成2020/08/25 15:08:25
  16. */
  17. 翻訳者
  18. @サービス
  19. パブリッククラス LockServiceImpl は ServiceImpl<LockDao, Lock> を拡張し、LockService を実装します {
  20.  
  21. @オーバーライド
  22. パブリックvoid lock(String name , int survivalTime) {
  23. 文字列 threadName = "system1-" + Thread.currentThread().getName();
  24. )の間{
  25. ロック lock = this.lambdaQuery().eq(Lock::getName, name ).one();
  26. ロック == nullの場合
  27. //説明 ロックなし
  28. ロックlk = 新しいロック();
  29. lk.setName(名前);
  30. lk.setSurvivalTime(生存時間);
  31. lk.setThreadName(スレッド名);
  32. 試す {
  33. 保存(lk);
  34. log.info(threadName + "ロックが正常に取得されました" );
  35. 戻る;
  36. } キャッチ (DuplicateKeyException e) {
  37. //再試行を続ける
  38. log.info(threadName + "ロックの取得に失敗しました" );
  39. 続く;
  40. }
  41. }
  42.  
  43. //この時点でロックが存在するので、ロックが期限切れかどうかを判断します
  44. 現在の日付= 新しい日付();
  45. 日付expireDate = new Date (lock.getCreateTime().getTime() + lock.getSurvivalTime());
  46. 有効期限が現在より前の場合
  47. //ロックの有効期限が切れました
  48. ブール値の結果 = removeById(lock.getId());
  49. if (結果) {
  50. log.info(threadName + "期限切れのロックを削除しました" );
  51. }
  52.  
  53. //ロックを取得しようとする
  54. ロックlk = 新しいロック();
  55. lk.setName(名前);
  56. lk.setSurvivalTime(生存時間);
  57. lk.setThreadName(スレッド名);
  58. 試す {
  59. 保存(lk);
  60. log.info(threadName + "ロックが正常に取得されました" );
  61. 戻る;
  62. } キャッチ (DuplicateKeyException e) {
  63. log.info(threadName + "ロックの取得に失敗しました" );
  64. }
  65. }
  66. }
  67.  
  68. }
  69.  
  70. @オーバーライド
  71. パブリックvoid unLock(文字列) {
  72. //ロックを解除するときは、作成したロックのみを解除できることに注意する必要があります
  73. 文字列 threadName = "system1-" + Thread.currentThread().getName();
  74. ロック lock = lambdaQuery().eq(Lock::getName, name ).eq(Lock::getThreadName, threadName).one();
  75. if (ロック != null ) {
  76. ブール値 b = removeById(lock.getId());
  77. (b) の場合 {
  78. log.info(threadName + "ロックを解除しました" );
  79. }それ以外{
  80. log.info(threadName + "ロックを解除する準備はできましたが、ロックの有効期限が切れており、他のクライアントによって強制的に解除されました" );
  81. }
  82. }それ以外{
  83. log.info(threadName + "ロックを解除する準備はできましたが、ロックの有効期限が切れており、他のクライアントによって強制的に解除されました" );
  84. }
  85. }
  86.  
  87. }

テストクラスは次のとおりです。

  1. パッケージ com.yang.lock1;
  2.  
  3. com.yang.lock1.service.LockService をインポートします。
  4. lombok.extern.slf4j.Slf4j をインポートします。
  5. org.junit.Test をインポートします。
  6. org.junit.runner.RunWith をインポートします。
  7. org.springframework.boot.test.context.SpringBootTest をインポートします。
  8. org.springframework.test.context.junit4.SpringRunner をインポートします。
  9.  
  10. javax.annotation.Resource をインポートします。
  11.  
  12. /**
  13. * @著者 qcy
  14. *@作成2020/08/25 15:10:54
  15. */
  16. 翻訳者
  17. SpringRunner クラスで実行します。
  18. @SpringBootテスト
  19. パブリッククラスLock1ApplicationTest {
  20.  
  21. @リソース
  22. ロックサービスロックサービス;
  23.  
  24. @テスト
  25. パブリックボイドテストロック(){
  26. log.info( "system1はロックを取得する準備ができています" );
  27. lockService.lock( "キー" 、 6 * 1000);
  28. 試す {
  29. //シミュレーション業務時間
  30. スレッドをスリープ状態にします(4 * 1000);
  31. } キャッチ (例外 e) {
  32. e.printStackTrace();
  33. ついに
  34. lockService.unLock( "キー" );
  35. }
  36. }
  37.  
  38. }

コードをコピーし、system1 を system2 に変更します。次に、両方のシステムを起動します。

system1 の出力は次のとおりです。


system2 の出力は次のとおりです。


23.037 秒に、system1 はロックの取得を試み、23.650 秒に成功し、分散ロックを保持します。 26 秒目に、system2 はロックを取得しようとしますが、ブロックされます。 27.701秒に、system1はロックを解除しました。 System2 は 27.749 秒でロックを取得し、31 秒でロックを解除しました。

ここで、system1 のサービス期間を 10 秒に変更し、system2 が system1 のタイムアウト ロックを解除するシナリオをシミュレートできます。

まずシステム1を起動し、次にシステム2を起動します

このとき、system1 の出力は次のようになります。


system2 の出力は次のとおりです。


14 秒で、システム 1 がロックを取得し、その後、ビジネスは予想よりも時間がかかり、10 秒間実行する必要がありました。この間に、system1 によって作成されたロックの有効期間が終了しました。このとき、system2 は 19 秒で期限切れのロックを削除し、その後ロックを取得しました。 24 秒後、システム 1 は振り返って、ロックが解除されたことに気づきました。最後に、system2 は正常にロックを解除しました。

データベースに基づく分散ロックの実装、および悲観的ロックと楽観的ロックの方法について、別の記事を書く予定です。

<<:  ビッグデータとクラウドコンピューティングの深い統合はどのような側面に反映されていますか?

>>:  Kubernetes ベースのハイブリッド クラウドの長所と短所

推薦する

ローカルオンライン旅行ウェブサイトのユーザー定着率の秘密

インターネットで最も人気のある旅行ウェブサイトのほとんどが、ホテル、チケット、サービスなどの補助的な...

大学マーケティングのための効果的なプロモーションチャネルを確立するにはどうすればよいでしょうか?

大学マーケティングは主に学生を対象としています。学校が比較的集中しており、学生の購買力が強いため、効...

Flashサイトの最適化の難しさに関する4つの簡単な説明

Flash サイトは、おそらく最適化担当者にとって最も厄介なタイプのサイトの 1 つです。 Goog...

おすすめ: a2hosting - 無制限ホスティング (SSD) が 58% オフ

a2hostingは、11月27日から12月2日まで、cloudvpsを除くすべてのホストを48%割...

IBMがターボノミックを15~20億ドルで買収へ

最近、IBM は、同社の AIOps 製品を改善するために、アプリケーション リソースとネットワーク...

ウェブサイトのデータ分析と収集を簡単に完了する5つのステップ

ウェブサイトや製品の運用については、孤立した状態や紙の上だけで議論することはできず、実際のデータに基...

電子商取引が欧州の伝統的小売業者に影響:賃料は下落を余儀なくされる

北京時間2月19日早朝、欧州最大の不動産ファンド運用会社アクサ・リアル・エステートは月曜日、今後数年...

ウェブサイト運営の「大スローガン」の裏側をユーモラスに描く

SEO最適化のエラーといえば、多くの友人がキーワードスタッキング、ブラックリンクの購入、または疑似オ...

アンカーテキストリンクを見つける方法についての経験を共有してください

少し前に、Baidu のメジャーアップデートにより、多くのウェブサイトが深刻なダメージを受けましたが...

クロスボーダーボーイ | 画像 SEO 最適化: 実用的な 16 のヒント

目を道に向け、足を地につけ、心の中に壮大な計画を持ちなさい。みなさんこんにちは。Riven@Cros...

クラウドコンピューティング契約に署名する際に注意すべきいくつかの点

Forrester Research の調査によると、クラウド コンピューティング市場は年間 22%...

ウェブサイトのランキングの「可愛さ」から判断すると、コンテンツが多いだけでは十分ではありません。

QQタイプのウェブサイトを運営している友人なら誰でも、「Kwai Dian」というウェブサイトに注目...

MozillaがWebXR標準を提案し、複合現実技術を標準化すると発表

Mozilla は、ユーザーが使用しているデバイスに関係なく、Web 上で複合現実と拡張現実が機能す...

オンラインストアのプロモーションは状況を評価し、タオバオプロモーションの無限の可能性を活用します

序文タオバオの最新の動きは大きなもので、6月1日からタオバオの商品検索サービスを禁止することになる。...

ウェブサイトのタイトルが見つかりませんか?接続が切れるのは検索エンジンの遅れによるものでしょうか?

多くの人がウェブサイトを最適化するとき、サイト内最適化とサイト外最適化の両方を怠ることはありません。...