分散ロックの原理と3つの実装方法の詳細な説明

分散ロックの原理と3つの実装方法の詳細な説明

現在、ほぼすべての大規模な Web サイトとアプリケーションは分散方式で展開されています。分散シナリオにおけるデータの一貫性の問題は常に重要なトピックとなっています。分散 CAP 理論によれば、「分散システムは一貫性、可用性、およびパーティション耐性を同時に満たすことはできず、同時に満たすことができるのはそのうちの 2 つだけです」。したがって、多くのシステムでは、設計の初期段階でこれら 3 つの間でトレードオフを行う必要があります。インターネット分野のほとんどのシナリオでは、システムの高可用性と引き換えに、強力な一貫性を犠牲にする必要があります。多くの場合、最終的な時間がユーザーにとって許容可能な範囲内である限り、システムでは「最終的な一貫性」のみを確保する必要があります。

多くのシナリオでは、データの最終的な一貫性を保証するために、分散トランザクションや分散ロックなど、それをサポートする多くの技術的ソリューションが必要です。場合によっては、メソッドが同じスレッドによってのみ同時に実行されるようにする必要があります。スタンドアロン環境では、Java は実際に多くの並行処理関連の API を提供しますが、これらの API は分散シナリオでは無力です。つまり、純粋な Java API では分散ロック機能を提供できません。したがって、現在、分散ロックの実装には複数のソリューションが存在します。

分散ロックの実装には、現在、次のソリューションが一般的に使用されています。

データベースに基づいて分散ロックを実装する キャッシュ (redis、memcached、tair) に基づいて分散ロックを実装する
Zookeeper に基づく分散ロックの実装

これらの実装ソリューションを分析する前に、どのような分散ロックが必要かを考えてみましょう。 (ここではメソッドロックを例に挙げていますが、リソースロックについても同様です)

分散アプリケーション クラスターでは、同じメソッドは 1 台のマシン上の 1 つのスレッドによってのみ同時に実行されることが保証されます。
このロックは再入可能ロックである必要があります(デッドロックを回避するため)
このロックはブロッキング ロックであることが望ましい (ビジネス ニーズに基づいてこのロックを使用するかどうかを検討してください)
可用性の高いロック取得およびロック解放機能。優れたロック取得およびロック解放パフォーマンス。

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

データベーステーブルに基づく

分散ロックを実装するには、ロック テーブルを直接作成し、テーブル内のデータを操作して実装するのが最も簡単な方法です。
メソッドまたはリソースをロックする場合は、テーブルにレコードを追加し、ロックを解除する場合はレコードを削除します。

次のようなデータベース テーブルを作成します。

CREATE TABLE `methodLock` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主キー',
 `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT 'ロックされたメソッド名',
 `desc` varchar(1024) NOT NULL DEFAULT '備考',
 `update_time` タイムスタンプ NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'データ時刻を保存、自動生成',
 主キー (`id`)、
 BTREE を使用したユニーク キー `uidx_method_name` (`method_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ロック方法';

メソッドをロックしたい場合は、次の SQL を実行します。

methodLock(method_name,desc) に値 ('method_name','desc') を挿入します

method_name に一意制約を設定したので、複数のリクエストが同時にデータベースに送信された場合、データベースは 1 つの操作のみが成功することを保証します。その後、正常に操作されたスレッドはメソッドのロックを取得し、メソッド本体の内容を実行できると想定できます。

メソッドが実行されたときにロックを解除したい場合は、次の SQL を実行する必要があります。

method_name = 'method_name' の場合、methodLock から削除します。

上記の単純な実装には、次の問題があります。

1. このロックはデータベースの可用性に大きく依存します。データベースは単一ポイントです。データベースに障害が発生すると、ビジネス システムは使用できなくなります。
2. このロックには有効期限がありません。ロック解除操作が失敗すると、ロック レコードはデータベースに残り、他のスレッドはロックを取得できなくなります。
3. データの挿入操作では、挿入が失敗した場合にエラーが直接報告されるため、このロックは非ブロッキングのみになります。ロックを取得していないスレッドはキューに入りません。再度ロックを取得する場合は、ロック取得操作を再度トリガーする必要があります。
4. このロックは再入不可能であり、ロックを解放する前に同じスレッドが再度ロックを取得することはできません。データは既にデータ内に存在しているからです。

