MySQL を使用した分散ロックの実装

MySQL を使用した分散ロックの実装

導入

分散システムでは、分散ロックは最も基本的なツール クラスです。たとえば、支払い機能を備えた 2 つのマイクロサービスがデプロイされている場合、ユーザーは注文に対して 2 つの支払い操作を開始し、これらの 2 つのリクエストが 2 つのサービスに送信される可能性があります。したがって、重複送信を防ぐために分散ロックを使用する必要があります。ロックを取得したサービスは支払い操作を通常どおり実行し、ロックを取得していないサービスは重複操作を要求します。

当社では、多数の基本的なツール クラスをカプセル化しています。分散ロックを使用する場合は、次の 3 つのことだけを行う必要があります。

1. データベースにgloballocktableテーブルを作成する
2. 対応するjarパッケージを導入する
3. このコンポーネントを使用するには、コードに@Autowired GlobalLockComponent globalLockComponentを記述します。

この記事を読んだ後、springboot-starter を使用して同じ機能を実現することもできます。しかし、私たちはこの方法では実装しませんでした。どのように実装したかを分析する別の記事を書く予定です。

この記事ではまずMySQLディストリビューションの実装を分析します

テーブルを作成する

CREATE TABLE `globallocktable` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `lockKey` varchar(60) NOT NULL COMMENT 'ロック名',
 `createTime` datetime NOT NULL COMMENT '作成時刻',
 主キー (`id`)、
 ユニークキー `lockKey` (`lockKey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='グローバルロック';

他の人が使用できるコンポーネント

@成分
パブリッククラス GlobalLockComponent {
 @リソース
 グローバルロックテーブルDAO グローバルロックDAO;
 /**
  * ロックを取得しようとします。成功した場合は true、失敗した場合は false
  */
 パブリックブール型tryLock(文字列キー) {
  GlobalLockUtil.tryLock(this.globalLockDAO, キー) を返します。
 }
 /**
  * 別のプログラムがロックを占有していて、timeoutMs (ミリ秒) を超えると、ロックは強制的に解除されます。 * つまり、最初にキーに従ってレコードを削除し、次にレコードを追加します */
 パブリックブール型 tryLockWithClear(文字列キー、Long timeoutMs) {
  GlobalLockUtil.tryLockWithClear(this.globalLockDAO、キー、timeoutMs) を返します。
 }
 /**
  * ロックを解除し、キーに従ってレコードを削除します */
 パブリック void releasesLock(文字列キー) {
  GlobalLockUtil.releasLock(this.globalLockDAO、キー);
 }
}

ロックオブジェクトは次のように定義されます

パブリッククラス GlobalLockTable {

 プライベート整数ID;
 プライベート文字列 lockKey;
 プライベート日付createTime;
 // get メソッドと set メソッドを省略}

GlobalLockTableDAOは以下のように定義されます。public interface GlobalLockTableDAO {
 int deleteByPrimaryKey(整数ID);
 int deleteByLockKey(文字列 lockKey);
 GlobalLockTable selectByLockKey(文字列キー);
 int insertSelectiveWithTest(GlobalLockTable レコード);
}

特定のロックおよびロック解除ロジック

パブリッククラス GlobalLockUtil {
 プライベート静的 Logger ロガー = LoggerFactory.getLogger(GlobalLockUtil.class);
 プライベート静的GlobalLockTable tryLockInternal(GlobalLockTableDAO lockDAO, 文字列キー) {
  GlobalLockTable を挿入 = 新しい GlobalLockTable();
  新しい日付を挿入します。
  insert.setLockKey(キー);
  // 注1
  int count = lockDAO.insertSelectiveWithTest(挿入);
  カウント == 0 の場合
   GlobalLockTable 準備完了 = lockDAO.selectByLockKey(キー);
   logger.warn("キーをロックできません: {}, {}, {}", insert.getLockKey(), ready.getCreateTime(),
     準備完了。
   返却準備完了。
  }
  logger.info("はい、キーによってロックを取得しました: {}", insert.getId(), insert.getLockKey());
  null を返します。
 }
 /** ロックを解除して再ロックするまでのタイムアウト**/
 パブリック静的ブール型 tryLockWithClear(GlobalLockTableDAO lockDAO、文字列キー、Long timeoutMs) {
  GlobalLockTable ロック = tryLockInternal(lockDAO, キー);
  lock == null の場合は true を返します。
  System.currentTimeMillis() - lock.getCreateTime().getTime() <= timeoutMs の場合 {
   logger.warn("申し訳ありませんが、キーを取得できません。: {}, {}, {}", key, lock.getId(), lock.getCreateTime());
   false を返します。
  }
  logger.warn("キーは既にタイムアウトしています: {}, {}, はクリアされます", key, timeoutMs);
  // 注2
  int count = lockDAO.deleteByPrimaryKey(lock.getId());
  カウント == 0 の場合
   logger.warn("申し訳ありませんが、キーは既に他のキーによってプリエンプトされています: {}、{}", lock.getId()、lock.getLockKey());
   false を返します。
  }
  ロック = tryLockInternal(lockDAO、キー);
  ロック!= null ? false : true を返します。
 }
 /** ロック **/
 パブリック静的ブール型tryLock(GlobalLockTableDAO lockDAO, 文字列キー) {
  tryLockInternal(lockDAO, key) == null を返します。true の場合、false になります。
 }
 /** ロック解除 **/
 パブリック静的void releasesLock(GlobalLockTableDAO lockDAO、文字列キー) {
  lockDAO.deleteByLockKey(キー);
 }
}

このツールクラスには特に興味深い点が2つあります。まず、ポイント2(上のコードでマークされている)を見てみましょう。

1. ロックが長時間解除されないことを避けるために、Redis で実装する場合は、ロックタイムアウトを設定することができ、タイムアウト後にロックは自動的に解除されます (Redis で分散ロックを実装する方法については後で書きます)。MySQL で実装する場合は、最初に削除してから追加することができます。削除するときに、名前ではなく ID を使用して削除されることがわかります。なぜ?まず考えてみましょう

名前で削除すると、他の誰かがロックを削除し、タイムアウト期間前に名前でロックを追加したが、あなたが名前で削除した可能性があるからです。 ID で削除した場合、返される ID が 0 であれば、他の誰かが再度ロックしたため、再度取得する必要があることを意味します。

2. GlobalLockTable オブジェクト dao レイヤーの他のメソッドは説明不要です。このメソッドを見てみましょう。つまり、コードの注1
ロックを試みるたびに、最初に選択するのではなく、直接 insertSelectiveWithTest を実行することで、クエリ時間が節約され、効率が向上することがわかります。

insertSelectiveWithTest の機能は、lockKey が存在する場合に挿入操作を実行せず、0 を返すことです。 lockKeyが存在しない場合は、挿入操作を実行し、1を返します。

<挿入 id="insertSelectiveWithTest" useGeneratedKeys="true" keyProperty="id" パラメータタイプ="com.javashitang.middleware.lock.mysql.pojo.GlobalLockTable">
 `globallocktable` (`id`, に挿入
 `lockKey`、`createTime` )
  #{id,jdbcType=INTEGER}、#{lockKey,jdbcType=VARCHAR}、#{createTime,jdbcType=TIMESTAMP} を選択します
  存在しない二重から
  (lockKey = #{lockKey,jdbcType=VARCHAR} の globallocktable から 1 つを選択)
</挿入>

使用

使用したいときにはビジネスロジックを記述するだけでよいので、非常に便利です。

if (!globalLockComponent.tryLock(name)) {
 // ロックが取得されていない場合は戻ります。
}
試す {
 // ここでビジネスロジックを記述します} catch (Exception e) {
ついに
 globalLockComponent.releasLock(名前)

要約する

以上が、MySQL を使用して分散ロックを実装するための編集者による紹介です。皆様のお役に立てれば幸いです。

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

<<:  JavaScript の知識: コンストラクタも関数である

>>:  Linuxで大きなファイルを素早くコピーする方法

推薦する

TS 数値区切り文字とより厳密なクラス属性チェックの詳細な説明

目次概要演算子の改良と正確なinstanceofよりスマートなオブジェクトリテラル推論固有のシンボル...

Linux システムのスワップ領域の紹介

スワップ スペースは、オペレーティング システムに関係なく、今日のコンピューティングの一般的な側面で...

HTML 初心者のためのベストプラクティス 15 選

HTML 初心者向けのベストプラクティスを 30 個紹介します。 1. タグを閉じたままにする過去に...

6つの珍しいHTMLタグ

まず: <abbr> または <acronym>これら 2 つの記号は同じ意...

vue $http の get および post リクエストのクロスドメイン問題を解決する

Vue $http get および post リクエストのクロスドメイン問題まずconfig/ind...

MySQL のメモリ使用量と CPU 使用率が高い場合のテストと解決策

変更後: innodb_buffer_pool_size=576M ->256M InnoDB...

docker コマンド例外「権限が拒否されました」の解決方法

Linuxシステムでは、dockerを新しくインストールし、次のようなコマンドを入力します。dock...

js を使用して数字推測ゲームを実装する

先週、先生が私に数字当てゲームをするちょっとした宿題を出しました。とても面白いと思ったので、適当に書...

CSS3 で translate と transition を使用する方法

translate と transition は非常に強力で、習得するのは不可能だといつも感じていま...

WeChatアプレットでグローバル変数を監視する方法

最近、仕事で問題に遭遇しました。グローバル変数 red_heart があります。これは多くの場所で使...

ウェブページのフッターで注意すべきことのまとめ

たくさんのリンクおそらく、このようなサイトをたくさん見たことがあるでしょう。ページの下部に 50 個...

MySQLトランザクションの基本的な学習と経験の共有

トランザクションは、論理的な操作のグループです。この操作グループを構成する各ユニットは、成功するか失...

継続的インテグレーションテストにおけるDocker Swarmの適用の詳細な説明

背景アジャイル モデルは広く使用されており、テストは特に重要です。新しいバージョンは頻繁にリリースす...

MYSQL 左結合の最適化 (10 秒から 20 ミリ秒)

目次【機能背景】 [生のSQL] 【独自SQL解析】 【分析手順】 [最適化されたSQL] 【最適化...

MySQL の中国語ソートの詳細と例

MySQL の漢字ソートの詳細な説明デフォルトでは、MySQL は日付、時刻、および英語の文字列の並...