目录Redis实现分布式锁(悲观锁/乐观锁)Redis连接池使用Redis的SetNX命令实现加锁,调用方式Redis实现分布式锁(悲观锁/乐观锁) 对锁的概念和应用场景在此就不阐
对锁的概念和应用场景在此就不阐述了,网上搜索有很多解释,只是我搜索到的使用C#利用Redis的SetNX命令实现的锁虽然能用,但是都不太适合我需要的场景。
Redis有三个最基本属性来保证分布式锁的有效实现:
基于ServiceStack.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
});
}
/// <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方法,并没有提供设置过期时间的方法,对于加锁业务又不能分开执行(如果加锁成功设置过期时间失败导致的永久死锁问题),所以就使用脚本实现,解决了异常情况死锁问题.
如果设置为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}个\t\t{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}个\t\t{selfmark}");
}
else
{
Console.WriteLine("宝贝已经没有了");
}
Thread.Sleep(1000);
}
else
{
Console.WriteLine("没有拿到,不想等了");
}
}
finally
{
PublicLockHelper.UnLock(lockkey, selfmark);
}
}
});
}
单机只能用多线模拟使用分布式锁了
此锁已经可以满足大多数场景了,若有不妥,还请多多指出,以免误别人!
(次方案不支持Redis集群,Redis集群不能调用脚本执行)
到此这篇关于C#实现Redis的分布式锁的文章就介绍到这了,更多相关C# Redis分布式锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: C#实现Redis的分布式锁
本文链接: https://lsjlt.com/news/131817.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0