MySQLは実際に分散ロックを実装できる

MySQLは実際に分散ロックを実装できる

序文

前回の記事では、eコマース シナリオでのフラッシュ セールの例を通じて、モノリシック アーキテクチャでロックを使用する方法を紹介しました。ただし、現在では多くのアプリケーション システムが非常に大規模になっており、多くのアプリケーション システムはマイクロサービス アーキテクチャ システムです。では、このクロス JVM シナリオでは、並行性をどのように解決すればよいのでしょうか。

モノリシック アプリケーション ロックの制限

実際の実践に入る前に、インターネット システムのアーキテクチャの進化について簡単にお話ししたいと思います。

インターネット システムの開発当初は、リソースの消費量は比較的少なく、ユーザー数も比較的少なかったため、ニーズを満たすには 1 つの Tomcat アプリケーションを展開するだけで済みました。 Tomcat を JVM プロセスと見なすことができます。大量のリクエストが同時にシステムに到着すると、すべてのリクエストがこの 1 つの Tomcat に送られます。前の記事で説明したフラッシュ セールや在庫削減のシナリオなど、一部のリクエスト メソッドをロックする必要がある場合、この Tomcat はニーズを満たすことができます。しかし、アクセス数が増えてくると、1台のTomcatでは対応しきれなくなります。このとき、Tomcatをクラスターにデプロイし、複数のTomcatでシステムをサポートする必要があります。

上図の単純な進化の後、システムを共同でサポートするために 2 つの Tomcat を展開します。リクエストがシステムに到着すると、まず nginx を通過します。nginx はロード バランサとして機能し、独自のロード バランシング構成戦略に従って、リクエストをいずれかの tomcat に転送します。多数のリクエストが同時にアクセスされた場合、2 つの Tomcat がすべてのトラフィックを共有します。この後、在庫を減らすためにフラッシュセールを実施する場合、単一のアプリケーションロックを使用して需要を満たすことはできますか?

先ほど追加したロックは、JDK が提供するロックです。このロックは単一の JVM で動作します。2 台以上ある場合、大量の同時リクエストが異なる Tomcat に分散されます。各 Tomcat では同時実行を防ぐことができます。ただし、複数の Tomcat 間では、各 Tomcat のロック取得リクエストによって、再び同時実行が発生します。したがって、在庫を減算するという問題は依然として存在します。これは単一アプリケーション ロックの制限です。では、この問題をどう解決すればよいのでしょうか?次に、分散ロックについて説明します。

分散ロック

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

では、分散ロックとは何でしょうか? 分散ロックについて説明する前に、単一アプリケーション ロックの特徴は、1 つの JVM 内では有効ですが、複数の JVM やプロセス間では有効ではないという点であることがわかります。したがって、あまり公式ではない定義をすることができます。分散ロックとは、複数の JVM と複数のプロセスにまたがるロックです。このようなロックが分散ロックです。

デザインのアイデア

Tomcat は Java によって起動されるため、各 Tomcat は JVM と見なすことができ、JVM 内のロックは複数のプロセスにまたがることはできません。したがって、分散ロックを実装する場合は、これらの JVM の外部でそれらを探し、他のコンポーネントを通じて実装することしかできません。

上の図では、2 つの Tomcat がサードパーティ コンポーネントを使用して、JVM 間およびプロセス間の分散ロックを実装しています。これは分散ロックのソリューションです。

実装

では、これを実現するために現在利用できるサードパーティ コンポーネントは何でしょうか?より人気のあるものは次のとおりです。

  • データベースはデータベースを通じて分散ロックを実装できますが、同時実行性が高いとデータベースに大きな負担がかかるため、ほとんど使用されません。
  • Redis では、Redis の助けを借りて分散ロックを実装することができ、Redis Java クライアントの種類が多数あるため、使用方法も異なります。
  • Zookeeper は分散ロックも実装できます。同様に、zk にも多くの Java クライアントがあり、使用方法も異なります。

上記の実装方法については、Lao Mao が具体的なコード例を通じて 1 つずつ実演しています。

データベースに基づく分散ロック

アイデア: 主に更新に select ... を使用して、データベース悲観的ロックに基づく分散ロックを実装します。 select ... for update の目的は、クエリ中にクエリされたデータをロックすることです。ユーザーがこの種の操作を実行すると、他のスレッドはデータを変更または削除できなくなります。他のスレッドは、前のスレッドが操作を完了して解放するまで待機してから操作を続行する必要があります。これにより、ロックの効果が得られます。

実装: 電子商取引における過剰販売の例に基づいてコードを共有します。

前回のモノリシック アーキテクチャのオーバーセリングの例を使って、皆さんと共有しましょう。前回のコードを修正し、distribute_lock という新しいテーブルを作成します。このテーブルの主な目的は、データベース ロックを提供することです。このテーブルの状況を見てみましょう。

