首页 > 数据库 >自己动手基于 Redis 实现一个 .NET 的分布式锁类库

自己动手基于 Redis 实现一个 .NET 的分布式锁类库

时间:2022-12-09 12:12:07浏览次数:86  
标签:类库 300 Redis expiry 获取 using NET public

分布式锁的核心其实就是采用一个集中式的服务,然后多个应用节点进行抢占式锁定来进行实现,今天介绍如何采用Redis作为基础服务,实现一个分布式锁的类库,本方案不考虑 Redis 集群多节点问题,如果引入集群多节点问题,会导致解决成本大幅上升,因为 Redis 单节点就可以很容易的处理10万并发量了,这对于日常开发中 99% 的项目足够使用了。

目标如下:

  1. 支持 using 语法,出 using 范围之后自动释放锁
  2. 支持 尝试行为,如果锁获取不到则直接跳过不等待
  3. 支持 等待行为,如果锁获取不到则持续等待直至超过设置的等待时间
  4. 支持信号量控制,实现一个锁可以同时获取到几次,方便对一些方法进行并发控制

代码整体结构图


创建 DistributedLock 类库,然后定义接口文件 IDistributedLock ,方便我们后期扩展其他分布式锁的实现。

namespace DistributedLock
{
    public interface IDistributedLock
    {

        /// <summary>
        /// 获取锁
        /// </summary>
        /// <param name="key">锁的名称,不可重复</param>
        /// <param name="expiry">失效时长</param>
        /// <param name="semaphore">信号量</param>
        /// <returns></returns>
        public IDisposable Lock(string key, TimeSpan expiry = default, int semaphore = 1);

        /// <summary>
        /// 尝试获取锁
        /// </summary>
        /// <param name="key">锁的名称,不可重复</param>
        /// <param name="expiry">失效时长</param>
        /// <param name="semaphore">信号量</param>
        /// <returns></returns>
        public IDisposable? TryLock(string key, TimeSpan expiry = default, int semaphore = 1);

    }
}

创建 DistributedLock.Redis 类库,安装下面两个 Nuget 包

StackExchange.Redis
Microsoft.Extensions.Options

定义配置模型 RedisSetting

namespace DistributedLock.Redis.Models
{
    public class RedisSetting
    {
        public string Configuration { get; set; }

        public string InstanceName { get; set; }
    }
}

定义 RedisLockHandle

using StackExchange.Redis;

namespace DistributedLock.Redis
{
    public class RedisLockHandle : IDisposable
    {

        public IDatabase Database { get; set; }

        public string LockKey { get; set; }

        public void Dispose()
        {
            try
            {
                Database.LockRelease(LockKey, "123456");
            }
            catch
            {
            }

            GC.SuppressFinalize(this);
        }
    }
}

实现 RedisLock

using DistributedLock.Redis.Models;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System.Security.Cryptography;
using System.Text;

namespace DistributedLock.Redis
{
    public class RedisLock : IDistributedLock
    {

        private readonly ConnectionMultiplexer connectionMultiplexer;

        private readonly RedisSetting redisSetting;

        public RedisLock(IOptionsMonitor<RedisSetting> config)
        {
            connectionMultiplexer = ConnectionMultiplexer.Connect(config.CurrentValue.Configuration);
            redisSetting = config.CurrentValue;
        }


        /// <summary>
        /// 获取锁
        /// </summary>
        /// <param name="key">锁的名称,不可重复</param>
        /// <param name="expiry">失效时长</param>
        /// <param name="semaphore">信号量</param>
        /// <returns></returns>
        public IDisposable Lock(string key, TimeSpan expiry = default, int semaphore = 1)
        {

            if (expiry == default)
            {
                expiry = TimeSpan.FromMinutes(1);
            }

            var endTime = DateTime.UtcNow + expiry;

            RedisLockHandle redisLockHandle = new();

        StartTag:
            {
                for (int i = 0; i < semaphore; i++)
                {
                    var keyMd5 = redisSetting.InstanceName + Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(key + i)));

                    try
                    {
                        var database = connectionMultiplexer.GetDatabase();

                        if (database.LockTake(keyMd5, "123456", expiry))
                        {
                            redisLockHandle.LockKey = keyMd5;
                            redisLockHandle.Database = database;
                            return redisLockHandle;
                        }
                    }
                    catch
                    {

                    }
                }


