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で大きなファイルを素早くコピーする方法

推薦する

MySQLでスケジュールされたタスクを設定する方法の分析

この記事では、例を使用して、MySQL でスケジュールされたタスクを設定する方法について説明します。...

MySQL 外部キー制約の無効化と有効化コマンド

MySQL 外部キー制約の無効化と有効化: MySQL 外部キー制約が有効になっているかどうかは、グ...

突然外部ネットワークからDockerにアクセスできなくなる問題の解決方法

マスターのメソッドによると、原因は sysctl net.ipv4.ip_forward であること...

太字の <b> と <strong> の違いの分析

私たちウェブマスターは皆、ウェブサイトを最適化する際に記事内のキーワードを太字にすることが最適化に非...

JavaScript プロトタイプオブジェクトの this ポイント問題の詳細な説明

目次1. これは2. この点を修正する1. call() メソッド2. apply() メソッド要約...

MySQLデータクエリが多すぎるとOOMが発生するかどうかについての簡単な議論

目次サーバー層でのフルテーブルスキャンの影響InnoDB におけるフルテーブルスキャンの影響Inno...

MySQLのSeconds_Behind_Masterの詳細な説明

目次マスターの後ろの秒数オリジナルの実装最終マスタータイムスタンプマスターとのクロック差他の実行時間...

データベースの冗長フィールドを合理的に使用する方法

privot は、多対多の関係の中間テーブルです。 PT5 フレームワークは自動的に privot ...

Linux で大きなファイルの指定された内容を見つける方法

大きなことも小さなことも考えて、方向転換しましょう。 Linux では非常に大きなファイルに遭遇する...

一時ファイルを作成できないために MySQL が起動できない問題を解決する方法

問題の説明最近、仕事中に問題が発生しました。MySQL が起動に失敗しました。エラー ログは次のとお...

CSS3 のフィルタプロパティの使用に関する詳細な説明

最近、イントラネットポータルを修正していたときに、フィルターを使用する必要がある箇所に遭遇しました。...

CSS を使用して fullpage.js のフルスクリーン スクロール効果を実装するサンプル コード

最近 CSS を勉強していたとき、 2 つの CSS プロパティだけを使用して全画面スクロール効果を...

フロントエンド JavaScript におけるリフレクションとプロキシ

目次1. 反射とは何ですか? 2. JavaScriptで反映する2.1 Reflect.get(タ...

Ubuntu 基本チュートリアル: apt-get コマンド

序文apt-get コマンドは、Ubuntu システムのパッケージ管理ツールです。パッケージのインス...

Windows システムに MySQL を素早くインストールして展開する方法 (グリーンの無料インストール バージョン)

まずは緑色の無料インストール版のMySQLをダウンロードします。任意のフォルダに入れて構いません。今...