前言

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

什么是锁

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

public class Program
{
private static readonly object Obj = new { }; public static void Main()
{
lock (obj)
{
//同一时刻只有一个线程可以访问
}
}
}

什么是分布式锁

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

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

实现

分布式锁是特定于实现的,目前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,欢迎联系我们

MasaFramework -- 锁与分布式锁的更多相关文章

  1. 锁、分布式锁、无锁实战全局性ID

    1.为什么要使用锁 当发生并发时,会产生多线程争夺一个资源,为保证资源的唯一性. JVM锁:对象锁,死锁,重入锁,公平锁,偏向锁 分布式锁:数据库 nosql .zookeeper 面试题:如何排查死 ...

  2. Springboot分别使用乐观锁和分布式锁(基于redisson)完成高并发防超卖

    原文 :https://blog.csdn.net/tianyaleixiaowu/article/details/90036180 乐观锁 乐观锁就是在修改时,带上version版本号.这样如果试图 ...

  3. redis 加锁与释放锁(分布式锁1)

    使用Redis的 SETNX 命令可以实现分布式锁 SETNX key value 返回值 返回整数,具体为 - 1,当 key 的值被设置 - 0,当 key 的值没被设置 分布式锁使用 impor ...

  4. redis 加锁与释放锁(分布式锁)

    使用Redis的 SETNX 命令可以实现分布式锁 SETNX key value 返回值 返回整数,具体为 - 1,当 key 的值被设置 - 0,当 key 的值没被设置

  5. Java锁?分布式锁?乐观锁?行锁?

    转载自:公众号来源:码农翻身 作者:刘欣 Tomcat的锁 Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来处理,有的执行登录,有的查看购物车,有的下订 ...

  6. 利用MySQL中的乐观锁和悲观锁实现分布式锁

    背景 对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙. 下面就一个小例子,针对不加锁.乐观锁以及悲观锁这三种方式来实现. 主要是一个用户表,它有一个年龄的字段,然后并发地对其 ...

  7. .net 分布式架构之分布式锁实现

    分布式锁 经常用于在解决分布式环境下的业务一致性和协调分布式环境. 实际业务场景中,比如说解决并发一瞬间的重复下单,重复确认收货,重复发现金券等. 使用分布式锁的场景一般不能太多. 开源地址:http ...

  8. Redis分布式锁服务(八)

    阅读目录: 概述 分布式锁 多实例分布式锁 总结 概述 在多线程环境下,通常会使用锁来保证有且只有一个线程来操作共享资源.比如: object obj = new object(); lock (ob ...

  9. zookeeper分布式锁原理

    一.分布式锁介绍分布式锁主要用于在分布式环境中保护跨进程.跨主机.跨网络的共享资源实现互斥访问,以达到保证数据的一致性. 二.架构介绍在介绍使用Zookeeper实现分布式锁之前,首先看当前的系统架构 ...

随机推荐

  1. 基于UniApp社区论坛多端开发实战

    什么是移动端WebApp 移动端WebApp: 泛指手持设备移动端的web 特点: - 类App 应用,运行环境是浏览器 - 可以包一层壳,成为App - 常见的混合应用: ionic, Cordov ...

  2. pop!_OS换国内源

    今天给电脑换源了,虽然本来的源大部分好像也都连的上(不知道是不是错觉)换的这个:阿里云镜像开源站 先进入存放源的目录:` cd /etc/apt 里面有这些文件: sources.list是要修改的, ...

  3. IO多路复用epoll

    0 why: 问题来源 0.1 网络编程流程 //创建socket int s = socket(AF_INET, SOCK_STREAM, 0); //绑定IP地址和端口号port bind(s, ...

  4. 漏洞扫描工具nessus、rapid7 insightvm、openvas安装&简单使用

    Rapid7-insightvm 申请试用 申请地址 邮件地址不能用常用邮件,要使用自己域名的邮件,可以使用这个临时邮箱 手机号随便输入,10位以上 提交后会跳转下载页面 安装 安装:./Rapid7 ...

  5. C# MVCapi跨域问题

     he 'Access-Control-Allow-Origin' header contains multiple values ', *', but only one is allowed. Or ...

  6. Windows 下如何调试 PowerShell

    背景 最近在用 PowerShell 的时候,发现一些地方特别有意思.于是就萌生了看源代码的想法,单看肯定不过瘾,调试起来才有意思.于是就有了这个,记录下. 调试 PowerShell 主要分为两种方 ...

  7. day02_基本语法

    基本语法 学习目标: 1. 数据类型 2. 变量 3. 编码介绍 4. 标识符和关键字 5. 字符串类型 6. 数据类型转化 7. 进制转换 8. 运算符 一.数据类型 什么是数据类型? 在开发软件的 ...

  8. python sphinx(文档生成器)入门

    简介 Sphinx 是一个 文档生成器 ,您也可以把它看成一种工具,它可以将一组纯文本源文件转换成各种输出格式,并且自动生成交叉引用.索引等.也就是说,如果您的目录包含一堆 reStructuredT ...

  9. github访问较慢问题初步解决方案

    简介 众所周知,github在国内向来都是访问的非常坎坷,不挂代理访问起来非常困难,本文将介绍一种不依赖代理的加快访问的方式,但不一定可行哦,只能说是优化 步骤 1.在搜索框中输入记事本或者notep ...

  10. 什么是WordPress

    首先,假设您没有WordPress的经验: 我将从基础开始. 在本教程中,我将回答问题:"什么是WordPress?" 在这篇文章中,我将说明您可以在哪里获得WordPress以及 ...