C#实现Redis的分布式锁

目录
  • Redis实现分布式锁(悲观锁/乐观锁)
  • Redis连接池
  • 使用Redis的SetNX命令实现加锁,
  • 调用方式

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

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

Redis有三个最基本属性来保证分布式锁的有效实现:

  • 安全性: 互斥,在任何时候,只有一个客户端能持有锁。
  • 活跃性A:没有死锁,即使客户端在持有锁的时候崩溃,最后也会有其他客户端能获得锁,超时机制。
  • 活跃性B:故障容忍,只有大多数Redis节点时存活的,客户端仍可以获得锁和释放锁。

基于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的分布式锁的文章就介绍到这了,更多相关C# Redis分布式锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • LCN分布式事务解决方案详解

    目录 一.什么是分布式事务? 二.lcn的实现思路 2.1 本地执行的状态怎么提交给全局事务? 2.2 本地事务的提交或回滚怎么实现? 三.lcn的使用 3.1 下载lcn-manager (全局的事务管理器) 3.2 配置lcn-manager 3.3 启动lcn 3.4 模拟转账服务 3.4.1 add-service 3.4.2 decr-service 3.5 2 个微服务都需要添加依赖 3.6 需要自定义数据库的连接池 3.7 使用 3.7.1 事务的发起者 3.7.2 添加配置文件

  • Java面试题冲刺第二十三天--分布式

    目录 面试题1:说说什么分布式事务?解释一下什么是CAP? CAP理解: 追问1:怎么理解强一致性.弱一致性和最终一致性? 面试题2:了解BASE理论么? 追问1:基于BASE理论,举几个实际的例子 面试题3:实现分布式事务一致性(Consistency)的方法有哪些? 追问1:说一下二阶段提交(2PC)的原理吧 总结 面试题1:说说什么分布式事务?解释一下什么是CAP? 现在互联网开发多使用微服务架构,一个简单的操作,在服务端可能就是由多个服务和数据库实例协同完成的.但在一致性要求较高且高QP

  • 支持python的分布式计算框架Ray详解

    项目地址:https://github.com/ray-project/ray 1.简介 Ray为构建分布式应用程序提供了一个简单.通用的API.Ray是一种分布式执行框架,便于大规模应用程序和利用先进的机器学习库. Ray通过以下方式完成这项任务: 为构建和运行分布式应用程序提供简单的原语. 使最终用户能够并行化单个机器代码,而代码更改很少到零. 在核心Ray之上包含大量应用程序.库和工具,以支持复杂的应用程序. 2.安装 安装方式比较简单: pip install ray==1.4.1 [r

  • java基于mongodb实现分布式锁的示例代码

    目录 原理 实现 使用 原理 通过线程安全findAndModify 实现锁 实现 定义锁存储对象: /** * mongodb 分布式锁 */ @Data @NoArgsConstructor @AllArgsConstructor @Document(collection = "distributed-lock-doc") public class LockDocument { @Id private String id; private long expireAt; privat

  • Redis分布式锁Redlock的实现

    目录 普通实现 Redlock实现 Redlock源码 用法 唯一ID 获取锁 释放锁 普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获取锁(unique_value可以是UUID等) SET resource_name unique_value NX PX 30000 - 释放锁(lua脚本中,一定要比较value,防止误解锁) if redis.call("g

  • Redis分布式限流组件设计与使用实例

    目录 1.背景 2.Redis计数器限流设计 2.1Lua脚本 2.2自定义注解 2.3限流组件 2.4限流切面实现 3.测试一下 3.1方法限流示例 3.2动态入参限流示例 4.其它扩展 5.源码地址 本文主要讲解基于 自定义注解+Aop+反射+Redis+Lua表达式 实现的限流设计方案.实现的限流设计与实际使用. 1.背景 在互联网开发中经常遇到需要限流的场景一般分为两种 业务场景需要(比如:5分钟内发送验证码不超过xxx次); 对流量大的功能流量削峰; 一般我们衡量系统处理能力的指标是每

  • Redis分布式非公平锁的使用

    目录 前言 redis分布式锁第一版 redis分布式锁第二版 redis分布式锁第三版 redis分布式锁最终版 前言 看了很多博客,和资料,这里只针对redis做分布式锁做一下深入探讨,希望对你们有帮助.网上提供了很多分布式锁的操作,这里逐一举例然后评论优缺点及改进方案,希望这样子能让当家更好的理解redis分布式锁. redis分布式锁第一版 大家应该都知道Redis做分布式锁无非就是INCR命令或者是SetNx命令,这里我们采用setnx命令. 操作:setnx key 如果操作成功则代

  • Redisson实现Redis分布式锁的几种方式

    目录 Redis几种架构 普通分布式锁 单机模式 哨兵模式 集群模式 总结 Redlock分布式锁 实现原理 问题合集 前几天发的一篇文章<Redlock:Redis分布式锁最牛逼的实现>,引起了一些同学的讨论,也有一些同学提出了一些疑问,这是好事儿.本文在讲解如何使用Redisson实现Redis普通分布式锁,以及Redlock算法分布式锁的几种方式的同时,也附带解答这些同学的一些疑问. Redis几种架构 Redis发展到现在,几种常见的部署架构有: 单机模式: 主从模式: 哨兵模式: 集

  • Redis实现分布式锁的几种方法总结

    Redis实现分布式锁的几种方法总结 分布式锁是控制分布式系统之间同步访问共享资源的一种方式.在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁. 我们来假设一个最简单的秒杀场景:数据库里有一张表,column分别是商品ID,和商品ID对应的库存量,秒杀成功就将此商品库存量-1.现在假设有1000个线程来秒杀两件商品,500个线程秒杀第一个商品,

  • 基于Redis实现分布式锁以及任务队列

    一.前言 双十一刚过不久,大家都知道在天猫.京东.苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力,严重时说不定就宕机了,另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下 减1),那次这个

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • python如何使用Redis构建分布式锁

    这篇文章主要介绍了python如何使用Redis构建分布式锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在实际应用场景中,我们可能有多个worker,可能在一台机器,也可能分布在不同的机器,但只有一个worker可以同时持有一把锁,这个时候我们就需要用到分布式锁了. 这里推荐python的实现库,Redlock-py(Python 实现). 正常情况下,worker获得锁后,处理自己的任务,完成后自动释放持有的锁,是不是感觉有点熟悉,很容易

  • SpringBoot使用Redis实现分布式锁

    前言 在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对操作的对象加锁就可以解决对象的线程安全问题. 分布式应用时代这个方法却行不通了,我们的应用可能被部署到多台机器上,运行在不同的JVM里,一个对象可能同时存在多台机器的内存中,怎样使共享对象同时只被一个线程处理就成了一个问题. 在分布式系统中为了保证一个对象在高并发的情况下只能被一个线程使用,我们需要一种跨JVM的互斥机制来控制共享资源的访问,此时就需要用到

  • 基于redis实现分布式锁的原理与方法

    前言 系统的不断扩大,分布式锁是最基本的保障.与单机的多线程不一样的是,分布式跨多个机器.线程的共享变量无法跨机器. 为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 今天我们介绍通过redis实现分布式锁.实际上这三种和java对比看属于一类.都是属于程序外部锁. 原理剖析 上述三种分布

  • SpringBoot中使用redis做分布式锁的方法

    一.模拟问题 最近在公司遇到一个问题,挂号系统是做的集群,比如启动了两个相同的服务,病人挂号的时候可能会出现同号的情况,比如两个病人挂出来的号都是上午2号.这就出现了问题,由于是集群部署的,所以单纯在代码中的方法中加锁是不能解决这种情况的.下面我将模拟这种情况,用redis做分布式锁来解决这个问题. 1.新建挂号明细表 2.在idea上新建项目 下图是创建好的项目结构,上面那个parent项目是其他项目不用管它,和新建的没有关系 3.开始创建controller,service,dao(mapp

  • php redis setnx分布式锁简单原理解析

    我就废话不多说了,大家还是直接看代码吧~ <?php //高并发分布式锁 header("Content-type:text/html;charset=utf-8"); $redis = new Redis(); $redis->connect('127.0.0.1', 6379); echo "Connection to server sucessfully"; //echo $redis->get("name");exit;

  • SpringBoot集成redis实现分布式锁的示例代码

    1.准备 使用redis实现分布式锁,需要用的setnx(),所以需要集成Jedis 需要引入jar,jar最好和redis的jar版本对应上,不然会出现版本冲突,使用的时候会报异常redis.clients.jedis.Jedis.set(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String; 我使用的redis版本是2.3.0,Jedis使用的是3.3.0 <de

  • SpringBoot之使用Redis实现分布式锁(秒杀系统)

    一.Redis分布式锁概念篇 建议直接采用Redis的官方推荐的Redisson作为redis的分布式锁 1.1.为什么要使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间! 后来业务发展,需要做集群,一个应用需要部署到几台机

随机推荐