Implementing Distributed Locking with Redis and IDistributedLock
Distributed locking is a critical feature for ensuring resource safety in distributed applications. Here’s how you can implement a distributed lock using the IDistributedLock
interface and Redis as the backing store, leveraging the StackExchange.Redis
library.
Step 1: Implement the IDistributedLock
Interface
Let’s start by creating the RedisDistributedLock
class to implement the IDistributedLock
interface:
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
public class RedisDistributedLock : IDistributedLock
{
private readonly IDatabase _database;
private readonly string _lockKey;
private string _lockToken;
public RedisDistributedLock(IDatabase database, string lockKey)
{
_database = database;
_lockKey = lockKey;
_lockToken = Guid.NewGuid().ToString();
}
public async Task<bool> AcquireLockAsync(string resource, TimeSpan leaseTime)
{
_lockToken = Guid.NewGuid().ToString();
return await _database.StringSetAsync(resource, _lockToken, leaseTime, When.NotExists);
}
public async Task RenewLockAsync(TimeSpan leaseTime)
{
if (!await _database.StringGetAsync(_lockKey).ConfigureAwait(false).Equals(_lockToken))
{
throw new InvalidOperationException("Cannot renew a lock that is not held.");
}
await _database.KeyExpireAsync(_lockKey, leaseTime);
}
public async Task ReleaseLockAsync()
{
var token = await _database.StringGetAsync(_lockKey);
if (token == _lockToken)
{
await _database.KeyDeleteAsync(_lockKey);
}
}
public void Dispose()
{
ReleaseLockAsync().GetAwaiter().GetResult();
}
}
Step 2: Create a Sample Console Application
Next, build a console application to demonstrate the functionality of the RedisDistributedLock
:
using StackExchange.Redis;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var redis = ConnectionMultiplexer.Connect("localhost");
var database = redis.GetDatabase();
var resource = "sample-resource";
var leaseTime = TimeSpan.FromSeconds(30);
using (var distributedLock = new RedisDistributedLock(database, resource))
{
if (await distributedLock.AcquireLockAsync(resource, leaseTime))
{
Console.WriteLine("Lock acquired.");
// Simulate some work
await Task.Delay(TimeSpan.FromSeconds(10));
// Renew the lock
await distributedLock.RenewLockAsync(leaseTime);
Console.WriteLine("Lock renewed.");
// Simulate some more work
await Task.Delay(TimeSpan.FromSeconds(10));
// Release the lock
await distributedLock.ReleaseLockAsync();
Console.WriteLine("Lock released.");
}
else
{
Console.WriteLine("Failed to acquire lock.");
}
}
}
}
Summary
Here’s a quick recap of what we did:
- Implemented the
IDistributedLock
interface using Redis to manage lock states. - Built a sample console application to demonstrate acquiring, renewing, and releasing distributed locks.
This implementation ensures that only one instance of the application can hold the lock on a given resource at a time, making it a robust solution for distributed systems. Redis’s speed and reliability make it an excellent choice for managing distributed locks.