もちろん、上記の問題は他の方法でも解決できます。

データベースは単一のポイントですか? 2 つのデータベースを作成し、双方向でデータを同期します。障害が発生したら、すぐにバックアップ データベースに切り替えます。
有効期限はありませんか?定期的にデータベース内のタイムアウト データをクリーンアップするスケジュールされたタスクを実行するだけです。
非ブロッキング?挿入が成功するまで while ループを実行し、成功を返します。
再入不可能?データベース テーブルにフィールドを追加して、現在ロックを取得しているマシンのホスト情報とスレッド情報を記録します。次にロックを取得するときに、最初にデータベースを照会します。現在のマシンのホスト情報とスレッド情報がデータベースで見つかった場合は、ロックを直接割り当てることができます。

データベース排他ロックに基づく

データ テーブル内のレコードの追加と削除に加えて、データに組み込まれたロックを使用して分散ロックを実装することもできます。

先ほど作成したデータベース テーブルも使用します。分散ロックは、データベースの排他ロックを通じて実装できます。 MySQL InnoDB エンジンに基づいて、次の方法を使用してロック操作を実装できます。

パブリックブールロック(){
	接続.setAutoCommit(false)
	  while(true){
		試す{
			result = select * from methodLock where method_name=xxx for update;
			if(結果==null){
				true を返します。
			}
		}
		catch(例外 e){
		}
		スリープ(1000);
	}
	false を返します。
}

クエリ ステートメントの後に for update を追加すると、データベースはクエリ プロセス中にデータベース テーブルに排他ロックを追加します (ここで言及しておきたいのは、InnoDB エンジンがロックする場合、インデックスを検索するときにのみ行レベル ロックを使用し、それ以外の場合はテーブル レベル ロックを使用することです。ここでは行レベル ロックを使用するため、method_name にインデックスを追加する必要があります。このインデックスは一意のインデックスとして作成する必要があることに注意してください。そうしないと、複数のオーバーロードされたメソッドに同時にアクセスできないという問題が発生します。オーバーロードされたメソッドの場合は、パラメーター タイプも追加することをお勧めします)。レコードに排他ロックが追加されると、他のスレッドはレコードの行に排他ロックを追加できなくなります。

排他ロックを取得したスレッドは、分散ロックを取得できると想定できます。ロックを取得した後、メソッドのビジネス ロジックを実行できます。メソッドを実行した後、次のメソッドでロックを解除できます。

パブリックvoidロック解除(){
  接続をコミットします。
}

connection.commit() 操作を通じてロックを解除します。

この方法は、前述のロックを解除できない、ロックがブロックされるといった問題を効果的に解決できます。

ブロックロックですか? for update ステートメントは、実行が成功するとすぐに戻り、実行が失敗した場合は成功するまでブロックされたままになります。
ロック後、サービスがクラッシュし、解放できなくなりますか?この方法を使用すると、サービスが停止した後、データベース自体がロックを解除します。

ただし、データベースのシングルポイントおよび再入可能性の問題を直接解決することはできません。

method_name に一意のインデックスを使用し、行レベルのロックを使用するために更新に明示的に使用していますが、ここでは別の問題がある可能性があります。ただし、MySql はクエリを最適化します。条件でインデックス フィールドが使用されている場合でも、データを取得するためにインデックスを使用するかどうかは、さまざまな実行プランのコストを判断することによって MySQL によって決定されます。MySQL は、非常に小さなテーブルなど、完全なテーブル スキャンの方が効率的であると判断した場合、インデックスを使用しません。この場合、InnoDB は行ロックではなくテーブル ロックを使用します。もしそんなことが起こったら悲劇だ。 。 。

もう 1 つの問題は、排他ロックを使用して分散ロックをロックする場合、排他ロックが長時間送信されないと、データベース接続が占有されてしまうことです。類似の接続が多すぎると、データベース接続プールが破裂する可能性があります。

要約する

データベースを使用して分散ロックを実装する方法をまとめると、どちらの方法もデータベース内のテーブルに依存します。1 つはテーブル内のレコードの存在に基づいて現在ロックが存在するかどうかを判断する方法であり、もう 1 つはデータベース内の排他ロックを通じて分散ロックを実装する方法です。