売られ過ぎ注文のシナリオをシミュレートしているため、上図の注文のロック データがあります。

前の記事のコードを変更してコントローラーを抽出し、Postman を介して呼び出しを要求します。もちろん、ポート 8080 とポート 8081 の 2 つの JVM がバックグラウンドで起動されて動作します。完成したコードは次のとおりです。

/**
 * @著者 [email protected]
 * @日付 2021/1/3 10:48
 * @desc 公開アカウント「プログラマーおじさん」
 */
@サービス
翻訳者
パブリッククラスMySQLOrderService {
  @リソース
  プライベート KdOrderMapper orderMapper;
  @リソース
  プライベート KdOrderItemMapper orderItemMapper;
  @リソース
  プライベート KdProductMapper productMapper;
  @リソース
  プライベートDistributeLockMapperdistributeLockMapper;
  //購入商品ID
  プライベート int 購入製品 ID = 100100;
  //購入商品の数量 private int purchaseProductNum = 1;
  
  @Transactional(伝播 = 伝播.REQUIRED)
  パブリックInteger createOrder()は例外をスローします{
    log.info("メソッドを入力しました");
    DistributeLock ロック = distributorLockMapper.selectDistributeLock("order");
    if(lock == null) throw new Exception("このビジネスの分散ロックは構成されていません");
    log.info("ロックを取得しました");
    //ここでは、並行性を手動で実証するために 1 分間スリープします。Thread.sleep(60000);

    KdProduct 製品 = productMapper.selectByPrimaryKey(purchaseProductId);
    if (product==null){
      新しい例外をスローします("購入製品: "+purchaseProductId+" は存在しません");
    }
    //商品の現在の在庫 Integer currentCount = product.getCount();
    log.info(Thread.currentThread().getName()+"在庫数"+currentCount);
    //在庫を確認する if (purchaseProductNum > currentCount) {
      throw new Exception("Product"+purchaseProductId+" には "+currentCount+" 個のアイテムしか残っていないため、購入できません");
    }

    //データベース内の削減操作を完了します。productMapper.updateProductCount(purchaseProductNum,"kd",new Date(),product.getId());
    // 順序を生成... 回数は省略されています。ソースコードは Lao Mao の github からダウンロードできます: https://github.com/maoba/kd-distribute
    order.getId() を返します。
  }
}

SQL は次のように記述されます。

選択
  *
  配布ロックから
  ここで、business_code = #{business_code,jdbcType=VARCHAR}
  更新用

上記が主な実装ロジックです。コード内の以下の点に注意してください。

  • 更新ロックの選択はトランザクションが存在する場合にのみトリガーできるため、createOrder メソッドにはトランザクションが必要です。
  • コードは現在のロックの存在を判断する必要があります。空の場合は、例外が報告されます。

最終的な実行効果を見てみましょう。まず、コンソール ログを確認します。

8080 のコンソール ログは次のとおりです。

11:49:41 INFO 16360 --- [nio-8080-exec-2] ckdservice.MySQLOrderService: 入力されたメソッド
11:49:41 INFO 16360 --- [nio-8080-exec-2] ckdservice.MySQLOrderService: ロックを取得しました

8081 のコンソール ログは次のとおりです。

11:49:48 INFO 17640 --- [nio-8081-exec-2] ckdservice.MySQLOrderService: 入力されたメソッド

ログから、2 つの異なる JVM の 8080 への最初のリクエストが最初にロックを取得し、8081 へのリクエストは実行前にロックが解放されるのを待機していることがわかります。これは、分散ロックが効果的であることを示しています。
実行が完了した後のログを見てみましょう。

8080のリクエスト:

11:58:01 INFO 15380 --- [nio-8080-exec-1] ckdservice.MySQLOrderService: 入力されたメソッド
11:58:01 INFO 15380 --- [nio-8080-exec-1] ckdservice.MySQLOrderService: ロックを取得しました
11:58:07 INFO 15380 --- [nio-8080-exec-1] ckdservice.MySQLOrderService : http-nio-8080-exec-1 在庫数 1

8081 のリクエスト:

