zoukankan      html  css  js  c++  java
  • C# 用Redis实现的分布式锁

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

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

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

    Redis连接池

            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命令实现加锁,

            /// <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;
                }
            }
    

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

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

    释放锁

            /// <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:在设置锁的时候返回的自己标识,用来解锁自己加的锁(此值不能随意传,必须是加锁时返回的值

    调用方式

    • 悲观锁方式
                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}个		{selfmark}");
                                }
                                else
                                {
                                    Console.WriteLine("宝贝已经没有了");
                                }
                                Thread.Sleep(100);
                            }
                        }
                        finally
                        {
                            PublicLockHelper.UnLock(lockkey, selfmark);
                        }
                    });
                }
    
    • 乐观锁方式
                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}个		{selfmark}");
                                    }
                                    else
                                    {
                                        Console.WriteLine("宝贝已经没有了");
                                    }
    
                                    Thread.Sleep(1000);
                                }
                                else
                                {
                                    Console.WriteLine("没有拿到,不想等了");
                                }
                            }
                            finally
                            {
                                PublicLockHelper.UnLock(lockkey, selfmark);
                            }
                        }
                    });
                }
    

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

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

  • 相关阅读:
    vue-cli3.0配置开发环境,测试环境,线上环境
    jQuery使用CDN加速
    浏览器中JavaScript脚本代码的load、ready等方法的加载顺序
    使用 JavaScript 拦截和跟踪浏览器中的 HTTP 请求
    Node和NPM在Windows环境下绿色安装及配置
    Nodejs 中将html转换成pdf文件
    数学励志公式:每天进步一点点
    网页调用打印机打印时纸张A4的设置
    用JS或jQuery访问页面内的iframe,兼容IE/FF
    HTML to DOM
  • 原文地址:https://www.cnblogs.com/simoncai/p/11477177.html
Copyright © 2011-2022 走看看