データベースにおける分散ロックの利点

データベースに直接依存するので、理解しやすいです。

データベースにおける分散ロックの欠点

さまざまな問題が発生し、問題を解決する過程で、計画全体がますます複雑になります。
データベースを操作するには、ある程度のオーバーヘッドが必要であり、パフォーマンスの問題を考慮する必要があります。
データベースで行レベルのロックを使用することは、特にロック テーブルが大きくない場合には、必ずしも信頼できるとは限りません。

キャッシュに基づく分散ロックの実装

データベースに基づいて分散ロックを実装するソリューションと比較すると、キャッシュに基づくソリューションはパフォーマンスの点で優れています。さらに、多数のキャッシュをクラスターに展開して、単一ポイントの問題を解決することもできます。

当社には、Redis、memcached、Tair など、成熟したキャッシュ製品が数多くあります。

ここでは、Tair を例に、キャッシュを使用して分散ロックを実装するソリューションを分析します。インターネット上には Redis と memcached に関する記事が多数あり、直接使用できる成熟したフレームワークやアルゴリズムもいくつかあります。

Tair に基づく分散ロックの実装は実際には Redis に似ており、主な実装方法は TairManager.put メソッドを使用することです。

パブリックブール型trylock(文字列キー) {
  ResultCode code = ldbTairManager.put(NAMESPACE, key, "これはロックです。", 2, 0);
  (ResultCode.SUCCESS.equals(コード)) の場合
    true を返します。
  それ以外
    false を返します。
}
パブリックブールロック解除(文字列キー) {
  ldbTairManager.invalid(NAMESPACE、キー);
}

上記の実装にはいくつかの問題もあります。

1. このロックには有効期限がありません。ロック解除操作が失敗すると、ロック レコードは Tair に残り、他のスレッドはロックを取得できなくなります。
2. このロックは非ブロッキングのみが可能で、成功か失敗かに関係なく直接戻ります。
3. このロックは再入不可能です。スレッドがロックを取得した後、使用されたキーがすでに Tair に存在するため、ロックを解放する前に再度ロックを取得することはできません。 put 操作は実行できなくなりました。

もちろん、この問題を解決する方法もあります。

有効期限はありませんか? Tair の put メソッドは有効期限の渡しをサポートしており、有効期限が切れるとデータは自動的に削除されます。
非ブロッキング? while は実行を繰り返します。
再入不可能?スレッドはロックを取得した後、現在のホスト情報とスレッド情報を保存し、次回ロックを取得する前に現在のロックの所有者であるかどうかを確認します。

しかし、有効期限はどのくらいに設定すればよいのでしょうか?有効期限が短すぎると、メソッドが実行される前にロックが自動的に解除され、同時実行の問題が発生します。時間を長く設定しすぎると、ロックを取得した他のスレッドがより長い時間待機しなければならない可能性があります。この問題は、データベースを使用して分散ロックを実装する場合にも発生します。

要約する

キャッシュはデータベースの代わりに使用して分散ロックを実装することができ、パフォーマンスを向上させることができます。同時に、単一ポイントの問題を回避するために、多くのキャッシュ サービスがクラスターに展開されます。さらに、多くのキャッシュ サービスでは、Tair の put メソッドや redis の setnx メソッドなど、分散ロックを実装するために使用できるメソッドが提供されています。さらに、これらのキャッシュ サービスは、期限切れのデータの自動削除もサポートしており、タイムアウト時間を直接設定してロックの解除を制御することもできます。

キャッシュを使用して分散ロックを実装する利点

パフォーマンスが良好で実装も簡単です。

キャッシュを使用して分散ロックを実装することの欠点

ロックの有効期限をタイムアウトで制御するのはあまり信頼性が高くありません。

Zookeeper に基づく分散ロックの実装

分散ロックは、Zookeeper の一時的に順序付けられたノードに基づいて実装できます。

一般的な考え方は、各クライアントがメソッドをロックすると、ZooKeeper 上のメソッドに対応する指定されたノードのディレクトリに、一意の瞬間順序付きノードが生成されます。 ロックを取得するかどうかの判定方法は非常に簡単で、順序付けられたノードの中でシリアル番号が最も小さいものを決定するだけです。 ロックが解除されたら、一時ノードを削除するだけです。同時に、サービスのダウンタイムによりロックを解除できないために発生するデッドロックの問題を回避できます。