11:58:03 INFO 16276 --- [nio-8081-exec-1] ckdservice.MySQLOrderService: 入力されたメソッド
11:58:08 INFO 16276 --- [nio-8081-exec-1] ckdservice.MySQLOrderService: ロックを取得しました
11:58:14 INFO 16276 --- [nio-8081-exec-1] ckdservice.MySQLOrderService : http-nio-8081-exec-1 在庫数 0
11:58:14 エラー 16276 --- [nio-8081-exec-1] oaccC[.[.[/].[dispatcherServlet] : コンテキスト [] のサーブレット [dispatcherServlet] の Servlet.service() が例外 [リクエスト処理に失敗しました。ネストされた例外は java.lang.Exception です: 製品 100100 の残りアイテムが 0 個のみ、購入できません] をスローしました。根本的な原因

java.lang.Exception: アイテム 100100 は残り 0 個しかなく、購入できません。
com.kd.distribute.service.MySQLOrderService.createOrder(MySQLOrderService.java:61) で ~[classes/:na]

明らかに、2 番目のリクエストは在庫不足のため失敗しました。もちろん、このシナリオは当社の通常のビジネス シナリオと一致しています。最終的に、データベースは次のようになります。

当然ながら、このデータベースにある在庫と注文数量も正確です。この時点で、データベースに基づく分散ロックの実践的なデモンストレーションは完了です。このタイプのロックを使用する利点と欠点をまとめてみましょう。

  • 利点: シンプルで便利、理解しやすく操作も簡単。
  • デメリット: 同時実行性が大きい場合、データベースへの負荷が大きくなります。
  • 推奨事項: ロック データベースをビジネス データベースから分離します。

最後に

前述のデータベース分散ロックについては、実際のところ、日常の開発ではほとんど使用されていません。より一般的に使用されているのは、Redis と ZK ベースのロックです。本来、この記事では Redis ロックと ZK ロックを一緒に共有したかったのですが、同じ記事に書くと長くなりすぎるため、この記事ではこのタイプの分散ロックについて共有します。ソースコードはLao MaoのGitHubからダウンロードできます。アドレスは https://github.com/maoba/kd-distribute です。

MySQL が実際に分散ロックを実装する方法についてのこの記事はこれで終わりです。MySQL 分散ロックに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQL を使用した分散ロックの実装
  • MySQLにおける分散ロックの考え方をDBの助けを借りて詳しく説明します

<<:  TCPソケットSYNキューとAcceptキューの差異分析

>>:  HTML テーブル マークアップ チュートリアル (30): セルの暗い境界線の色属性 BORDERCOLORDARK

推薦する

Vue を使用して CSS トランジションとアニメーションを実装する方法

目次1. トランジションとアニメーションの違い2. Vueを使用して基本的なCSSトランジションとア...

単一行関数と文字計算日付プロセス制御を説明する MySQL の例

目次1. キャラクター機能1. ケースコントロール機能2. キャラクターコントロール機能2. 数学関...

HTMLは正規表現を使用してテーブルの例をテストします

以下は、HTML で正規表現を使用してテーブルをチェックするサンプル コードです。具体的なコードの内...

Docker コンテナ アプリケーションで避けるべき 10 の悪い習慣

コンテナが企業の IT インフラストラクチャに欠かせない要素となっていることは間違いありません。コン...

JavaScriptの浅いコピーと深いコピーについての簡単な説明

目次1. 直接譲渡2. 浅いコピー3. ディープコピー1. JSONオブジェクトメソッド2. 再帰コ...

CSSメディアクエリのアスペクト比を小さくする方法

CSS メディア クエリには非常に便利なアスペクト比、aspect-ratio があり、幅と高さを直...

Linux Crontab シェル スクリプトを使用して第 2 レベルのスケジュールされたタスクを実装する方法

1. シェルスクリプトcrontab.shを書く #!/bin/bash step=1 #ステップ間...

js でシンプルなタブを実装する

タブ選択カードは、実際の Web ページで非常に頻繁に使用されます。基本的に、すべての Web ペー...

JS を使用して Web ページのウォーターフォール レイアウトを実装する方法

目次序文:ウォーターフォールレイアウトとは何ですか?達成方法: 1. 画像を取得する2. 画像の帯域...

HTML知識ポイントの実践経験のまとめ

1. 表タグはtable、trは行、tdはセル、cellspacingはセル間の距離、cellpad...

React でのポータルとエラー境界処理の実装

目次ポータルエラー境界処理エラー境界を使用しない場合はどうなりますか?注記ポータルスロットとも言えま...

Linux でも利用できる人気の Windows アプリ 10 選

データ分析会社Net Market Shareによると、Linuxデスクトップオペレーティングシステ...

Ubuntu 向け VMware Tools のインストールと構成のチュートリアル

以前、ブロガーは VMware 仮想マシンに Ubuntu システムをインストールしました。まだイン...

MySQLリモート接続権限の詳細な説明

1. MySQLデータベースにログインするmysql -u ルート -pユーザーテーブルを表示する ...

mysql を解決: エラー 1045 (28000): ユーザー 'root'@'localhost' のアクセスが拒否されました (パスワードの使用: NO/YES)

1. 問題時々Mysqlにログインしてパスワードを入力すると、この状況が発生しますmysql -u...