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

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

  • 参数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}个\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实现的分布式锁的更多相关文章

  1. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  2. 一个Redis实现的分布式锁

    import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...

  3. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  4. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  5. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  6. 在redis上实现分布式锁

    /** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...

  7. 如何用redis正确实现分布式锁?

    先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...

  8. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  9. 一般实现分布式锁都有哪些方式?使用redis如何设计分布式锁?使用zk来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?

    #(1)redis分布式锁 官方叫做RedLock算法,是redis官方支持的分布式锁算法. 这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点创建了 ...

  10. Redis如何实现分布式锁

    今天我们来聊一聊分布式锁的那些事. 相信大家对锁已经不陌生了,我们在多线程环境中,如果需要对同一个资源进行操作,为了避免数据不一致,我们需要在操作共享资源之前进行加锁操作.在计算机科学中,锁(lock ...

随机推荐

  1. Java 处理0x00特殊字符

    Java 处理0x00特殊字符 一.0x00字符 1,0x00是ascii码的0值:NUL 2,0x00在windows系统中显示: 3,0x00在Linux中显示: ctrl+V ctrl+@可以打 ...

  2. Maltego更新到4.2.6

    Maltego更新到4.2.6   此次更新包含以下两处修改: (1)在服务管理中,允许用户修改OAuth回调协议的端口. (2)修复启动画面溢出错误.

  3. 图像处理代码举例(C++、MATLAB、OpenCV)

    一.C中用数组存图像信息,下标索引: pbTag[y*newWidth+x] 见:https://www.cnblogs.com/wxl845235800/p/11149853.html HRESUL ...

  4. C#nameof用法

    1.实现代码 using System; namespace NameofUsage { /// <summary> /// C# nameof用法 /// </summary> ...

  5. Python中__new__和__init__的区别与联系

    __new__ 负责对象的创建而 __init__ 负责对象的初始化. __new__:创建对象时调用,会返回当前对象的一个实例 __init__:创建完对象后调用,对当前对象的一些实例初始化,无返回 ...

  6. Microsoft VBScript 运行时错误 错误 800a005e 无效使用 Null: Replace

    查看数据库   表的字段里面是否有空的字段. where 字段名 is null

  7. "AttributeError: /lib64/libcrypto.so.1.1: undefined symbol: EVP_CIPHER_CTX_cleanup"

    以前在openssl,有 EVP_CIPHER_CTX_cleanup函数. 1.1.0版本中 替换成为EVP_CIPHER_CTX_reset 解决办法: 找到报错的文件vim /usr/local ...

  8. Qt编写气体安全管理系统15-网络转发

    一.前言 在本系统中网络转发是个什么功能含义呢,其实就是将本地采集设备的所有数据打包发送到指定的网络地址,默认采用UDP的形式,无连接开销小,我也是看到很多的组态软件有这个功能,其实现有的很多的气体探 ...

  9. SegNet

    Paper link:https://arxiv.org/pdf/1511.00561.pdf Motivation:为了实际应用,主要是在时间效率和存储空间上做了改进: Introduction: ...

  10. 常见问题:MySQL/事务隔离

    数据库并行产生的问题 A事务撤销时,将B事务更改的数据撤销. A事务提交时,将B事务更改的同行数据覆盖. 脏读:A事务读取到了B事务未提交的数据. 不可重复读:A事务中同查询语句不幂等,读到已更新数据 ...