分布式锁的核心其实就是采用一个集中式的服务,然后多个应用节点进行抢占式锁定来进行实现,今天介绍如何采用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

自己动手基于 Redis 实现一个 .NET 的分布式锁的更多相关文章

  1. 【spring boot】【redis】spring boot基于redis的LUA脚本 实现分布式锁

    spring boot基于redis的LUA脚本 实现分布式锁[都是基于redis单点下] 一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁 1.pom.xml &l ...

  2. 基于redis集群实现的分布式锁,可用于秒杀商品的库存数量管理,有測试代码(何志雄)

    转载请标明出处. 在分布式系统中,常常会出现须要竞争同一资源的情况,本代码基于redis3.0.1+jedis2.7.1实现了分布式锁. redis集群的搭建,请见我的另外一篇文章:<>& ...

  3. 【连载】redis库存操作,分布式锁的四种实现方式[四]--基于Redis lua脚本机制实现分布式锁

    一.redis lua介绍 Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向 ...

  4. 使用redis设计一个简单的分布式锁

    最近看了有关redis的一些东西,了解了redis的一下命令,就记录一下: redis中的setnx命令: 关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我 ...

  5. 基于redis集群实现的分布式锁,可用于秒杀,定时器。

    在分布式系统中,经常会出现需要竞争同一资源的情况,使用redis可以实现分布式锁. 前提:redis集群已经整合项目,并且可以直接注入JedisCluster使用: @Autowired privat ...

  6. java-spring基于redis单机版(redisTemplate)实现的分布式锁+redis消息队列,可用于秒杀,定时器,高并发,抢购

    此教程不涉及整合spring整合redis,可另行查阅资料教程. 代码: RedisLock package com.cashloan.analytics.utils; import org.slf4 ...

  7. Redis中是如何实现分布式锁的?

    分布式锁常见的三种实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 本地面试考点是,你对Redis使用熟悉吗?Redis中是如何实现分布式锁的. 要点 Red ...

  8. python使用redis实现协同控制的分布式锁

    python使用redis实现协同控制的分布式锁 上午的时候,有个腾讯的朋友问我,关于用zookeeper分布式锁的设计,他的需求其实很简单,就是节点之间的协同合作. 我以前用redis写过一个网络锁 ...

  9. Redis的“假事务”与分布式锁

    关注公众号:CoderBuff,回复"redis"获取<Redis5.x入门教程>完整版PDF. <Redis5.x入门教程>目录 第一章 · 准备工作 第 ...

  10. 使用数据库、Redis、ZK分别实现分布式锁!

    分布式锁三种实现方式: 基于数据库实现分布式锁: 基于缓存(Redis等)实现分布式锁: 基于Zookeeper实现分布式锁: 基于数据库实现分布式锁 悲观锁 利用select - where - f ...

随机推荐

  1. 常见Content-Type(MIME)列表

    Content-Type(MIME)用于标识发送或接收数据的类型,浏览器根据该参数来决定数据的打开方式.多用于指定一些客户端自定义的文件,以及一些媒体文件的打开方式. 文件扩展名 Content-Ty ...

  2. 2_JDBC

    一. 引言 1.1 如何操作数据库 使用客户端工具访问数据库, 需要手工建立连接, 输入用户名和密码登陆, 编写SQL语句, 点击执行, 查看操作结果(结果集或受行数影响) 1.2 实际开发中, 会采 ...

  3. vue2.x引入threejs

    @ 目录 vue2.x引入threejs npm安装 使用指定版本: 其他插件 实例 强调 vue2.x引入threejs npm安装 npm install three 使用指定版本: npm in ...

  4. 驱动开发:内核取ntoskrnl模块基地址

    模块是程序加载时被动态装载的,模块在装载后其存在于内存中同样存在一个内存基址,当我们需要操作这个模块时,通常第一步就是要得到该模块的内存基址,模块分为用户模块和内核模块,这里的用户模块指的是应用层进程 ...

  5. win7升级到win10系统后,node13升级为node16,node版本node-sass版本与不匹配,导致出现npm ERR! ERESOLVE could not resolve

    1. 错误npm ERR! code ERESOLVE 系统从win7升级到win10,之前的node版本是13.14.0,现在版本是16.17.1.正常的vue程序无法正常运行.从网上查询得知&qu ...

  6. Docker 部署 Kibana

    Docker 部署 Kibana 本篇主要介绍 使用 Docker 部署 kibana 用于操作 Elasticsearch 使用. 1. 前置准备 1.1 Elasticsearch 准备 可以先准 ...

  7. 驱动开发:内核通过PEB得到进程参数

    PEB结构(Process Envirorment Block Structure)其中文名是进程环境块信息,进程环境块内部包含了进程运行的详细参数信息,每一个进程在运行后都会存在一个特有的PEB结构 ...

  8. A-卷积网络压缩方法总结

    卷积网络的压缩方法 一,低秩近似 二,剪枝与稀疏约束 三,参数量化 四,二值化网络 五,知识蒸馏 六,浅层网络 我们知道,在一定程度上,网络越深,参数越多,模型越复杂,其最终效果越好.神经网络的压缩算 ...

  9. 什么是齐博/齐博CMS之X1?

    齐博x1:核心+模块+插件+钩子的理念把系统的灵活性及拓展性做到了极致!!!齐博X1是齐博软件基于thinkphp5开发的内容管理系统,拓展性非常强,后台一键升级,后台提供丰富的频道模块云市插件市场. ...

  10. 知识图谱-生物信息学-医学顶刊论文(Bioinformatics-2021)-MSTE: 基于多向语义关系的有效KGE用于多药副作用预测

    MSTE: 基于多向语义关系的有效KGE用于多药副作用预测 论文标题: Effective knowledge graph embeddings based on multidirectional s ...