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:

  1. Implemented the IDistributedLock interface using Redis to manage lock states.
  2. 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.

Related Posts