2

MasaFramework -- 锁与分布式锁 - 磊_磊

 1 year ago
source link: https://www.cnblogs.com/zhenlei520/p/16694025.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MasaFramework -- 锁与分布式锁

什么是锁?什么是分布式锁?它们之间有什么样的关系?

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

public class Program
{
    private static readonly object Obj = new { };

    public static void Main()
    {
        lock (obj)
        {
            //同一时刻只有一个线程可以访问
        }
    }
}

什么是分布式锁

但随着业务发展的需要,原单体单机部署的系统被部署成分布式集群系统后,原来的并发控制策略失效,为了解决这个问题就需要引入分布式锁,那分布式锁应该具备哪些条件?

  • 原子性:在分布式环境下,一个方法在同一个时间点只能被一台机器下的一个线程所执行,防止数据资源的并发访问,避免数据不一致情况
  • 高可用:具备自动失效机制,防止死锁,获取锁后如果出现错误,并且无法释放锁,则使用租约一段时间后自动释放锁
  • 阻塞性:具备非阻塞锁特性(没有获取到锁时直接返回获取锁失败,不会长时间因等待锁导致阻塞)
  • 高性能:高性能的获取锁与释放锁
  • 可重入性:具备可重入特性,在同一线程外层函数获得锁之后,内层方法会自动获取锁

VITwhafQNtvBzHF.png

分布式锁是特定于实现的,目前MasaFramework提供了两个实现,分别是LocalMedallion,下面会介绍如何配置并使用它们

是基于SemaphoreSlim实现的,它不是真正的分布式锁,我们建议你在开发和测试环境中使用它,不需要联网也不会与其他人冲突

Medallion

是基于DistributedLock实现的分布式锁,它提供了很多种技术的实现,包括Microsoft SQL ServerPostgresqlMySQL 或 MariaDBOracleRedisAzure blobApache ZooKeeper锁文件操作系统全局WaitHandles(Windows),我们只需要任选一种实现即可,目前Medallion提供的分布式锁并不支持可重入性,点击了解原因

本地锁单应用锁为例:

  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

https://github.com/zhenlei520/MasaFramework.Practice

MASA.Framework:https://github.com/masastack/MASA.Framework

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

16373211753064.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK