C# Redis分布式锁的应用 - 叶子栈 - SegmentFault 思否

原文: C# Redis分布式锁的应用 - 叶子栈 - SegmentFault 思否

C# Redis分布式锁的应用

 阅读约 7 分钟

一、背景

咱们在开发不少业务场景会使用到锁,例如库存控制,抽奖等。通常咱们会使用内存锁的方式来保证线性的执行。但如今大多站点都会使用分布式部署,那多台服务器间的就必须使用同一个目标来判断锁。分布式与单机状况下最大的不一样在于其不是多线程而是多进程。redis

二、演变

[分布式站点使用内存锁方式以下图]
clipboard.png
假设有3个用户同时购买一件商品,商品库存只剩下1,若是3个用户同时购买,负载均衡把3个用户分别指向站点一、二、3,那结果将会是3个用户都购买成功。下面咱们使用分布式锁解决这个问题。数据库

[分布式站点使用分布式锁以下图]
clipboard.png
单台服务器因为能够共享堆内存,所以能够简单的采起内存做为标记存储位置。而多服务器之间都不在同一台物理机上,所以须要将标记存储在一个全部进程都能看到的地方。c#

三、实现

选型

想一想咱们实现分布式锁要知足哪些条件?
一、在分布式系统环境下,一个锁在同一时间只能被一个服务器获取;(这是全部分布式锁的基础)
二、高性能的获取锁和释放锁;(锁用完了,要及时释放,以供别人继续使用)
三、具有锁失效机制,防止死锁;(防止由于某些意外,锁没有获得释放,那别人将永远没法使用)
四、具有非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。(知足等待锁的同时,也要知足非阻塞锁特性,便于多样性的业务场景使用)segmentfault

分布式锁有不少的实现方式:数据库排他锁、Zookeeper、缓存(redis、memcached)等,本文选用Redis实现。缘由以下:
一、Redis有很高的性能;
二、Redis命令对此支持较好,实现起来比较方便;
Redis官网上对C#的使用推荐有ServiceStack.Redis和StackExchange.Redis
因为StackExchange.Redis虽然有提供LockTake方法,很方便的使用锁,可是只支持.Net4.5以上。公司不少站点都是3.5和4.0的,因此选用ServiceStack.Redis,本身封装一下。缓存

Redis使用命令介绍

一、SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不作,返回0。
二、expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
三、delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。服务器

C#代码

一、锁方法微信

/// <summary>
        /// 分布式锁
        /// </summary>
        /// <param name="key">锁key</param>
        /// <param name="lockExpirySeconds">锁自动超时时间(秒)</param>
        /// <param name="waitLockMs">等待锁时间(秒)</param>
        /// <returns></returns>
        public static bool Lock(string key, int lockExpirySeconds = 10, double waitLockSeconds = 0) {
            //间隔等待50毫秒
            int waitIntervalMs = 50;

            RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0);

            string lockKey = "LockForSetNX:" + key;

            DateTime begin = DateTime.Now;
            using (client)
            {
                while (true)
                {
                    //循环获取取锁
                    if (client.SetNX(lockKey, new byte[] { 1 }) == 1)
                    {
                        //设置锁的过时时间
                        client.Expire(lockKey, lockExpirySeconds);
                        return true;
                    }

                    //不等待锁则返回
                    if (waitLockSeconds == 0) break;

                    //超过等待时间,则再也不等待
                    if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break;

                    Thread.Sleep(waitIntervalMs);
                }
                return false;
            }
        }

二、释放锁多线程

/// <summary>
        /// 删除锁 执行完代码之后调用释放锁
        /// </summary>
        /// <param name="key"></param>
        public static void DelLock(string key) {
            RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0);
            string lockKey = "LockForSetNX:" + key;
            using (client)
            {
                client.Del(lockKey);
            }
        }

三、业务应用代码负载均衡

try
            {
                //取锁,设置key10秒后失效,最大等待锁5秒
                if (RedisHelper.Lock("LockKey", 10, 5))
                {
                    //取到锁,执行具体业务
                    //例如商品购买,库存-1
                }
            }
            finally
            {
                //释放锁
                DelLock("LockKey");
            }

【若是文章对你有所帮助,请在评论区留言点赞,以资鼓励。】分布式

阅读 2.8k 更新于 2月12日
1 收藏 1 赞扬
相关文章
相关标签/搜索