C#中基于StackExchange.Redis的分布式锁

直入主题

以IDatabase扩展方法的形式实现分布式锁方法,将代码拷到任意静态类里即可使用。

/// <summary>
/// 使用Redis分布式锁执行某些操作
/// </summary>
/// <param name="lockName">锁名</param>
/// <param name="act">操作</param>
/// <param name="expiry">锁过期时间,若超出时间自动解锁 单位:sec</param>
/// <param name="retry">获取锁的重复次数</param>
/// <param name="tryDelay">获取锁的重试间隔  单位:ms</param>
public static void LockAction(this IDatabase db, string lockName, Action act, int expiry = 10, int retry = 3, int tryDelay = 200)
{
    if (act.Method.IsDefined(typeof(AsyncStateMachineAttribute), false))
    {
        throw new ArgumentException("使用异步Action请调用LockActionAsync");
    }

    TimeSpan exp = TimeSpan.FromSeconds(expiry);
    string token = Guid.NewGuid().ToString("N");
    try
    {
        bool ok = false;
        // 延迟重试
        for (int test = 0; test < retry; test++)
        {
            if (db.LockTake(lockName, token, exp))
            {
                ok = true;
                break;
            }
            else
            {
                Task.Delay(tryDelay).Wait();
            }
        }
        if (!ok)
        {
            throw new InvalidOperationException($"获取锁[{lockName}]失败");
        }
        act();
    }
    finally
    {
        db.LockRelease(lockName, token);
    }
}

/// <summary>
/// 使用Redis分布式锁执行某些异步操作
/// </summary>
/// <param name="lockName">锁名</param>
/// <param name="act">操作</param>
/// <param name="expiry">锁过期时间,若超出时间自动解锁 单位:sec</param>
/// <param name="retry">获取锁的重复次数</param>
/// <param name="tryDelay">获取锁的重试间隔  单位:ms</param>
public static async Task LockActionAsync(this IDatabase db, string lockName, Func<Task> act, int expiry = 10, int retry = 3, int tryDelay = 200)
{
    TimeSpan exp = TimeSpan.FromSeconds(expiry);
    string token = Guid.NewGuid().ToString("N");
    try
    {
        bool ok = false;
        // 延迟重试
        for (int test = 0; test < retry; test++)
        {
            if (await db.LockTakeAsync(lockName, token, exp))
            {
                ok = true;
                break;
            }
            else
            {
                await Task.Delay(tryDelay);
            }
        }
        if (!ok)
        {
            throw new InvalidOperationException($"获取锁[{lockName}]失败");
        }
        await act();
    }
    finally
    {
        await db.LockReleaseAsync(lockName, token);
    }
}

使用方法

private async Task RedisLockTestAsync()
{
    string connStr = "Redis连接字符串";
    var conn = await ConnectionMultiplexer.ConnectAsync(connStr);
    IDatabase db = conn.GetDatabase();
    // 带异步操作的用 LockActionAsync
    await db.LockActionAsync("MyLockName", async () =>
    {
        // 执行异步方法...
        await Task.Delay(1000);
        Console.WriteLine("Done");
    });
}

private void RedisLockTest()
{
    string connStr = "Redis连接字符串";
    var conn = ConnectionMultiplexer.Connect(connStr);
    IDatabase db = conn.GetDatabase();
    // 同步操作的用 LockAction
    db.LockAction("MyLockName", () =>
    {
        // 同步方法...
        Task.Delay(1000).Wait();
        Console.WriteLine("Done");
    });
}

一些思考

分布式锁使用的场景

最近在Redis的使用中遇到了需要在List中查找某个值,若不存在则向List中新增,即需要维护一个没有重复项的List的需求。虽然Redis本身的所有操作都是互斥的,但是Redis本身并没有提供无重复项的List类型,也没有相关指令能对List进行去重,故需要拉整个List下来进行对比再写入,在这里就会存在一个并发的问题,若在分布式部署且高并发的情景里就有可能使List出现重复项。所以需要对这个操作加上分布式锁,类似的场景还有很多这里不一一列举。

Redis里分布式锁的实现

所谓分布式锁实际上就是一个Redis里的一个键值对,锁名为Key,占用者(Token)为value。当A需要占用时则将value修改为A的Token并返回True,当B再想占用时,发现这个Key(锁名)的value已经有值且不是自己的Token,此时就返回False。这部分的逻辑StackExchange.Redis都已经帮我们做了。我们只需要调用它的LockTake,LockRelease方法即可。

