首页 > 数据库 >.net core 分布式锁 之 基于 Redis 的 RedLock

.net core 分布式锁 之 基于 Redis 的 RedLock

时间:2023-12-14 11:36:27浏览次数:43  
标签:core Thread RedLock Redis stopwatch var public 分布式

使用场景

分布式锁的业务场景涉及到并发控制、任务调度、缓存更新、分布式事务和防止重复操作等方面,能够保证分布式系统的数据一致性和正确性。

  1. 并发控制:当多个线程或进程同时访问共享资源时,使用分布式锁可以确保只有一个线程或进程能够访问该资源,避免数据竞争和并发冲突。

  2. 分布式任务调度:在分布式系统中,多个节点可能同时竞争执行某个任务。使用分布式锁可以确保只有一个节点能够获取到任务的执行权限,避免重复执行和资源浪费。

  3. 缓存更新:在使用缓存的场景下,当缓存失效时,多个请求可能会同时访问数据库或其他资源来重新生成缓存。使用分布式锁可以确保只有一个请求能够重新生成缓存,避免缓存雪崩和数据库压力过大。

  4. 分布式事务:在分布式系统中,涉及到多个服务之间的事务操作时,使用分布式锁可以协调各个服务的操作顺序和一致性,保证分布式事务的正确执行。

  5. 防止重复操作:在某些业务场景下,需要确保某个操作只能执行一次,例如订单支付、秒杀等。使用分布式锁可以防止重复操作,确保每个操作只会执行一次。

秒杀业务模拟

不使用锁的业务

备注:这里使用了parallel模拟多线程并发执行操作,也可以用Jemeter来模拟,后面的示例我就使用Jemeter来模拟并发

 public bool Buy3()
        {
            // 模拟执行的逻辑代码花费的时间
            Thread.Sleep(200);
            if (stock.stockCount > 0)
            {
                Thread.Sleep(10);  //这里为了更能够看出效果,休眠了10毫秒
                stock.stockCount--;
                return true;
            }
            return false;

        }
public static class stock
    {
        // 有5个商品库存
        public static int stockCount = 5;
    }
