分散ロックは、大規模なアプリケーションで最も広く採用されている機能の 1 つです。Redis に基づいて、さまざまなメソッドを使用して分散ロックを実装できます。このトピックでは、分散ロックを実装するための一般的なメソッドと、Tair (Enterprise Edition) と分散ロックを使用して分散ロックを実装するためのベストプラクティスについて説明します。これらのベストプラクティスは、Alibaba Group が Tair (Enterprise Edition) を使用して蓄積した経験に基づいて開発されています。
背景情報
分散ロックとその使用シナリオ
アプリケーション開発中に、同じプロセス内の複数のスレッドによって特定の リソースに同時にアクセスされる場合、ミューテックス(相互排他ロックとも呼ばれます)と読み取り/書き込みロックを使用できます。同じホスト上の複数のプロセスによって特定の リソースに同時にアクセスされる場合、セマフォ、パイプライン、共有メモリなどのプロセス間同期プリミティブを使用できます。ただし、複数のホストによって特定の リソースに同時にアクセスされる場合は、分散ロックを使用する必要があります。分散ロックは、グローバルに存在する相互排他ロックです。分散システム内の リソースに分散ロックを適用して、リソース競合によって発生する可能性のある論理的な障害を防ぐことができます。

分散ロックの特徴
相互排他
いかなる時点でも、ロックを保持できるクライアントは 1 つだけです。
デッドロックなし
分散ロックは、リースベースのロックメカニズムを使用します。クライアントがロックを取得した後に例外が発生した場合、一定時間後にロックは自動的に解放されます。これにより、リソースのデッドロックが防止されます。
一貫性
ApsaraDB for Redis のスイッチオーバーは、外部または内部のエラーによってトリガーされる場合があります。外部エラーにはハードウェア障害やネットワーク例外が含まれ、内部エラーには低速クエリやシステムの欠陥が含まれます。スイッチオーバーがトリガーされると、レプリカノードが新しいマスターノードに昇格され、高可用性 (HA) が確保されます。このシナリオでは、ビジネスで相互排他に対する要件が高い場合、スイッチオーバー後もロックは同じままである必要があります。
オープンソース Redis に基づく分散ロックの実装
このセクションで説明されている方法は、Redis Open-Source Edition(Community Edition) にも適用されます。
ロックの取得
Redis では、SET コマンドを実行するだけでロックを取得できます。次のセクションでは、コマンドの例を示し、コマンドのパラメーターまたはオプションについて説明します。
SET resource_1 random_value NX EX 5表 1. パラメーターまたはオプション
パラメーター/オプション
説明
resource_1
分散ロックのキー。キーが存在する場合、対応する リソースはロックされ、他の クライアントはアクセスできません。
random_value
ランダムな文字列。値は、クライアント全体で一意である必要があります。
EX
キーの有効期間。単位:秒。PX オプションを使用して、ミリ秒単位の有効期間を設定することもできます。
NX
設定するキーが Redis にすでに存在する場合、設定操作はキャンセルされます。
サンプルコードでは、resource_1 キーの有効期間は 5 秒に設定されています。クライアントがキーを解放しない場合、5 秒後にキーの有効期限が切れ、システムによってロックが再利用されます。その後、他の クライアントは リソースをロックしてアクセスできます。
ロックの解放
ほとんどの場合、DEL コマンドを実行してロックを解放できます。ただし、これにより、次の問題が発生する可能性があります。

