.NET Core での Redis 分散ロックの実装
はじめに
この記事では、.NET Core の Redis を使用して分散ロックを作成する方法について説明します。
分散システムを構築する場合、共有リソースを一緒に処理する複数のプロセスに直面することになります。共有リソースを一度に利用できるのは 1 つだけであるため、予期せぬ問題が発生する可能性があります。
この問題を解決するには、分散ロックを使用できます。
分散ロックを使用する理由
いつものように、ロックを使用してこの問題を処理します。
以下に、ロックの使用法を示すサンプル コードをいくつか示します。
public void SomeMethod()
{
// Do something...
lock (obj)
{
// Do ....
}
// Do something...
} ただし、このタイプのロックは問題をうまく解決するのに役立ちません。これは、共有リソースを使用して 1 つのプロセスのみを解決できるインプロセス ロックです。
これは、分散ロックが必要な主な理由でもあります。
ここでは Redis を使用して単純な分散ロックを作成します。
そして、なぜこの仕事をするのに Redis を使用するのでしょうか? Redis はシングルスレッドの性質を持ち、アトミックな操作を実行できるためです。
ロックを作成するには?
.NET Core コンソール アプリケーションを作成して紹介します。
次のステップに進む前に、Redis サーバーを起動する必要があります。
StackExchange.Redis は .NET 上で最も人気のある Reid クライアントであり、次のジョブを実行するためにこれを使用することは間違いありません。
最初に Redis との接続を作成します。
/// <summary>
/// The lazy connection.
/// </summary>
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
ConfigurationOptions configuration = new ConfigurationOptions
{
AbortOnConnectFail = false,
ConnectTimeout = 5000,
};
configuration.EndPoints.Add("localhost", 6379);
return ConnectionMultiplexer.Connect(configuration.ToString());
});
/// <summary>
/// Gets the connection.
/// </summary>
/// <value>The connection.</value>
public static ConnectionMultiplexer Connection => lazyConnection.Value; 共有リソースのロックをリクエストするには、次の操作を行います。
SET resource_name unique_value NX PX duration resource_name は、アプリケーションのすべてのインスタンスが共有する値です。
unique_value は、アプリケーションの各インスタンスに対して一意である必要があります。この固有の値の目的は、ロックを解除する (ロック解除) ことです。
最後に、期間 (ミリ秒単位) も指定します。この期間を過ぎると、Redis によってロックが自動的に削除されます。
C# コードでの実装は次のとおりです。
/// <summary>
/// Acquires the lock.
/// </summary>
/// <returns><c>true</c>, if lock was acquired, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param>
/// <param name="value">Value.</param>
/// <param name="expiration">Expiration.</param>
static bool AcquireLock(string key, string value, TimeSpan expiration)
{
bool flag = false;
try
{
flag = Connection.GetDatabase().StringSet(key, value, expiration, When.NotExists);
}
catch (Exception ex)
{
Console.WriteLine($"Acquire lock fail...{ex.Message}");
flag = true;
}
return flag;
} 以下は、ロックの取得をテストするコードです。
static void Main(string[] args)
{
string lockKey = "lock:eat";
TimeSpan expiration = TimeSpan.FromSeconds(5);
// 5 person eat something...
Parallel.For(0, 5, x =>
{
string person = $"person:{x}";
bool isLocked = AcquireLock(lockKey, person, expiration);
if (isLocked)
{
Console.WriteLine($"{person} begin eat food (with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");
}
else
{
Console.WriteLine($"{person} cannot eat food due to not getting the lock.");
}
});
Console.WriteLine("end");
Console.Read();
} コードを実行すると、次の結果が得られる場合があります。