[HttpGet("TestLock")]
        public async Task<IActionResult> TestLock()
        {
            Test test=new Test();
// 使用parallel模拟多线程并发执行操作 Parallel.For(0, 15, (index) => { var stopwatch = new Stopwatch(); stopwatch.Start(); var data = test.Buy3(); stopwatch.Stop(); Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}"); }); return Ok(); }

执行后,我们的5个库存变成了8个执行成功,出现了超卖的情况

 

单机锁

在没有集群的情况下,只有一个进程可以使用单机锁,会比分布式锁效率更高,因为是在进程内进行的,不会涉及到和其他应用进行通信

        public object obj=new object();

        public bool Buy()
        {
            Thread.Sleep(200);
            lock (obj)
            {
                if (stock.stockCount > 0)
                {
                    Thread.Sleep(10);
                    stock.stockCount--;
                    return true;
                }
                return false;
            }
           
        }
        [HttpGet("TestLock3")]
        public async Task<IActionResult> TestLock3()
        {
            Test test = new Test();
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            var data = test.Buy();
            stopwatch.Stop();
            Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
            return Ok();
        }

 

执行后,结果正确,可以看到5个卖出,但是这个执行的时候每个进程都要等待前一个进程执行成功才能够开始执行,所以速度相对不加锁是比较慢的,但是没办法,为了保证数据的正确性,所以分清楚场景,并不是每一个场景都适合用单机锁和分布式锁

 

分布式锁RedLock

官方使用地址:samcook/RedLock.net:C语言中Redlock算法的实现# (github.com)

在集群模式下,系统部署于多台机器(一个系统运行在多个进程中),语言本身实现的锁只能确保当前进程内有效(基于内存),多进程就没办法共享锁状态,这时我们就得考虑采用分布式锁,分布式锁可以采用数据库、ZooKeeper、Redis等来实现,最终都是为了达到在不同的进程、线程内能共享锁状态的目的。

RedLock的原理:

  RedLock 的思想是使用多台 Redis Master ,节点完全独立,节点间不需要进行数据同步,因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave,被选为 Master 的 Slave 就丢掉了锁,另一个客户端就可以再次拿到锁。锁通过 setNX(原子操作) 命令设置,在有效时间内当获得锁的数量大于 (n/2+1) 代表成功,失败后需要向所有节点发送释放锁的消息。

官方的说明:

 

1. 引入nuget包

RedLock.net
StackExchange.Redis   //如果RedLock 2.2.0+ 需要 StackExchange.Redis 2.0+

2. 配置Program.cs

#region 分布式锁redlock
var existingConnectionMultiplexer1 = ConnectionMultiplexer.Connect("redis连接地址");
var multiplexers = new List<RedLockMultiplexer>
            {
                existingConnectionMultiplexer1
                //existingConnectionMultiplexer2,                            //redis节点2
                //existingConnectionMultiplexer3                            //redis节点3
            };
var redlockFactory = RedLockFactory.Create(multiplexers);
//注入锁和服务
builder.Services.AddSingleton(typeof(IDistributedLockFactory), redlockFactory);
#endregion
#region 应用生命周期释放分布式锁
app.Lifetime.ApplicationStopping.Register(() =>
{
    redlockFactory.Dispose();
});
#endregion

3. 业务模拟代码

        private readonly IDistributedLockFactory _distributedLockFactory;

        public ValuesController(IDistributedLockFactory distributedLockFactory)
        {
            _distributedLockFactory = distributedLockFactory;
        }
        [HttpGet("TestLock2")]
        public async Task<IActionResult> TestLock2()
        {
            var resource = "the-thing-we-are-locking-on";  // 锁定的对象
            var expiry = TimeSpan.FromSeconds(30);  //  锁定过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放
            var wait = TimeSpan.FromSeconds(10);   //等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间,超过这个时间就不会等待锁了,即 redLock.IsAcquired 为false 
            var retry = TimeSpan.FromSeconds(1); //等待时间内,间隔多久尝试获取一次,如果超过上面设置的等待时间还没有获取到锁,也不会等待锁,即redLock.IsAcquired 为false 
            
            await using (var redLock = await _distributedLockFactory.CreateLockAsync(resource, expiry, wait, retry)) // 这里也可以不使用 wait 和 retry ,如果不使用当出现锁被占用就直接跳过,不会等待
            {
                // make sure we got the lock
                if (redLock.IsAcquired)
                {
                    var stopwatch = new Stopwatch();
                    stopwatch.Start();
                    var data = new Test().Buy2();
                    stopwatch.Stop();
                    Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
                    return Ok($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
                }
                else
                {
                    Console.WriteLine("等待超时");
                    return Ok("等待超时");
                }
            }
        }

执行结果,全部正常

总结:在单个进程或线程内部进行并发控制,简单的锁可能更加高效。而在分布式系统中需要保证数据一致性和并发控制时,分布式锁是必要的,尽管可能会带来一些性能开销。

 

参考文档:【愚公系列】2023年01月 .NET CORE工具案例-RedLock.net实现分布式锁-云社区-华为云 (huaweicloud.com)

官方地址:samcook/RedLock.net: An implementation of the Redlock algorithm in C# (github.com)

 

标签:core,Thread,RedLock,Redis,stopwatch,var,public,分布式
From: https://www.cnblogs.com/roubaozidd/p/17900823.html

相关文章

  • 关于Redis
    1、Redis事务不支持回滚即使事务执行过程中,有其中一条命令出错了,那么只有该条命令不会被执行,其前后的命令仍然会被执行;只有在执行事务之前的组队阶段发生错误,才会回滚2、Redis事务只是保证在事务中的命令在执行的过程中不会被打断3、Redis是基于单线程的,每个命令都能保证其原......
  • redis stream的所有方法以及用处和使用场景
    目录一、用途:将消息添加到Stream中。二、用途:按范围获取消息。三、用途:阻塞读取消息,支持多个Stream。四、用途:创建消费者组。五、用途:阻塞读取消息并将其分配给消费者组中的消费者。六、用途:确认消息已被消费。七、用途:获取待处理的消息列表。八、用途:删除消息。九、用......
  • Redis数据结构4:REDIS_ZIPLIST
    REDIS_ZIPLISTzipList(压缩列表)是一种紧凑型的数据结构,占用一片连续的内存,本质上是一个字节数组。能提高CPU缓存的利用效率,并且针对不同数据结构进行不同编码,节省内存开销。编码结构zipList的字节数组主要由5个部分组成:zlbytes、zltail、zllen、zltail和entry。zlbytes记录......
  • Asp.net core Net6.0 Webapi 项目如何优雅地使用内存缓存
    前言缓存是提升程序性能必不可少的方法,Asp.netcore支持多级缓存配置,主要有客户端缓存、服务器端缓存,内存缓存和分布式缓存等。其中客户端缓和服务器端缓存在使用上都有比较大的限制,而内存缓和分布式缓存则比较灵活。内存缓存就是一种把缓存数据放到应用程序内存中的机制。本......
  • .net core 同步锁/异步锁
    一、同步锁privatestaticreadonlyobject_lock=newobject();///同步锁publicvoidTestLock(){lock(_lock){//需要处理的业务Console.Write("输出内容");......
  • net core 异步超时取消机制
    方法一:利用Task.WhenAnynamespaceConsoleApp1{internalclassProgram{staticvoidMain(string[]args){Console.WriteLine("Hello,World!");CancellationTokenSourcects=newCancellationTokenSource......
  • Redis实战篇
    Redis实战篇开篇导读短信登录这一块我们会使用redis共享session来实现商户查询缓存通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码中看到对应的内容优惠卷秒杀通过本章节,我们可以学会Redis的计数......
  • 2023最新中级难度Redis面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-中级难度Redis面试题合集问:请解释Redis中的持久化机制RDB和AOF的区别,并谈谈你在实际应用中的选择。Redis的两种持久化机制分别为RDB和AOF:RDB(RedisDatabase)是Redis默认的持久化方式,会在指定的时间间隔内将内存中的数据集快照写入磁盘......
  • 2023最新初级难度Redis面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-初级难度Redis面试题合集问:请简单介绍一下Redis,以及它主要用于解决什么问题?Redis是一款键值存储系统,也被称为“内存数据库”,其主要特点是在内存中高速存储数据。它的优点在于其极高的读写速度和较低的延迟,因此常被用来作为缓存、队列......
  • orchardcore 部署saas系统
    使用orchardcore搭建saas的webhost服务网络托管服务是一种互联网托管服务,允许个人和组织通过万维网访问他们的网站。我们将使用OrchardCoreCMS框架创建一个多租户、SaaS、Web托管服务。1启动VisualStudio,然后“创建新项目”。选择“ASP.NETCoreWebApplication”......