Zookeeper が上記の問題を解決できるかどうか見てみましょう。

ロックが解除できない? Zookeeper を使用すると、ロックが解除されない問題を効果的に解決できます。ロックを作成すると、クライアントは ZK に一時ノードを作成するからです。クライアントがロックを取得した後に突然ハングアップすると (セッション接続が切断されると)、一時ノードは自動的に削除されます。その後、他のクライアントは再度ロックを取得できます。
非ブロッキングロック? Zookeeper を使用すると、ブロッキング ロックを実装できます。クライアントは、ZK に連続ノードを作成し、そのノードにリスナーをバインドできます。ノードが変更されると、Zookeeper はクライアントに通知します。クライアントは、作成したノードが現在のすべてのノードの中で最小のシーケンス番号を持っているかどうかを確認できます。そうであれば、ロックを取得してビジネス ロジックを実行できます。

再入可能ではないですか? Zookeeper を使用すると、再入不可能の問題も効果的に解決できます。クライアントがノードを作成すると、現在のクライアントのホスト情報とスレッド情報がノードに直接書き込まれます。次にロックを取得するときは、現在の最小ノードのデータと比較するだけです。自分の情報と同じ場合は直接ロックを取得し、異なる場合は一時的にシーケンシャルノードを作成してキューに参加します。

問題は一つだけ? Zookeeper を使用すると、単一ポイントの問題を効果的に解決できます。ZK はクラスターに展開されます。クラスター内のマシンの半分以上が稼働している限り、外部にサービスを提供できます。

再入可能ロック サービスをカプセル化する Zookeeper サードパーティ ライブラリ Curator クライアントを直接使用できます。

パブリック ブール型 tryLock(long timeout, TimeUnit unit) は InterruptedException をスローします {
  試す {
    interProcessMutex.acquire(timeout, unit) を返します。
  } キャッチ (例外 e) {
    e.printStackTrace();
  }
  true を返します。
}
パブリックブールロック解除() {
  試す {
    プロセス間ミューテックスを解放します。
  } キャッチ (Throwable e) {
    ログエラー(e.getMessage(), e);
  ついに
    executorService.schedule(新しい Cleaner(クライアント、パス)、delayTimeForClean、TimeUnit.MILLISECONDS);
  }
  true を返します。
}

Curator が提供する InterProcessMutex は分散ロックの実装です。 acquire メソッドはロックを取得するために使用され、release メソッドはロックを解放するために使用されます。

ZK を使用して実装された分散ロックは、この記事の冒頭で述べた分散ロックに対する期待をすべて完全に満たしているようです。しかし、そうではありません。Zookeeper によって実装された分散ロックには、実際には、パフォーマンスがキャッシュ サービスほど高くない可能性があるという欠点があります。ロックの作成と解放のプロセスごとに、ロック機能を実装するために瞬間的なノードを動的に作成および破棄する必要があるためです。 ZK では、ノードの作成と削除はリーダー サーバー経由でのみ実行でき、そのデータをすべてのフォロワー マシンで共有することはできません。

実際、Zookeeper を使用すると同時実行の問題が発生する可能性もありますが、これは一般的ではありません。次の状況を考えてみましょう。ネットワークジッターにより、クライアントと ZK クラスター間のセッション接続が切断されます。すると、ZK はクライアントがハングアップしたと判断し、一時ノードを削除します。この時点で、他のクライアントは分散ロックを取得できます。同時実行の問題が発生する可能性があります。 zk には再試行メカニズムがあるため、この問題は一般的ではありません。zk クラスターがクライアントのハートビートを検出できなくなると、再試行します。Curator クライアントは複数の再試行戦略をサポートしています。一時ノードは、複数回の再試行後に失敗した場合にのみ削除されます。 (したがって、適切な再試行戦略を選択し、ロックの粒度と同時実行性のバランスを見つけることも重要です。)

要約する

Zookeeperを使用して分散ロックを実装する利点

単一点問題、非再入問題、非ブロッキング問題、ロックを解除できない問題を効果的に解決します。実装は比較的簡単です。

Zookeeperを使用して分散ロックを実装することの欠点

