MASA Framework的分布式锁设计
前言
什么是锁?什么是分布式锁?它们之间有什么样的关系?
什么是锁
加锁(lock)是2018年公布的计算机科学技术名词,是指将控制变量置位,控制共享资源不能被其他线程访问。通过加锁,可以确保在同一时刻只有一个线程在访问被锁住的代码片段,我们在单机部署时可使用最简单的加锁完成资源的独享,如:
public class Program { private static readonly object Obj = new { }; public static void Main() { lock (obj) { //同一时刻只有一个线程可以访问 } } }
什么是分布式锁
但随着业务发展的需要,原单体单机部署的系统被部署成分布式集群系统后,原来的并发控制策略失效,为了解决这个问题就需要引入分布式锁,那分布式锁应该具备哪些条件?
- 原子性:在分布式环境下,一个方法在同一个时间点只能被一台机器下的一个线程所执行,防止数据资源的并发访问,避免数据不一致情况
- 高可用:具备自动失效机制,防止死锁,获取锁后如果出现错误,并且无法释放锁,则使用租约一段时间后自动释放锁
- 阻塞性:具备非阻塞锁特性(没有获取到锁时直接返回获取锁失败,不会长时间因等待锁导致阻塞)
- 高性能:高性能的获取锁与释放锁
- 可重入性:具备可重入特性,在同一线程外层函数获得锁之后,内层方法会自动获取锁
实现
分布式锁是特定于实现的,目前MasaFramework提供了两个实现,分别是Local
、Medallion
,下面会介绍如何配置并使用它们
本地锁
是基于SemaphoreSlim
实现的,它不是真正的分布式锁,我们建议你在开发和测试环境中使用它,不需要联网也不会与其他人冲突
Medallion
是基于DistributedLock实现的分布式锁,它提供了很多种技术的实现,包括Microsoft SQL Server
、Postgresql
、MySQL 或 MariaDB
、Oracle
、Redis
、Azure blob
、Apache ZooKeeper
、锁文件
、操作系统全局WaitHandles(Windows)
,我们只需要任选一种实现即可,目前Medallion
提供的分布式锁并不支持可重入性,点击了解原因
快速入门
- 安装.NET 6.0
以本地锁单应用锁为例:
- 新建ASP.NET Core 空项目
Assignment.DistributedLock.Local
,并安装Masa.Contrib.Data.DistributedLock.Local
dotnet new web -o Assignment.DistributedLock.Local cd Assignment.DistributedLock.Local dotnet add package Masa.Contrib.Data.DistributedLock.Local --version 0.6.0-preview.10
- 注册锁,修改类
Program
builder.Services.AddLocalDistributedLock();//注册本地锁
- 如何使用锁?修改类
Program
app.MapGet("lock", (IDistributedLock distributedLock) => { using var @lock = distributedLock.TryGet("test");//获取锁 if (@lock != null) { //todo: 获取锁成功 return "success"; } return "获取超时"; });
通过DI获取IDistributedLock
,并通过TryGet
方法获取锁,如果获取锁失败,则返回null,如果返回到的对象不为null,则表明获取锁成功,最后在获取锁成功后写自己的业务代码即可
TryGet
方法拥有以下参数
key
(string, 必须): 锁的唯一名称,可通过key来访问不同的资源,执行不同的业务timeout
(TimeSpan): 等待获取锁的最大超时时间. 默认值为: TimeSpan.Zero(代表如果锁已经被另一个应用程序拥有, 它不会等待.)
TryGetAsync
方法除了拥有TryGet
的所有参数之外,还拥有以下参数
cancellationToken
: 取消令牌可在触发后取消操作
如果你选择使用Medallion
,只需要选择一种技术实现,并根据Readme
注册锁即可,在使用锁上是没有区别的
如何扩展其它的分布式锁
新建类库
Masa.Contrib.Data.DistributedLock.{分布式锁名}
,并添加引用Masa.BuildingBlocks.Data.csproj
新建分布式锁实现类
DefaultDistributedLock
,并实现IDistributedLock
public class DefaultDistributedLock : IDistributedLock { public IDisposable? TryGet(string key, TimeSpan timeout = default) { // 获取锁失败则返回null,当资源被释放时,主动释放锁, 无需人为手动释放 throw new NotImplementedException(); } public Task<IAsyncDisposable?> TryGetAsync(string key, TimeSpan timeout = default, CancellationToken cancellationToken = default) { //获取锁失败则返回null,当资源被释放时,主动释放锁, 无需人为手动释放 throw new NotImplementedException(); } }
- 新建类
ServiceCollectionExtensions
,注册分布式锁到服务集合
public static class ServiceCollectionExtensions { public static IServiceCollection AddDistributedLock(this IServiceCollection services, Action<MedallionBuilder> builder) { services.TryAddSingleton<IDistributedLock, DefaultDistributedLock>(); return services; } }
小知识
为什么TryGet
、TryGetAsync
方法的返回类型分别是IDisposable
、IAsyncDisposable
?
我们希望使用锁可以足够的简单,在使用完锁之后可以自动释放锁,而不是必须手动释放,当返回类型为IDisposable
、IAsyncDisposable
时,使用完毕后会触发Dispose
或DisposeAsync
,这样一来就可以使得开发者可以忽略释放锁的逻辑
以本地锁为例:
public class DefaultLocalDistributedLock : IDistributedLock { private readonly MemoryCache<string, SemaphoreSlim> _localObjects = new(); public IDisposable? TryGet(string key, TimeSpan timeout = default) { var semaphore = GetSemaphoreSlim(key); if (!semaphore.Wait(timeout)) { return null; } return new DisposeAction(semaphore); } //todo: 以下省略 TryGetAsync 方法 private SemaphoreSlim GetSemaphoreSlim(string key) { ArgumentNullOrWhiteSpaceException.ThrowIfNullOrWhiteSpace(key); return _localObjects.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); } } internal class DisposeAction : IDisposable, IAsyncDisposable { private readonly SemaphoreSlim _semaphore; public DisposeAction(SemaphoreSlim semaphore) => _semaphore = semaphore; public ValueTask DisposeAsync() { _semaphore.Release(); return ValueTask.CompletedTask; } public void Dispose() => _semaphore.Release(); }
本章源码
Assignment09
https://github.com/zhenlei520/MasaFramework.Practice
开源地址
MASA.Framework:https://github.com/masastack/MASA.Framework
如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们
- WeChat:MasaStackTechOps
- QQ:7424099

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
kubernetes kube-apiserver 存在SSRF漏洞
漏洞描述 kube-apiserver 是Kubernetes 的核心组件之一,kube-apiserver 主要提供集群管理的 REST API 接口和其他模块之间的数据交互和通信的枢纽。 kube-apiserver 的受影响版本中存在SSRF漏洞,漏洞源于其允许聚合API 服务器将客户端流量重定向到任何 URL,进而将客户端的 API服务器凭据转发给第三方。 攻击者可利用该漏洞进行内网扫描甚至访问内部系统数据。 漏洞名称 kubernetes kube-apiserver 存在SSRF漏洞 漏洞类型 SSRF 发现时间 2022/9/17 漏洞影响广度 广 MPS编号 MPS-2022-56496 CVE编号 CVE-2022-3172 CNVD编号 - 影响范围 k8s.io/kubernetes/cmd/kube-apiserver@[1.25.0, 1.25.1) k8s.io/kubernetes/cmd/kube-apiserver@(-∞, 1.21.15) k8s.io/kubernetes/cmd/kube-apiserver@[1.22.0, 1.22.14) ...
- 下一篇
Flink+ice 实现可视化规则编排与灵活配置(Demo)
ice文档站:http://124.221.148.247/zh 1 Demo仓库地址: github:https://github.com/zjn-zjn/flink-ice gitee:https://gitee.com/waitmoon/flink-ice 2 Demo功能描述 通过netcat制造输入流(nc -l 9000 windows:nc -l -p 9000) flink接收本地9000端口输入流,以回车(\n)分割单词 输入流经过IceProcessor处理后打印结果流 3 项目搭建 使用flink-quickstart-java快速搭建flink项目 3.1 添加ice依赖 因flink为非Spring项目,需依赖ice-core并手动初始化,Spring项目直接依赖ice-client-spring-boot-starter即可 <dependency> <groupId>com.waitmoon.ice</groupId> <artifactId>ice-core</artifactId> <v...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8编译安装MySQL8.0.19