使用 Redis 在 .NET Core 中创建分布式锁

发布:2024-09-14 11:58 阅读:37 点赞:0

在这篇文章中,我们将讨论如何在 .NET Core 中使用 Redis 创建分布式锁。分布式系统中的多个进程可能会共享资源,这会导致资源竞争的问题。分布式锁可以帮助我们解决这个问题。

一. 为什么需要分布式锁?

普通的锁机制通常用于解决单一进程中的共享资源问题。例如:

public void SomeMethod()
{
    // 执行某些操作...
    lock (obj)
    {
        // 执行加锁的操作...
    }
    // 执行其他操作...
}

这种锁是进程内部的锁,只能解决单个进程中的资源竞争问题。对于分布式系统,我们需要分布式锁来处理跨多个进程的资源竞争。

二. 使用 Redis 创建分布式锁

Redis 是一个单线程的数据库,能够执行原子操作,这使它非常适合用于创建分布式锁。接下来,我们将演示如何在 .NET Core 中使用 Redis 创建一个简单的分布式锁。

1. 设置 Redis 连接

首先,我们需要设置 Redis 连接。使用 StackExchange.Redis 客户端库来连接 Redis。

/// <summary>
/// 懒加载的 Redis 连接。
/// </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>
/// 获取 Redis 连接。
/// </summary>
/// <value>返回 Redis 连接。</value>
public static ConnectionMultiplexer Connection => lazyConnection.Value;

2. 获取锁

为了请求对共享资源的锁,我们使用以下命令:

SET resource_name unique_value NX PX duration
  • resource_name 是所有应用实例共享的资源名称。
  • unique_value 是每个应用实例的唯一值,用于释放锁。
  • duration 是锁的过期时间(以毫秒为单位)。

以下是获取锁的 C# 代码实现:

/// <summary>
/// 获取锁。
/// </summary>
/// <returns><c>true</c> 如果锁已获取,<c>false</c> 否则。</returns>
/// <param name="key">锁的键。</param>
/// <param name="value">锁的值。</param>
/// <param name="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($"获取锁失败...{ex.Message}");
        flag = true;
    }

    return flag;
}

3. 测试获取锁

以下是测试获取锁的代码示例:

static void Main(string[] args)
{
    string lockKey = "lock:eat";
    TimeSpan expiration = TimeSpan.FromSeconds(5);

    // 5 个人尝试获取锁并进行操作
    Parallel.For(05, x =>
    {
        string person = $"person:{x}";
        bool isLocked = AcquireLock(lockKey, person, expiration);

        if (isLocked)
        {
            Console.WriteLine($"{person} 开始吃东西(获得锁)于 {DateTimeOffset.Now.ToUnixTimeMilliseconds()}。");
        }
        else
        {
            Console.WriteLine($"{person} 无法吃东西,因为没有获取到锁。");
        }
    });

    Console.WriteLine("结束");
    Console.Read();
}

4. 释放锁

释放锁时,我们需要匹配唯一值来确保正确释放锁。以下是释放锁的代码实现:

/// <summary>
/// 释放锁。
/// </summary>
/// <returns><c>true</c> 如果锁已释放,<c>false</c> 否则。</returns>
/// <param name="key">锁的键。</param>
/// <param name="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($"释放锁失败...{ex.Message}");
        return false;
    }
}

5. 处理锁未释放的情况

当一个进程获取了锁但没有释放时,其他进程应该继续尝试获取锁。以下是处理这种情况的示例代码:

Parallel.For(05, 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} 开始吃东西(获得锁)于 {DateTimeOffset.Now.ToUnixTimeMilliseconds()}。");
        if (new Random().NextDouble() < 0.6)
        {
            Console.WriteLine($"{person} 释放锁 {ReleaseLock(lockKey, person)} {DateTimeOffset.Now.ToUnixTimeMilliseconds()}");
        }
        else
        {
            Console.WriteLine($"{person} 不释放锁....");
        }
    }
    else
    {
        Console.WriteLine($"{person} 开始吃东西(未获得锁)于 {DateTimeOffset.Now.ToUnixTimeMilliseconds()}。");
    }
});

三. 总结

这篇文章介绍了如何在 .NET Core 中使用 Redis 创建分布式锁。这是一个基本的实现,您可以根据自己的业务需求进行改进。