パフォーマンスは、キャッシュを使用して分散ロックを実装する場合ほど良くありません。 ZK の原則をある程度理解している必要があります。

3つのソリューションの比較

上記の方法はどれも完璧ではありません。 CAP と同様に、複雑さ、信頼性、パフォーマンスなどの要件を同時に満たすことは不可能です。したがって、さまざまなアプリケーション シナリオに応じて最も適したものを選択するのが最善の方法です。

理解の難しさの観点から(低い順から高い順)
データベース > キャッシュ > Zookeeper

実装の複雑さの観点から(低から高)
Zookeeper >= キャッシュ > データベース

パフォーマンスの観点から(高から低)
キャッシュ > Zookeeper >= データベース

信頼性の観点から(高から低)
Zookeeper > キャッシュ > データベース

要約する

以上が、分散ロックの原理と 3 つの実装方法の詳細な説明に関するこの記事の内容のすべてです。興味のある方は、引き続き、Java のミューテックス ロック セマフォとマルチスレッド待機メカニズムの詳細な説明、Apache Zookeeper の使用例の詳細、いくつかの重要な MySQL 変数、およびこのサイトのその他の関連トピックを参照してください。皆様のお役に立てれば幸いです。ご質問がございましたら、いつでもメッセージを残してください。編集者がすぐに返信し、より良い読書体験とサポートを提供します。このサイトをサポートしてくださっている皆様、ありがとうございます!

以下もご興味があるかもしれません:
  • SpringCloud 分散システムで分散ロックを実装する方法の詳細な説明
  • 分散ロックの3つの実装方法と比較
  • Java分散ロックの概念と実装の詳細な説明
  • Java分散アーキテクチャで分散ロックを実装する方法について簡単に説明します。
  • 分散インタビュー分散ロックの実装とアプリケーションシナリオ

<<:  1つの記事でJSONPの原理と応用を理解する

>>:  React プロジェクトにおける TypeScript の使用の概要

推薦する

MySQL の暗黙的な型変換によって発生するインデックス障害の解決策

目次質問再生暗黙的な変換要約する参照する質問仕事中、1 つの SQL クエリ ステートメントのみを実...

Dockerでイメージをプルするための手順を完了する

1. Docker pullはイメージをプルします$ docker pull {IMAGE_NAME...

Linux の GRUB ブート プログラムの暗号化の概要

目次1. GRUB暗号化とは何か2. grub暗号化手順3. grub暗号化のロック属性1. GRU...

Vue v-for ループを書く 7 つの方法

目次1. v-forループでは常にキーを使用する2. 特定のスコープ内でv-forループを使用する3...

Mac ノードの削除と再インストールのケーススタディ

Macノードの削除と再インストール消去 ノード -v sudo npm アンインストール npm -...

Amoeba を使用して MySQL データベースの読み取り/書き込み分離を実装する方法の詳細な説明

MySQL には読み取りと書き込みを分離するアーキテクチャが多数あります。Baidu のそれらのほと...

CentOS7にNginxをインストールして自動起動を設定する方法

1.公式サイトからインストールパッケージをダウンロードするhttp://nginx.org/en/d...

Element-ui の組み込み 2 つのリモート検索 (ファジークエリ) の使用方法の説明

問題の説明フロントエンドリモート検索やファジークエリと呼ばれる種類のクエリがあります。 Ele.me...

MySQLデータベースのトランザクション分離レベルの詳細な説明

データベーストランザクション分離レベルデータベース トランザクションには、低から高まで 4 つの分離...

jsは水平および垂直スライダーを実現します

最近、練習プロジェクトをしていたときにスライダーを使う必要があったので、調べてみました。まず、水平ス...

CocosCreatorの共通知識ポイントを整理する

目次1. シーンの読み込み2. ノードを見つける1. ノード検索2. その他のノード操作3. 再生ア...

MySQL エラー番号 1129 の解決方法

SQLyog が MySQL に接続する際にエラー番号 1129 が発生します: mysql エラー...

MySQL 権限昇格のさまざまな形態の概要

目次1. Webshel​​lを書く出力ファイルにシェルを書き込むログファイル書き込みシェル2. U...

Vue codemirrorはオンラインコードコンパイラの効果を実現します

序文Web 上でオンライン コード コンパイルの効果を実現したい場合は、 CodeMirrorを再度...