ロックを取得できるのは 1 人だけです。他の人が待っています。
ロックは Redis によって自動的に削除されますが、共有リソースも有効に活用されません。
なぜなら、プロセスがジョブを終了したら、延々と待つのではなく、他のプロセスがそのリソースを使用できるようにする必要があるからです。
したがって、ロックも解除する必要があります。
ロックを解除するにはどうすればよいですか?
ロックを解除するには、Redis 内の項目を削除しました。
ロックを作成する際に何を考慮するかというと、リソースの一意の値と一致する必要があります。これにより、右のロックをより安全に解除できるようになります。
一致すると、ロックが削除されます。これは、ロック解除が成功したことを意味します。それ以外の場合、ロック解除は失敗しました。
get コマンドと del コマンドを一度に実行する必要があるため、これを行うには Lua スクリプトを使用します。
/// <summary>
/// Releases the lock.
/// </summary>
/// <returns><c>true</c>, if lock was released, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param>
/// <param name="value">Value.</param>
static bool ReleaseLock(string key, string value)
{
string lua_script = @"
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
redis.call('DEL', KEYS[1])
return true
else
return false
end
";
try
{
var res = Connection.GetDatabase().ScriptEvaluate(lua_script,
new RedisKey[] { key },
new RedisValue[] { value });
return (bool)res;
}
catch (Exception ex)
{
Console.WriteLine($"ReleaseLock lock fail...{ex.Message}");
return false;
}
} プロセスが終了したら、このメソッドを呼び出す必要があります。
プロセスがロックを取得しても、何らかの理由でロックを解放しない場合、他のプロセスはロックが解放されるまで待つことができません。現時点では、他のプロセスを続行する必要があります。
このシーンに対処するサンプルを次に示します。
Parallel.For(0, 5, x =>
{
string person = $"person:{x}";
var val = 0;
bool isLocked = AcquireLock(lockKey, person, expiration);
while (!isLocked && val <= 5000)
{
val += 250;
System.Threading.Thread.Sleep(250);
isLocked = AcquireLock(lockKey, person, expiration);
}
if (isLocked)
{
Console.WriteLine($"{person} begin eat food (with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");
if (new Random().NextDouble() < 0.6)
{
Console.WriteLine($"{person} release lock {ReleaseLock(lockKey, person)} {DateTimeOffset.Now.ToUnixTimeMilliseconds()}");
}
else
{
Console.WriteLine($"{person} do not release lock ....");
}
}
else
{
Console.WriteLine($"{person} begin eat food (without lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");
}
}); サンプルを実行すると、次の結果が得られる場合があります。
ご覧のとおり、人 3 と 4 はロックなしで先に進みます。
これは、私の GitHub ページにあるソース コードです。
- RedisLockDemo
概要
この記事では、.NET Core の Redis を使用して分散ロックを作成する方法を紹介しました。これは基本バージョンであり、ビジネスに基づいて改善できます。
これがお役に立てば幸いです。
-
Redis GEOHASH –地理空間値の複数のメンバーのジオハッシュ文字列を取得する方法
このチュートリアルでは、キーに格納されている地理空間値の1つ以上の要素のジオハッシュ文字列を取得する方法について学習します。このために、Redis GEOHASHを使用します コマンド。 GEOHASHコマンド このコマンドは、キーに格納されている地理空間値の1つ以上の指定された要素の有効なジオハッシュ文字列を返すために使用されます。地理空間値は、GEOADDコマンドを使用して入力された並べ替えられた設定値で表されます。 Redisは、ジオハッシュ手法のバリエーションを使用して地理空間要素の位置(経度、緯度)を表します。この手法では、緯度と経度のビットをインターリーブして、一意の52ビ
-
Redis Sentinel と Redis Cluster:比較とどちらを選択するか
このビデオを表示するには、JavaScript を有効にし、HTML5 ビデオをサポートする Web ブラウザへのアップグレードを検討してください。 3月20日 1.9k 500 3 ニディ・シャルマ おすすめの動画 Redis 分散ロックの説明:安全なパターンと一般的な落とし穴 バイバブ・クマール 3月16日 2.9k 500 Redis とインメモリ キャッシュ:開発者が犯すスケーリングの間違い バイバブ・クマール 3月11日 2.5k 500 Redis マルチリージョン アーキテクチャの説明 (レイテンシ