                if (redisLockHandle.LockKey == default)
                {

                    if (DateTime.UtcNow < endTime)
                    {
                        Thread.Sleep(1000);
                        goto StartTag;
                    }
                    else
                    {
                        throw new Exception("获取锁" + key + "超时失败");
                    }
                }
            }

            return redisLockHandle;
        }


        public IDisposable? TryLock(string key, TimeSpan expiry = default, int semaphore = 1)
        {

            if (expiry == default)
            {
                expiry = TimeSpan.FromMinutes(1);
            }


            for (int i = 0; i < semaphore; i++)
            {
                var keyMd5 = redisSetting.InstanceName + Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(key + i)));

                try
                {
                    var database = connectionMultiplexer.GetDatabase();

                    if (database.LockTake(keyMd5, "123456", expiry))
                    {
                        RedisLockHandle redisLockHandle = new()
                        {
                            LockKey = keyMd5,
                            Database = database
                        };
                        return redisLockHandle;
                    }
                }
                catch
                {
                }
            }
            return null;

        }
    }
}

定义 ServiceCollectionExtensions

using DistributedLock.Redis.Models;
using Microsoft.Extensions.DependencyInjection;

namespace DistributedLock.Redis
{
    public static class ServiceCollectionExtensions
    {
        public static void AddRedisLock(this IServiceCollection services, Action<RedisSetting> action)
        {
            services.Configure(action);
            services.AddSingleton<IDistributedLock, RedisLock>();
        }
    }
}

使用时只要在配置文件中加入 redis 连接字符串信息,然后注入服务即可。
appsettings.json

{
  "ConnectionStrings": {
    "redisConnection": "127.0.0.1,Password=123456,DefaultDatabase=0"
  }
}

注入示例代码:

//注册分布式锁 Redis模式
builder.Services.AddRedisLock(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("redisConnection")!;
    options.InstanceName = "lock";
});

使用示例

using DistributedLock;
using Microsoft.AspNetCore.Mvc;

namespace WebAPI.Controllers
{

    [Route("[controller]")]
    [ApiController]
    public class DemoController : ControllerBase
    {


        private readonly IDistributedLock distLock;

        public DemoController(IDistributedLock distLock)
        {
            this.distLock = distLock;
        }


        [HttpGet("Test")]
        public void Test()
        {

            //锁定键只要是一个字符串即可,可以简单理解为锁的标识名字,可以是用户名,用户id ,订单id 等等,根据业务需求自己定义
            string lockKey = "xx1";


            using (distLock.Lock(lockKey))
            {
                //代码块同时只有一个请求可以进来执行,其余没有获取到锁的全部处于等待状态
                //锁定时常1分钟,1分钟后无论代码块是否执行完成锁都会被释放,同时等待时常也为1分钟,1分钟后还没有获取到锁,则抛出异常
            }


            using (distLock.Lock(lockKey, TimeSpan.FromSeconds(300)))
            {
                //代码块同时只有一个请求可以进来执行,其余没有获取到锁的全部处于等待状态
                //锁定时常300秒,300秒后无论代码块是否执行完成锁都会被释放,同时等待时常也为300秒,300秒后还没有获取到锁,则抛出异常
            }


            using (distLock.Lock(lockKey, TimeSpan.FromSeconds(300), 5))
            {
                //代码块同时有五个请求可以进来执行,其余没有获取到锁的全部处于等待状态
                //锁定时常300秒,300秒后无论代码块是否执行完成锁都会被释放,同时等待时常也为300秒,300秒后还没有获取到锁,则抛出异常

                //该代码块有5个请求同时拿到锁,签发出去的5把锁,每把锁的时间都是单独计算的,并非300秒后 5个锁会全部同时释放,可能只会释放 2个或3个,释放之后心的请求又可以获取到,总之最多只有5个请求可以进入
            }


            var lockHandle1 = distLock.TryLock(lockKey);

            if (lockHandle1 != null)
            {
                //代码块同时只有一个请求可以进来执行,其余没有获取到锁的直接为 null 不等待,也不执行
                //锁定时常1分钟,1分钟后无论代码块是否执行完成锁都会被释放
            }

            var lockHandle2 = distLock.TryLock(lockKey, TimeSpan.FromSeconds(300));

            if (lockHandle2 != null)
            {
                //代码块同时只有一个请求可以进来执行,其余没有获取到锁的直接为 null 不等待,也不执行
                //锁定时常300秒,300秒后无论代码块是否执行完成锁都会被释放
            }


            var lockHandle3 = distLock.TryLock(lockKey, TimeSpan.FromSeconds(300), 5);

            if (lockHandle3 != null)
            {
                //代码块同时有五个请求可以进来执行,其余没有获取到锁的直接为 null 不等待,也不执行
                //锁定时常300秒,300秒后无论代码块是否执行完成锁都会被释放

                //该代码块有5个请求同时拿到锁,签发出去的5把锁,每把锁的时间都是单独计算的,并非300秒后 5个锁会全部同时释放,可能只会释放 2个或3个,释放之后心的请求又可以获取到,总之最多只有5个请求可以进入
            }
        }

    }
}

