前言

什么是锁?什么是分布式锁?它们之间有什么样的联络?

什么是锁

加锁(lock)是2018年发布的计算机科学技能名词,是指将控制变量置位,控制共享资源不能被其他线程拜访。通过加锁,能够确保在同一时刻只要一个线程在拜访被锁住的代码片段,咱们在单机布置时可运用最简略的加锁完成资源的独享,如:

public class Program
{
    private static readonly object Obj = new { };
    public static void Main()
    {
        lock (obj)
        {
            //同一时刻只要一个线程能够拜访
        }
    }
}

什么是分布式锁

但随着业务发展的需要,原单体单机布置的体系被布置成分布式集群体系后,原来的并发控制战略失效,为了处理这个问题就需要引进分布式锁,那分布式锁应该具有哪些条件?

  • 原子性:在分布式环境下,一个办法在同一个时刻点只能被一台机器下的一个线程所执行,避免数据资源的并发拜访,避免数据不一致状况
  • 高可用:具有自动失效机制,避免死锁,获取锁后假如出现错误,而且无法开释锁,则运用租约一段时刻后自动开释锁
  • 堵塞性:具有非堵塞锁特性(没有获取到锁时直接回来获取锁失利,不会长期因等候锁导致堵塞)
  • 高性能:高性能的获取锁与开释锁
  • 可重入性:具有可重入特性,在同一线程外层函数取得锁之后,内层办法会自动获取锁

MASA Framework的分布式锁设计

完成

分布式锁是特定于完成的,现在MasaFramework供给了两个完成,分别是LocalMedallion,下面会介绍怎么配置并运用它们

  • 本地锁
  • Medallion
    • Azure
    • FileSystem
    • MySql
    • Oracle
    • PostgreSql
    • Redis
    • SqlServer
    • WaitHandles
    • ZooKeeper

本地锁

是基于SemaphoreSlim完成的,它不是真正的分布式锁,咱们主张你在开发和测试环境中运用它,不需要联网也不会与其他人抵触

Medallion

是基于DistributedLock完成的分布式锁,它供给了很多种技能的完成,包含Microsoft SQL ServerPostgresqlMySQL 或 MariaDBOracleRedisAzure blobApache ZooKeeper锁文件操作体系大局WaitHandles(Windows),咱们只需要任选一种完成即可,现在Medallion供给的分布式锁并不支持可重入性,点击了解原因

快速入门

  • 装置.NET 6.0

以本地锁单应用锁为例:

  1. 新建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
  1. 注册锁,修改类Program
builder.Services.AddLocalDistributedLock();//注册本地锁
  1. 怎么运用锁?修改类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注册锁即可,在运用锁上是没有区别的

怎么扩展其它的分布式锁

  1. 新建类库Masa.Contrib.Data.DistributedLock.{分布式锁名},并增加引用Masa.BuildingBlocks.Data.csproj

  2. 新建分布式锁完成类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();
    }
}
  1. 新建类ServiceCollectionExtensions,注册分布式锁到服务调集
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDistributedLock(this IServiceCollection services, Action<MedallionBuilder> builder)
    {
        services.TryAddSingleton<IDistributedLock, DefaultDistributedLock>();
        return services;
    }
}

小常识

为什么TryGetTryGetAsync办法的回来类型分别是IDisposableIAsyncDisposable

咱们希望运用锁能够满足的简略,在运用完锁之后能够自动开释锁,而不是有必要手动开释,当回来类型为IDisposableIAsyncDisposable时,运用结束后会触发DisposeDisposeAsync,这样一来就能够使得开发者能够疏忽开释锁的逻辑

以本地锁为例:

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

github.com/zhenlei520/…

开源地址

MASA.Framework:github.com/masastack/M…

假如你对咱们的 MASA Framework 感兴趣,无论是代码贡献、运用、提 Issue,欢迎联络咱们

  • WeChat:MasaStackTechOps
  • QQ:7424099