t1 時点では、アプリケーション 1 の分散ロックのキーは resource_1 で、resource_1 キーの有効期間は 3 秒に設定されています。
アプリケーション 1 は、長い応答時間などの特定の理由により、3 秒以上ブロックされたままになります。resource_1 キーの有効期限が切れ、t2 時点で分散ロックが自動的に解放されます。
t3 時点で、アプリケーション 2 は分散ロックを取得します。
アプリケーション 1 はブロック状態から再開し、t4 時点で
DEL resource_1コマンドを実行して、アプリケーション 2 が保持している分散ロックを解放します。
この例は、ロックを設定したクライアントのみがロックを解放する必要があることを示しています。したがって、クライアントは GET コマンドを実行して、ロックがクライアント自身によって設定されたかどうかを確認します。その後、クライアントは DEL コマンドを実行してロックを解放できます。ほとんどの場合、クライアントは Redis で次の Lua スクリプトを使用して、クライアントが設定したロックを解放します。
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end更新
クライアントがロックのリース時間内に必要な 操作を完了できない場合、クライアントはロックを更新する必要があります。ロックを設定した クライアントのみがロックを更新できます。Redis では、クライアントは次の Lua スクリプトを使用してロックを更新できます。
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("expire",KEYS[1], ARGV[2]) else return 0 end
Tair に基づく分散ロックの実装
インスタンスが Tair DRAM ベース インスタンスまたは永続メモリ インスタンスの場合、Lua スクリプトを使用せずに文字列拡張コマンドを実行して分散ロックを実装できます。
ロックの取得
Tair でロックを取得する方法は、オープンソース Redis で使用される方法と同じで、SET コマンドを実行することです。コマンド例:
SET resource_1 random_value NX EX 5ロックの解放
Tair(Enterprise Edition) の CAD コマンドを使用して、ロックをエレガントかつ効率的に解放できます。コマンド例:
/* if (GET(resource_1) == my_random_value) DEL(resource_1) */ CAD resource_1 my_random_value更新
CAS コマンドを実行して、ロックを更新できます。コマンド例:
CAS resource_1 my_random_value my_random_value EX 10説明CAS コマンドは、新しい値が元の値と同じであるかどうかを確認しません。
Jedis ベースのサンプルコード
CAS および CAD コマンドの定義
enum TairCommand implements ProtocolCommand { CAD("CAD"), CAS("CAS"); private final byte[] raw; TairCommand(String alt) { raw = SafeEncoder.encode(alt); } @Override public byte[] getRaw() { return raw; } }ロックの取得
public boolean acquireDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) { SetParams setParams = new SetParams(); setParams.nx().ex(expireTime); String result = jedis.set(resourceKey,randomValue,setParams); return "OK".equals(result); }ロックの解放
public boolean releaseDistributedLock(Jedis jedis,String resourceKey, String randomValue) { jedis.getClient().sendCommand(TairCommand.CAD,resourceKey,randomValue); Long ret = jedis.getClient().getIntegerReply(); return 1 == ret; }更新
public boolean renewDistributedLock(Jedis jedis,String resourceKey, String randomValue, int expireTime) { jedis.getClient().sendCommand(TairCommand.CAS,resourceKey,randomValue,randomValue,"EX",String.valueOf(expireTime)); Long ret = jedis.getClient().getIntegerReply(); return 1 == ret; }
ロックの一貫性を確保する方法
マスターノードとレプリカノード間のレプリケーションは非同期です。データの変更がマスターノードに書き込まれた後にマスターノードに障害が発生し、HA スイッチオーバーがトリガーされた場合、バッファー内のデータの変更は新しいマスターノードにレプリケートされない可能性があります。これにより、データの不整合が発生します。新しいマスターノードは元のレプリカノードであることに注意してください。失われたデータが分散ロックに関連している場合、ロックメカニズムに障害が発生し、 サービス例外が発生します。このセクションでは、ロックの一貫性を確保するために使用できる 3 つの方法について説明します。
Redlock アルゴリズムの使用
Redlock アルゴリズムは、ロックの整合性を確保するためにオープンソース Redis プロジェクトの創設者によって提案されました。Redlock アルゴリズムは、すべて確率の計算に関するものです。単一のマスターレプリカ Redis インスタンスは、HA スイッチオーバー中に
k%の確率でロックを失う可能性があります。Redlock アルゴリズムを使用して分散ロックを実装する場合、次の数式に基づいて、N 個の独立した Redis インスタンスが同時にロックを失う確率を計算できます: ロックを失う確率 =(k%)^N。Redis の安定性が高いため、ロックが失われることはほとんどなく、サービスの要件を簡単に満たすことができます。説明Redlock アルゴリズムを実装する場合、N 個の Redis インスタンスのすべてのロックが同時に有効になることを確認する必要はありません。ほとんどの場合、
M(1<M=<N)個の Redis ノードのロックが同時に有効になることを確認すれば、Redlock アルゴリズムはビジネス要件を満たすことができます。Redlock アルゴリズムには、次の問題があります。
クライアントがロックの取得または解放に時間がかかります。
クラスターインスタンスまたは標準マスターレプリカインスタンスでは、Redlock アルゴリズムを使用できません。
Redlock アルゴリズムは大量のリソースを消費します。Redlock アルゴリズムを使用するには、複数の独立した ApsaraDB for Redis インスタンスまたは自己管理の Redis インスタンスを作成する必要があります。
WAIT コマンドの使用
Redis の WAIT コマンドは、以前のすべての書き込みコマンドがマスターノードから指定された数のレプリカノードに同期されるまで、現在のクライアントをブロックします。WAIT コマンドでは、ミリ秒単位で測定されるタイムアウト期間を指定できます。WAIT コマンドは、Tair (Redis OSS-compatible) で分散ロックの整合性を確保するために使用されます。サンプルコマンド:
SET resource_1 random_value NX EX 5 WAIT 1 5000WAIT コマンドを実行すると、クライアントがロックを取得した後、クライアントは 2 つのシナリオでのみ他の操作を続行します。1 つのシナリオは、データがレプリカノードに同期されることです。もう 1 つのシナリオは、タイムアウト期間に達することです。この例では、タイムアウト期間は 5,000 ミリ秒です。WAIT コマンドの出力が 1 の場合、データはマスターノードとレプリカノード間で同期されます。この場合、データ整合性が確保されます。WAIT コマンドは、Redlock アルゴリズムよりもはるかにコスト効率が高いです。
注:
WAIT コマンドは、WAIT コマンドを送信した クライアントのみをブロックし、他の クライアントには影響しません。
WAIT コマンドが有効な値を返した場合、ロックはマスターノードからレプリカノードに同期されます。ただし、コマンドが成功の応答を返す前に HA スイッチオーバーがトリガーされた場合、データが失われる可能性があります。この場合、WAIT コマンドの出力は、同期障害の可能性のみを示しており、データの整合性は保証されません。WAIT コマンドがエラーを返した後、ロックを再度取得するか、データを確認できます。
ロックを解放するために WAIT コマンドを実行する必要はありません。これは、分散ロックが相互排他であるためです。論理障害は、一定時間後にロックを解放しても発生しません。
Tair の使用
CAS および CAD コマンドは、分散ロックの開発と管理のコストを削減し、ロック パフォーマンスを向上させるのに役立ちます。
Tair DRAM ベース インスタンスは、オープンソース Redis の 3 倍のパフォーマンスを提供します。DRAM ベース インスタンスを使用して高並列分散ロックを実装する場合でも、 サービスの継続性が確保されます。また、DRAM ベース インスタンスのマスターノードとレプリカノード間で準同期レプリケーションを構成することもできます。このモードでは、データがマスターノードに書き込まれ、レプリカノードに同期された場合にのみ、成功の応答が クライアントに返されます。これにより、HA スイッチオーバー後のデータ損失を防ぎます。データ同期中にレプリカノード障害またはネットワーク例外が発生した場合、準同期レプリケーションモードは非同期レプリケーションモードにダウングレードされます。