服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#实现Redis的分布式锁

C#实现Redis的分布式锁

2022-11-29 11:31闲僧 C#

我们在开发很多业务场景会使用到锁,例如库存控制,抽奖等。分布式与单机情况下最大的不同在于其不是多线程而是多进程。本文就来介绍一下,感兴趣的可以了解一下

Redis实现分布式锁(悲观锁/乐观锁)

对锁的概念和应用场景在此就不阐述了,网上搜索有很多解释,只是我搜索到的使用C#利用Redis的SetNX命令实现的锁虽然能用,但是都不太适合我需要的场景。

Redis有三个最基本属性来保证分布式锁的有效实现:

  • 安全性: 互斥,在任何时候,只有一个客户端能持有锁。
  • 活跃性A:没有死锁,即使客户端在持有锁的时候崩溃,最后也会有其他客户端能获得锁,超时机制。
  • 活跃性B:故障容忍,只有大多数Redis节点时存活的,客户端仍可以获得锁和释放锁。

基于ServiceStack.Redis写了一个帮助类

Redis连接池

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static PooledRedisClientManager RedisClientPool = CreateManager();
 
private static PooledRedisClientManager CreateManager()
{
    var redisHosts = System.Configuration.ConfigurationManager.AppSettings["redisHosts"];
    if (string.IsNullOrEmpty(redisHosts))
    {
        throw new Exception("AppSetting redisHosts no found");
    }
 
    string[] redisHostarr = redisHosts.Split(new string[] { ",", "," }, StringSplitOptions.RemoveEmptyEntries);
 
    return new PooledRedisClientManager(redisHostarr, redisHostarr, new RedisClientManagerConfig
    {
        MaxWritePoolSize = 1000,
        MaxReadPoolSize = 1000,
        AutoStart = true,
        DefaultDb = 0
    });
}

使用Redis的SetNX命令实现加锁,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/// <summary>
/// 加锁
/// </summary>
/// <param name="key">锁key</param>
/// <param name="selfMark">自己标记</param>
/// <param name="lockExpirySeconds">锁自动过期时间[默认10](s)</param>
/// <param name="waitLockMilliseconds">等待锁时间(ms)</param>
/// <returns></returns>
public static bool Lock(string key, out string selfMark, int lockExpirySeconds = 10, long waitLockMilliseconds = long.MaxValue)
{
    DateTime begin = DateTime.Now;
    selfMark = Guid.NewGuid().ToString("N");//自己标记,释放锁时会用到,自己加的锁除非过期否则只能自己打开
    using (RedisClient redisClient = (RedisClient)RedisClientPool.GetClient())
    {
        string lockKey = "Lock:" + key;
        while (true)
        {
            string script = string.Format("if redis.call('SETNX', KEYS[1], ARGV[1]) == 1 then redis.call('PEXPIRE',KEYS[1],{0}) return 1 else return 0 end", lockExpirySeconds * 1000);
            //循环获取取锁
            if (redisClient.ExecLuaAsInt(script, new[] { lockKey }, new[] { selfMark }) == 1)
            {
                return true;
            }
 
            //不等待锁则返回
            if (waitLockMilliseconds == 0)
            {
                break;
            }
 
            //超过等待时间,则不再等待
            if ((DateTime.Now - begin).TotalMilliseconds >= waitLockMilliseconds)
            {
                break;
            }
            Thread.Sleep(100);
        }
 
        return false;
    }
}

因为ServiceStack.Redis提供的SetNX方法,并没有提供设置过期时间的方法,对于加锁业务又不能分开执行(如果加锁成功设置过期时间失败导致的永久死锁问题),所以就使用脚本实现,解决了异常情况死锁问题.

  • 参数key:锁的key
  • 参数selfMark:在设置锁的时候会产生一个自己的标识,在释放锁的时候会用到,所谓解铃还须系铃人。防止锁被误释放,导致锁无效.
  • 参数lockExpirySeconds:锁的默认过期时间,防止被永久死锁.
  • 参数waitLockMilliseconds:循环获取锁的等待时间.

如果设置为0,为乐观锁机制,获取不到锁,直接返回未获取到锁.
默认值为long最大值,为悲观锁机制,约等于很多很多天,可以理解为一直等待.

