
- 9
C# Redis分布式锁的应用
一、背景
咱们在开发不少业务场景会使用到锁,例如库存控制,抽奖等。通常咱们会使用内存锁的方式来保证线性的执行。但如今大多站点都会使用分布式部署,那多台服务器间的就必须使用同一个目标来判断锁。分布式与单机状况下最大的不一样在于其不是多线程而是多进程。redis
二、演变
[分布式站点使用内存锁方式以下图]
假设有3个用户同时购买一件商品,商品库存只剩下1,若是3个用户同时购买,负载均衡把3个用户分别指向站点一、二、3,那结果将会是3个用户都购买成功。下面咱们使用分布式锁解决这个问题。数据库
[分布式站点使用分布式锁以下图]
单台服务器因为能够共享堆内存,所以能够简单的采起内存做为标记存储位置。而多服务器之间都不在同一台物理机上,所以须要将标记存储在一个全部进程都能看到的地方。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"); }
【若是文章对你有所帮助,请在评论区留言点赞,以资鼓励。】分布式