至此 关于 自己动手基于 Redis 实现一个 .NET 的分布式锁类库 就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流,有兴趣的朋友可以关注我目前在维护的一个 .NET 基础框架项目,项目地址如下
https://github.com/berkerdong/NetEngine.git
https://gitee.com/berkerdong/NetEngine.git

标签:类库,300,Redis,expiry,获取,using,NET,public
From: https://www.cnblogs.com/berkerdong/p/16968339.html

相关文章

  • 记.net framework php接口 返回数据格式问题 请求接口远程服务器返回错误: (500) 内
    .netframework框架请求时候php接口这边返回exit(json_encode(['code'=>200,'data'=>$tokenData]));.net报错 请求接口远程服务器返回错误:(500)内部服务器错误而......
  • asp.net 大文件分片上传处理
    ​ 前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好......
  • redis 状态 跟踪 参数值
    1查看客户端连接信息通过执行clientlist命令来查看客户端连接信息,每行都代表一个客户端127.0.0.1:6379>clientlistid=3addr=127.0.0.1:58752fd=7name=age=19951id......
  • Redis配置、优化以及相命令
    一、关系数据库和非关系型数据库1、关系型数据库关系型数据库是一个结构化的数据库,创建在关系模型(二维表格模型)基础上,一般面向于记录。SQL语句(标准数据查询语言)就是一......
  • Redis 高级使用
    生成全局idid使用Long类型,8个字节,64bitID的组成部分:符号位:1bit,永远为0时间戳:31bit,以秒为单位,可以使用69年序列号:32bit,秒内的计数器,支持每秒产生2^32个不......
  • Neural Network-神经网络算法本质
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • MySQL增强半同步复制执行net_flush()失败
    现象目前线上有套基于MySQL8.0.26做的增强半同步主从复制数据库,查看log_errorr发现有部分net_flush()执行失败的报错:2021-12-28T14:04:24.663005+08:0011[ERROR][MY-......
  • 什么是redis的hash记录(key)类型?
    redis中的hash记录类型是什么? redishash是一种记录的类型,或者说是数据类型。 存储的是:field-value(字段-值)对的集合。 比如:一个用户的,姓名-张三,地址-北京xx,年......
  • 【论文精读9】MVSNet系列论文详解-AA-RMVSNet
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • WinUI(WASDK)使用MediaPipe检查手部关键点并通过ML.NET进行手势分类
    前言之所以会搞这个手势识别分类,其实是为了满足之前群友提的需求,就是针对稚晖君的ElectronBot机器人的上位机软件的功能丰富,因为本来擅长的技术栈都是.NET,也刚好试试全能......