释放锁

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <summary>
/// 释放锁
/// </summary>
/// <param name="key">锁key</param>
/// <param name="selfMark">自己标记</param>
public void UnLock(string key, string selfMark)
{
    using (RedisClient redisClient = (RedisClient)RedisClientPool.GetClient())
    {
        string lockKey = "Lock:" + key;
        var script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisClient.ExecLuaAsString(script, new[] { lockKey }, new[] { selfMark });
    }
}

参数key:锁的key
参数selfMark:在设置锁的时候返回的自己标识,用来解锁自己加的锁(此值不能随意传,必须是加锁时返回的值)

调用方式

悲观锁方式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int num = 10;
string lockkey = "xianseng";
 
//悲观锁开启20个人同时拿宝贝
for (int i = 0; i < 20; i++)
{
    Task.Run(() =>
    {
        string selfmark = "";
        try
        {
            if (PublicLockHelper.Lock(lockkey, out selfmark))
            {
                if (num > 0)
                {
                    num--;
                    Console.WriteLine($"我拿到了宝贝:宝贝剩余{num}个\t\t{selfmark}");
                }
                else
                {
                    Console.WriteLine("宝贝已经没有了");
                }
                Thread.Sleep(100);
            }
        }
        finally
        {
            PublicLockHelper.UnLock(lockkey, selfmark);
        }
    });
}

乐观锁方式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int num = 10;
string lockkey = "xianseng";
 
//乐观锁开启10个线程,每个线程拿5次
for (int i = 0; i < 10; i++)
{
    Task.Run(() =>
    {
        for (int j = 0; j < 5; j++)
        {
            string selfmark = "";
            try
            {
                if (PublicLockHelper.Lock(lockkey, out selfmark, 10, 0))
                {
                    if (num > 0)
                    {
                        num--;
                        Console.WriteLine($"我拿到了宝贝:宝贝剩余{num}个\t\t{selfmark}");
                    }
                    else
                    {
                        Console.WriteLine("宝贝已经没有了");
                    }
 
                    Thread.Sleep(1000);
                }
                else
                {
                    Console.WriteLine("没有拿到,不想等了");
                }
            }
            finally
            {
                PublicLockHelper.UnLock(lockkey, selfmark);
            }
        }
    });
}

单机只能用多线模拟使用分布式锁了

此锁已经可以满足大多数场景了,若有不妥,还请多多指出,以免误别人!

(次方案不支持Redis集群,Redis集群不能调用脚本执行)

到此这篇关于C#实现Redis的分布式锁的文章就介绍到这了,更多相关C# Redis分布式锁内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/simoncai/p/11477177.html

延伸 · 阅读

精彩推荐
  • C#C#二维码图片识别代码

    C#二维码图片识别代码

    这篇文章主要为大家详细介绍了C#二维码图片识别代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    志在必得Shaun10112022-02-24
  • C#C#实现简单的登录界面

    C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望...

    C#教程网10132021-11-03
  • C#快速学习c# 枚举

    快速学习c# 枚举

    这篇文章主要介绍了c# 枚举的相关知识,文中讲解非常细致,示例代码帮助大家学习,感兴趣的朋友可以了解下...

    逆心3402022-09-09
  • C#C# 表达式目录树的应用详解

    C# 表达式目录树的应用详解

    下面小编就为大家分享一篇C# 表达式目录树的应用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    Torey_li7542022-02-17
  • C#C#简单输出日历的方法

    C#简单输出日历的方法

    这篇文章主要介绍了C#简单输出日历的方法,涉及C#针对日期与时间的简单操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    dongfengkuayue4942021-10-29
  • C#Visual Studio 2019配置vue项目的图文教程详解

    Visual Studio 2019配置vue项目的图文教程详解

    这篇文章主要介绍了Visual Studio 2019配置vue项目的教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作,具有一定的参考借鉴价值...

    夏子曦8562022-08-29
  • C#C#中常用的运算符总结

    C#中常用的运算符总结

    在本篇文章里小编给大家分享了关于C#中常用的运算符的知识点总结,需要的朋友们跟着学习下。...

    laozhang9062022-07-13
  • C#C# 设计模式系列教程-抽象工厂模式

    C# 设计模式系列教程-抽象工厂模式

    抽象工厂模式为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。...

    Wang Juqiang10872021-11-23