RedLock 实现分布式锁

并发是程序开发中不可避免的问题,根据系统面向用户、功能场景的不同,并发的重视程度会有不同。从程序的角度来说,并发意味着相同的时间点执行了相同的代码,而有些情况是不被允许的,比如:转账、抢购占库存等,如果没有做好临界条件的验证,会带来非常严重的后果。追根结底是因为并发引起的数据不一致问题,面对并发,我们通常会采用锁来优化。

场景模拟

如下模拟抢购的示例代码(C#):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 有10个商品库存
private static int stockCount = 10;

public bool Buy()
{
// 模拟执行的逻辑代码花费的时间
Thread.Sleep(new Random().Next(100,500));
if (stockCount > 0)
{
stockCount--;
return true;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
var test = new Test();

Parallel.For(1, 16, (i) =>
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var data = test.Buy();
stopwatch.Stop();
Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
});
Console.ReadKey();

模拟并行调用 Buy 方法 15 次(内部使用的是线程池,所以 ThreadId 会有重复),实际上只有 10 个库存,返回结果却显示 11 个请求都购买成功了。

单机部署模式解决方案

在单机部署模式下,我们只需要加 lock(){} 就可以解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 有10个商品库存
private static int stockCount = 10;

private static object obj = new object();

public bool Buy()
{
lock (obj)
{
// 模拟执行的逻辑代码花费的时间
Thread.Sleep(new Random().Next(100, 500));
if (stockCount > 0)
{
stockCount--;
return true;
}
return false;
}
}


从输出结果中可以看出,确实只有10个请求是显示购买成功,但同时发现部分请求的执行时间明显变长,这就是加锁带来的最直观影响,当某个线程获得锁之后,在没有释放之前,其他线程只能继续等待,并发越高,更多的线程需要等待轮流被处理。

各种语言一般都提供了锁的实现,用法大同小异,语言本身实现的锁只能作用于当前进程内,所以在单机模式部署的系统中使用基本没什么问题。

集群部署模式解决方案(分布式锁)

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

这里将介绍基于 Redis 的 RedLock.net 来解决分布式下的并发问题,RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现 (大部分语言都有对应的实现,查看) ,RedLock 分布式锁算法是由 Redis 的作者提出。

640?wx_fmt=png

RedLock 简介

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

获取锁:

1
SET resource_name my_random_value NX PX 30000

释放锁:

1
2
3
4
5
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

RedLock.net 集成

  1. 创建 .NETCore API 项目

  2. Nuget 安装 RedLock.net

    1
    Install-Package RedLock.net
  3. appsettings.json 添加 redis 配置

    1
    2
    3
    4
    {
    "RedisUrl": "127.0.0.1:6379", // 多个用,分割
    ...
    }
  4. 添加 ProductService.cs,模拟商品购买

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 有10个商品库存,如果同时启动多个API服务进行测试,这里改成存数据库或其他方式
    private static int stockCount = 10;
    public async Task<bool> BuyAsync()
    {
    // 模拟执行的逻辑代码花费的时间
    await Task.Delay(new Random().Next(100, 500));
    if (stockCount > 0)
    {
    stockCount--;
    return true;
    }
    return false;
    }
  5. 修改 Startup.cs ,创建 RedLockFactory

    定义 RedLockFactory 变量:

    1
    private RedLockFactory lockFactory;

    添加方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private RedLockFactory GetRedLockFactory()
    {
    var redisUrl = Configuration["RedisUrl"];
    if (string.IsNullOrEmpty(redisUrl))
    {
    throw new ArgumentException("RedisUrl 不能为空");
    }
    var urls = redisUrl.Split(",").ToList();
    var endPoints = new List<RedLockEndPoint>();
    foreach (var item in urls)
    {
    var arr = item.Split(":");
    endPoints.Add(new DnsEndPoint(arr[0], Convert.ToInt32(arr[1])));
    }
    return RedLockFactory.Create(endPoints);
    }

    在 ConfigureServices 注入 IDistributedLockFactory:

    1
    2
    3
    lockFactory = GetRedLockFactory();
    services.AddSingleton(typeof(IDistributedLockFactory), lockFactory);
    services.AddScoped(typeof(ProductService));

    修改 Configure,应用程序结束时释放 lockFactory :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
    {
    ...

    lifetime.ApplicationStopping.Register(() =>
    {
    lockFactory.Dispose();
    });
    }
  6. 在 Controller 添加方法 DistributedLockTest

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    private readonly IDistributedLockFactory _distributedLockFactory;
    private readonly ProductService _productService;

    public HomeController(IDistributedLockFactory distributedLockFactory,
    ProductService productService)
    {
    _distributedLockFactory = distributedLockFactory;
    _productService = productService;
    }

    [HttpGet]
    public async Task<bool> DistributedLockTest()
    {
    var productId = "id";
    // resource 锁定的对象
    // expiryTime 锁定过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放
    // waitTime 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间
    // retryTime 等待时间内,多久尝试获取一次
    using (var redLock = await _distributedLockFactory.CreateLockAsync(productId, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(20)))
    {
    if (redLock.IsAcquired)
    {
    var result = await _productService.BuyAsync();
    return result;
    }
    else
    {
    Console.WriteLine($"获取锁失败:{DateTime.Now}");
    }
    }
    return false;
    }
  7. 调用接口测试

    1
    2
    3
    4
    5
    6
    7
    8
       Parallel.For(1, 16, (i) =>
    {
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    var data = GetAsync($"http://localhost:5000/home/distributedLockTest").Result;
    stopwatch.Stop();
    Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
    });

关于 RedLock 分布式锁算法的争议大家可以参考:

How to do distributed locking
Is Redlock safe?

总结

如果使用锁,必然对性能上会有一定影响,我们需要根据实际场景来判断是真正需要。在指定锁过期时间时要相对合理,避免出现锁已过期,但逻辑还没执行完成,这样就失去了锁的意义,当然这种情况下我们还可以考虑重入锁。

最后推荐一下微软开源的一个基于 Actor 模型的分布式框架 Orleans,也可以达到分布式锁的效果。

参考链接

  • Distributed locks with Redis

  • RedLock.net

原文地址:http://beckjin.com/2019/01/06/redLock-net/

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/315756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[翻译] NumSharp的数组切片功能 [:]

原文地址&#xff1a;https://medium.com/scisharp/slicing-in-numsharp-e56c46826630翻译初稿&#xff08;英文水平有限&#xff0c;请多包涵&#xff09;&#xff1a;由于Numsharp新推出了数组切片这个牛逼的功能&#xff0c;所以.NET社区距离拥有强大的开源机器学习平台又近…

Visual Studio 2019 16.1发布,更快更高效

Visual Studio 2019 16.1 已正式发布&#xff0c;可以看到&#xff0c;新版本的启动速度有了显著的提升&#xff0c;还节省了不少的内存空间。主要更新如下&#xff1a;IDE现已公开发布 Visual Studio IntelliCode&#xff0c;并且可以随任何支持 C#、C、TypeScipt/JavaScript …

.NET Core 3.0之创建基于Consul的Configuration扩展组件

经过前面三篇关于.NET Core Configuration的文章之后&#xff0c;本篇文章主要讨论如何扩展一个Configuration组件出来。如果前面三篇文章没有看到&#xff0c;可以点击如下地址访问.NET Core 3.0之深入源码理解Configuration(一).NET Core 3.0之深入源码理解Configuration(二)…

Mono 和 .NET Core比翼双飞

大家好&#xff0c;今天给大家分享.NET 蓝图之下的Mono和.NET Core 话题&#xff0c;微软在Build 2019 大会上给.NET 做了一个五年规划&#xff0c;所以分享的主题就是《Mono和.NET Core 比翼双飞》&#xff0c;将在完成这个五年规划的时候合体。在开始这个主题之前&#xff0c…

在上司面前硬不起来?教你如何快速将字符串转换为可执行代码

老是因为活不好被上司欺凌&#xff1f;在上司面前很没面子&#xff1f;在上司面前硬不起来&#xff1f; 是时候分享一个可以快速将字符串转换为可执行代码的项目给你了 - YACEP !不过&#xff0c;这不是一篇专门对YACEP 做详细介绍的随笔&#xff0c;想知道更详细的的YACEP 细节…

ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?

事情不太多时&#xff0c;会时不时去看项目中同事写的代码可以作个参考或者学习&#xff0c;个人觉得只有这样才能走的更远&#xff0c;抱着一副老子天下第一的态度最终只能是井底之蛙。前两篇写到关于断点传续的文章&#xff0c;还有一篇还未写出&#xff0c;后续会补上&#…

记一次ORM的权衡和取舍

面对ORM的选型&#xff0c;有些人是根据自己熟悉程度来评判&#xff0c;有些人是根据他人的推荐来抉择&#xff0c;有些人觉得都差不多&#xff0c;随便了。当自己要真正做选择的时候&#xff0c;以上的这些依据都无法真正说服自己&#xff0c;因为不同的业务需求&#xff0c;不…

出让执行权:Task.Yield, Dispatcher.Yield

一个耗时的任务&#xff0c;可以通过 Task.Yield 或者 Dispatcher.Yield 来中断以便分割成多个小的任务片段执行。Yield 这个词很有意思&#xff0c;叫做“屈服”“放弃”“让步”&#xff0c;字面意义上是让出当前任务的执行权&#xff0c;转而让其他任务可以插入执行。Task、…

VS Code 即将迎来再一次的 logo 更新!已可在 Insiders 版本尝鲜

为什么要说“再一次”&#xff1f; 相信 VS Code 的老用户都还记得两年前的 logo 更新风波吧。当时 VS Code 改了新 logo 之后&#xff0c;VS Code 的用户们一片哀嚎&#xff0c;纷纷觉得新 logo 太丑&#xff0c;在 GitHub 和各种社交媒体上各种吐槽&#xff01;不过幸运的是&…

从零开始在 Windows 上部署 .NET Core 到 Kubernetes

本章节所有代码已上传至&#xff1a;https://github.com/Seanwong933/.NET-Core-on-Kubernetes文末附有本人遇到过的 Docker 和 k8s 的故障排除。本文目标&#xff1a;带领大家在 Kubernetes 上部署一个 .NET Core Api 的单节点集群。后续文章会帮助大家继续深入。安装 Kuberne…

.NET Core微服务 权限系统+工作流(一)权限系统

一、前言实际上权限系统老早之前我就在一直开发&#xff0c;大概在刚毕业没多久就想一个人写一个系统&#xff0c;断断续续一直坚持到现在&#xff0c;毕竟自己亲动手自写的系统才有收获&#xff0c;本篇仅介绍权限。小小系统上不了台面&#xff0c;望各位大神勿喷。二、系统介…

iNeuOS云操作系统,.NET Core全系打造

一.演示地址演示地址&#xff1a; 进入iNeuOS系统。&#xff08;建议使用chrome浏览器&#xff09;http://192.144.173.38:8081/login.html测试名称&#xff1a;admin测试密码&#xff1a;admin下载《iNeuOS云操作系统演示应用手册》 链接&#xff1a;https://pan.baidu.co…

译 | 你到底有多精通 C# ?

点击上方蓝字关注“汪宇杰博客”文&#xff1a;Damir Arh译&#xff1a;Edi Wang即使是具有良好 C# 技能的开发人员有时候也会编写可能会出现意外行为的代码。本文介绍了属于该类别的几个 C# 代码片段&#xff0c;并解释了令人惊讶的行为背后的原因。Null 值我们都知道&#xf…

各大主流K8S服务全方位能力比对

大家好&#xff0c;趁打开流量主的东风&#xff0c;特此贡献一篇长文&#xff0c;分析一下目前国内国外几大著名云厂商的kubernetes服务&#xff0c;以飨诸君。文起之前&#xff0c;先聊态度。 我本人是十分看好k8s的发展的&#xff0c;为何&#xff1f; 理因古往今来&#xff…

.NET Core 的Generic Host 之Generic Host Builder

通用Host(Generic Host) 与 web Host 不同的地方就是通用Host解耦了Http请求管道&#xff0c;使得通用Host拥有更广的应用场景。比如&#xff1a;消息收发、后台任务以及其他非http的工作负载。这些场景都可以通过使用通用Host拥有横切&#xff08;Cross-cutting&#xff09;的…

.NET Core微服务 权限系统+工作流(二)工作流系统

一、前言接上一篇 .NET Core微服务 权限系统工作流&#xff08;一&#xff09;权限系统 &#xff0c;再来一发工作流&#xff0c;我在接触这块开发的时候一直好奇它的实现方式&#xff0c;翻看各种工作流引擎代码&#xff0c;探究其实现方式&#xff0c;个人总结出来一个核心要…

开源分布式Job系统,调度与业务分离-如何创建一个计划HttpJob任务

项目介绍&#xff1a;Hangfire&#xff1a;是一个开源的job调度系统,支持分布式JOB&#xff01;&#xff01;Hangfire.HttpJob 是我针对Hangfire开发的一个组件,该组件和Hangfire本身是独立的。可以独立更新Hangfire版本不影响&#xff01;该组件已被Hangfire官方采纳&#xff…

Angular 8正式发布!

Angular 团队今天宣布推出 Angular 8 正式版。作为一个期待已久的重大版本更新&#xff0c;Angular 8 为框架、Angular Material 和命令行界面工具 Angular CLI 带来了大量的改进和新功能。团队表示 Angular 8 显著减少了在现代浏览器中应用程序的启动时间、提供了用于定制 CLI…

.NET Core 3.0 webapi集成Swagger 5.0

在项目中引用Swashbuckle.AspNetCore和Swashbuckle.AspNetCore.Filters两个dll&#xff0c;在Startup中的ConfigureServices相关配置代码如下 两个重点&#xff1a;1、options.DocumentFilter<HiddenApiFilter>();定义那些接口方法被隐藏2、启用oauth2安全授权访问…

站点部署,IIS配置优化指南

通常把站点发布到IIS上运行正常后&#xff0c;很少会去考虑IIS提供的各种参数&#xff0c;如何配置才是最适合当前站点运行需要的&#xff1f;这篇文章&#xff0c;从基本设置、回收机制、性能、并发、安全性等IIS设置讲解应当如何优化。先来“IIS应用程序池”优化后的参数配置…