因为获取锁的时候锁可能已经被占用,所以需要有一个重试机制,有重试就应该有重试间隔,这就是为什么LockAction方法里会有retry和tryDelay参数。

为什么异步和同步的方法要分开

刚开始是想做成像 Task.Run(()=>{}) 那样既可以传异步Action也可以传同步Action,但是后面发现.net更新之后异步Action没办法像以前那样用BeginInvoke方法等待执行完成,本人技术有限,实在找不到办法兼容同步和异步Action,这个是客观的原因。

另外,主观上我觉得兼容同步和异步就是一件不值得提倡的事情,若该扩展方法设计为同步方法,那在同步方法里执行异步操作再强行wait它是一件很瓜皮的事,而且还可能会出现莫名其妙的bug;若该扩展方法设计为异步方法但是如果传进来是同步Action的话,那么一个异步操作都没有,虽然没问题但很别扭,所以这里最终还是把同步操作和异步操作分开写成了两个方法。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: StackExchange.Redis 是一个.NET平台下的开源Redis客户端库,它提供了一种高性能、稳定和易于使用的方式来与Redis服务器进行通信。它支持异步和同步操作,并提供了一些高级特性,如管道、发布/订阅等。 StackExchange.Redis 具有以下特点: - 快速:通过使用高性能、低级别的Redis通信协议,StackExchange.Redis 可以实现高效的数据访问。 - 可扩展性:StackExchange.Redis 支持分区功能,使它能够在多个Redis节点上分布数据,并且可以在需要时轻松地添加或删除节点。 - 可靠性:StackExchange.Redis 有一个内置的连接池来管理与Redis服务器的连接,从而可以提高性能和可靠性,并避免了频繁的连接/断开开销。 - 易用性:StackExchange.Redis 提供了一个简单的API,使得开发人员能够轻松地与Redis进行交互。 总的来说,如果你在.NET平台上使用RedisStackExchange.Redis 应该是你的首选。 ### 回答2: StackExchange.Redis 是一个基于Redis的 .Net客户端库,由StackExchange团队开发和维护。Redis内存数据库是非常流行的键值对存储,支持复杂数据结构,速度快,有完善的集群方案,在web应用程序、消息系统等应用场景被广泛使用。 StackExchange.Redis提供了方便易用、高性能、可扩展的API,可以直接向Redis发送命令、订阅和发布消息、连接Redis集群等。他进一步扩展了Redis命令,使其更加简单易用,并支持事务、管道和多线程操作。同时,StackExchange.Redis支持Redis的高级特性,如SortedSet和Hash;而且,它还能够在运行时自动检测Redis服务状态,支持Redis Sentinel、Cluster和多节点环境。 此外,StackExchange.Redis还支持应用程序跟踪(application tracing),采用异步API调用,从而提高了性能和可伸缩性。与其他Redis客户端库不同,StackExchange.Redis实现了连接池、序列化和反序列化器等高级功能,使用起来更加方便和优雅,可以减少代码量和复杂性。 总的来说,StackExchange.Redis是一个很不错的Redis客户端库,它提供了强大而与Redis完全兼容的API,打破了语言壁垒,使得开发者可以在自己喜欢的语言环境(例如.Net)使用 Redis 。 许多一流的公司如 StackOverflow和GitHub都使用 StackExchange.Redis,证明了它的可靠性和性能优势。 ### 回答3: StackExchange.RedisRedis数据库的一个适用于 .NET 编程语言的客户端。它提供了一组丰富的功能,用于在 .NET 框架内与Redis数据库进行交互。 StackExchange.Redis旨在为 .NET 开发人员提供最佳的Redis集成体验。StackExchange.Redis提供了许多优点,其最主要的有: 高度优化的读写操作。StackExchange.Redis客户端库具有非常高的效率,用于从Redis服务器读取和写入数据时,可大大减少网络流量和延迟。这使得它成为处理高吞吐量负载的很好的选择。 支持许多复杂数据类型。除了提供基本的字符串和整数类型之外,StackExchange.Redis还支持许多复杂的数据类型,例如哈希表、排序集合和列表数据类型。这些数据类型将极大地帮助开发人员设计和实施高效的Redis应用程序。 易于使用和集成。StackExchange.Redis具有完美的适配性,可以很简单地与 .NET 的 ASP.NET 应用程序、Windows 服务、控制台应用程序等相集成。此外,它具有丰富的文档、教程和API reference,可以让开发者轻松入手。 另外,StackExchange.Redis还包括许多其他特性,如:连接复用、事务支持、发布与订阅、管道等。这使得它成为在 .NET 编程语言使用Redis数据库的非常好的选择。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值