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

推薦する

CentOS7 で Jenkins+Maven+Git 継続的インテグレーション環境を構築する方法

この記事では、Spring boot + Maven プロジェクトのデプロイメントを例に、Code ...

ElementUI のネストされたテーブルに基づいて複数選択を実装するためのサンプル コード

序文:私は友人のプロジェクトのバグを修正するのを手伝ったのでこれを書きました。この関数を書くのは初め...

sed コマンドを使用してファイルの特定の行を効率的に削除する方法

序文通常、ファイル内の特定の行を削除したい場合は、まずファイルを開き、削除する内容を見つけて、これら...

画像カルーセルを実装するためのネイティブJS 小さな広告プラグインを実装するためのJS

最近、ネイティブ JS を使用して、さらにいくつかの小さな機能を実装したいと思っています。現在、ブロ...

VS2019 が mysql8.0 データベースに接続する方法 (画像とテキスト付き)

1. まず、VS2019とMySQLデータベースを準備します。どちらも公式サイトからダウンロードで...

MySQLのテーブル構造を変更する際に知っておきたいメタデータロックの詳しい解説

序文MySQL を扱ったことがある人なら、テーブル メタデータ ロックの待機についてよく知っているは...

Vueプロジェクトがグラフィック検証コードを実装

この記事の例では、グラフィック検証コードを実装するためのVueプロジェクトの具体的なコードを参考まで...

docker runとstartの違い

docker における実行と開始の違いDocker run はミラーイメージを指定します。そしてdo...

MySQL 半同期レプリケーションの原理構成と導入の詳細な説明

環境の紹介: Ubuntu Server 16.04.2+MySQL 5.7.17 コミュニティ サ...

MySQL ステートメントロックの実装の分析

概要: 2 つの MySQL SQL ステートメント ロックの分析次のSQL文にどのようなロックが追...

TomcatをダウンロードしてLinuxにインストールする詳細な手順

Linux に触れたばかりの方には、この内容が役に立つかもしれません。Linux にしばらく触れてい...

JavaScript スクリプトが実行されるタイミングの詳細な説明

JavaScript スクリプトは HTML 内のどこにでも埋め込むことができますが、いつ呼び出され...

Centos7 での python3 のインストールとアンインストールに関するチュートリアル

1. Python 3をインストールする1. 依存パッケージをインストールしますyum instal...

Ubuntu 18.04にMySQL 5.7をインストールする

この記事は MySQL 公式サイトを参考にしてまとめたものであり、遭遇したいくつかの問題も記録されて...