ভূমিকা
এই আর্টিকেলে, আমরা .NET Core-এ রেডিস দিয়ে একটি ডিস্ট্রিবিউটেড লক কীভাবে তৈরি করব সেটি আলোচনা করব।
যখন আমরা ডিস্ট্রিবিউটেড সিস্টেম তৈরি করি, আমরা একাধিক প্রসেস ফেস করি যেগুলো একটি শেয়ার্ড রিসোর্স একসাথে হ্যান্ডেল করে। শুধু একটি প্রসেসই এক সময়ে শেয়ার্ড রিসোর্সটি ব্যবহার করতে পারে, এই ফ্যাক্টের কারণে অপ্রত্যাশিত সমস্যা সৃষ্টি হয়!
এই সমস্যাটি সমাধান করতে আমরা একটি ডিস্ট্রিবিউটেড লক ব্যবহার করতে পারি।
কেন ডিস্ট্রিবিউটেড লক?
সাধারণত, আমরা এই সমস্যা হ্যান্ডেল করতে একটি লক ব্যবহার করি।
নিচে লক ব্যবহারের কিছু নমুনা কোড দেওয়া হলো।
public void SomeMethod() { // Do something... lock (obj) { // Do .... } // Do something... }
তবে, এই ধরনের লক আমাদের সমস্যাটি ভালোভাবে সমাধান করতে পারে না! এটি একটি ইন-প্রসেস লক যা শুধু একটি প্রসেসের সাথে শেয়ার্ড রিসোর্স সমাধান করতে পারে।
এই হলো মূল কারণ আমাদের ডিস্ট্রিবিউটেড লক দরকার!
এখানে আমি রেডিস ব্যবহার করে একটি সহজ ডিস্ট্রিবিউটেড লক তৈরি করব।
আমি কেন রেডিস ব্যবহার করছি এই কাজের জন্য? কারণ রেডিসের সিঙ্গেল-থ্রেডেড নেচার এবং অ্যাটমিক অপারেশন পারফর্ম করার ক্ষমতা।
লক কীভাবে তৈরি করবেন?
এটি দেখানোর জন্য আমি একটি .NET Core কনসোল অ্যাপ্লিকেশন তৈরি করব।
পরবর্তী ধাপে যাওয়ার আগে, আমাদের রেডিস সার্ভার চালু করে রাখতে হবে!
StackExchange.Redis .NET-এ সবচেয়ে জনপ্রিয় রেডিস ক্লায়েন্ট, এবং নিঃসন্দেহে আমরা এটিই ব্যবহার করব।
প্রথমে রেডিসের সাথে কানেকশন তৈরি করি।
///
/// The lazy connection.
///
private static Lazy lazyConnection = new Lazy(() => {
ConfigurationOptions configuration = new ConfigurationOptions {
AbortOnConnectFail = false,
ConnectTimeout = 5000,
};
configuration.EndPoints.Add("localhost", 6379);
return ConnectionMultiplexer.Connect(configuration.ToString());
});
///
/// Gets the connection.
///
/// The connection.
public static ConnectionMultiplexer Connection => lazyConnection.Value;
একটি শেয়ার্ড রিসোর্সে লক অনুরোধ করতে, আমরা নিম্নলিখিত কাজ করি।
SET resource_name unique_value NX PX duration
resource_name হলো একটি মান যা আপনার অ্যাপ্লিকেশনের সব ইনস্ট্যান্স শেয়ার করবে।
unique_value হলো কিছু যেটি আপনার অ্যাপ্লিকেশনের প্রতিটি ইনস্ট্যান্সের জন্য ইউনিক হতে হবে। এবং এই ইউনিক ভ্যালুর উদ্দেশ্য হলো লক রিলিজ করা (আনলক)।
শেষে, আমরা একটি ডুরেশন (মিলিসেকেন্ডে) দিই, যার পর রেডিস স্বয়ংক্রিয়ভাবে লকটি রিমুভ করবে।
এখানে C# কোডে ইমপ্লিমেন্টেশন।
///
/// Acquires the lock.
///
/// true , if lock was acquired, false otherwise.
/// Key.
/// Value.
/// Expiration.
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();
}
কোড রান করার পর, আমরা নিচের রেজাল্ট পেতে পারি।

শুধু একজনই লক পেতে পারে! অন্যদের অপেক্ষা করতে হচ্ছে।
যদিও লকটি রেডিস দ্বারা স্বয়ংক্রিয়ভাবে রিমুভ হবে, এটি শেয়ার্ড রিসোর্সের ভালো ব্যবহার নিশ্চিত করে না!
কারণ যখন একটি প্রসেস তার কাজ শেষ করে, সেটি অন্যদের রিসোর্স ব্যবহার করতে দিয়ে দিতে হবে, না যে অনন্তকাল অপেক্ষা করবে!
তাই আমাদের লক রিলিজ করার পদ্ধতিও দরকার।
লক কীভাবে রিলিজ করবেন?
লক রিলিজ করতে, আমরা রেডিসে আইটেমটি শুধু রিমুভ করি!
লক তৈরি করার সময় যা আমরা নিলাম, সেটি মিলিয়ে রিসোর্সের ইউনিক ভ্যালু যাচাই করতে হবে। এটি সঠিক লক রিলিজ করতে নিশ্চিত করবে।
মিলান করার সময়, আমরা লকটি ডিলিট করব, যার মানে আনলক সফল হয়েছে। অন্যথায়, আনলক অসফল।
আমাদের GET এবং DEL কমান্ড একসাথে এক্সিকিউট করতে হবে, তাই আমরা একটি লুয়া স্ক্রিপ্ট ব্যবহার করব!
///
/// Releases the lock.
///
/// true , if lock was released, false otherwise.
/// Key.
/// Value.
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()}.");
}
});
নমুনা রান করার পর, আপনি নিচের রেজাল্ট পেতে পারেন।
যে तरह দেখছেন, পারসন ৩ এবং ৪ লক ছাড়া এগিয়ে যাচ্ছে।
সোর্স কোড আপনি আমার গিটহাব পেজে পাবেন।
- RedisLockDemo
সারসংক্ষেপ
এই আর্টিকেলে .NET Core-এ রেডিস দিয়ে ডিস্ট্রিবিউটেড লক তৈরি করার পদ্ধতি 소개 করা হয়েছে। এবং এটি একটি বেসিক ভার্সন, আপনি আপনার বিজনেসের ওপর ভিত্তি করে উন্নতি করতে পারেন।
আশা করি এটি আপনাকে সাহায্য করবে।