前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【愚公系列】2023年01月 .NET CORE工具案例-RedLock.net实现分布式锁

【愚公系列】2023年01月 .NET CORE工具案例-RedLock.net实现分布式锁

作者头像
愚公搬代码
发布2023-01-03 08:46:53
4590
发布2023-01-03 08:46:53
举报
文章被收录于专栏:历史专栏历史专栏

文章目录


前言

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。

在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

对于转账、抢购等都会设计分布式锁问题。归根结底是因为并发引起的数据不一致问题,面对并发,我们通常会采用锁来优化。

一、RedLock.net实现分布式锁

1.秒杀业务模拟

1、业务代码

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTest
{
    public class Test
    {

        // 有10个商品库存
        private static int stockCount = 10;

        public bool Buy()
        {
            // 模拟执行的逻辑代码花费的时间
            Thread.Sleep(new Random().Next(100, 500));
            if (stockCount > 0)
            {
                stockCount--;
                return true;
            }
            return false;
        }
    }
}
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
// See https://aka.ms/new-console-template for more information
using ConsoleTest;
using System.Diagnostics;

var test = new Test();

Parallel.For(1, 16, (i) =>
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    var data = test.Buy();
    stopwatch.Stop();
    Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
});
Console.ReadKey();
在这里插入图片描述
在这里插入图片描述

2、执行

在这里插入图片描述
在这里插入图片描述

模拟并行调用 Buy 方法 15 次(内部使用的是线程池,所以 ThreadId 会有重复),实际上只有 10 个库存,返回结果却显示 12 个请求都购买成功了。----超卖问题

2.单机锁解决问题

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
public class Test
{
    // 有10个商品库存
    private static int stockCount = 10;

    private static object obj = new object();

    public bool Buy()
    {
        lock (obj)
        {
            // 模拟执行的逻辑代码花费的时间
            Thread.Sleep(new Random().Next(100, 500));
            if (stockCount > 0)
            {
                stockCount--;
                return true;
            }
            return false;
        }
    }
}
在这里插入图片描述
在这里插入图片描述

从输出结果中可以看出,只有10个商品被抢购成功了,但同时发现部分请求的执行时间明显变长,这就是加锁带来的最直观影响,当某个线程获得锁之后,在没有释放之前,其他线程只能继续等待,并发越高,更多的线程需要等待轮流被处理。

3.分布式锁解决问题

3.1 方案介绍

在集群模式下,系统部署于多台机器(一个系统运行在多个进程中),语言本身实现的锁只能确保当前进程内有效(基于内存),多进程就没办法共享锁状态,这时我们就得考虑采用分布式锁,分布式锁可以采用数据库、ZooKeeper、Redis等来实现,最终都是为了达到在不同的进程、线程内能共享锁状态的目的。

这里将介绍基于 Redis 的 RedLock.net 来解决分布式下的并发问题,RedLock.net 是 RedLock 分布式锁算法的 .NET 版实现。

3.2 RedLock的概念

RedLock 官网:https://redis.io/docs/manual/patterns/distributed-locks/

在这里插入图片描述
在这里插入图片描述

RedLock 的思想是使用多台 Redis Master ,节点完全独立,节点间不需要进行数据同步,因为 Master-Slave 架构一旦 Master 发生故障时数据没有复制到 Slave,被选为 Master 的 Slave 就丢掉了锁,另一个客户端就可以再次拿到锁。锁通过 setNX(原子操作) 命令设置,在有效时间内当获得锁的数量大于 (n/2+1) 代表成功,失败后需要向所有节点发送释放锁的消息。

1、设置锁时,使用set命令,因为其包含了setnx,expire的功能,起到了原子操作的效果,给key设置随机值,并且只有在key不存在时才设置成功返回True,并且设置key的过期时间(最好用毫秒)

代码语言:javascript
复制
SET resource_name my_random_value NX PX 30000

2、在获取锁后,并完成相关业务后,需要删除自己设置的锁(必须是只能删除自己设置的锁,不能删除他人设置的锁);

删除原因:保证服务器资源的高利用效率,不用等到锁自动过期才删除;

删除方法:最好使用Lua脚本删除(redis保证执行此脚本时不执行其他操作,保证操作的原子性),代码如下;逻辑是 先获取key,如果存在并且值是自己设置的就删除此key;否则就跳过;

代码语言:javascript
复制
if redis.call("get",KEYS[1]) == ARGV[1] then
  return redis.call("del",KEYS[1])
else
  return 0
end

3.2 RedLock.net的概念

RedLock.net 官网:https://github.com/samcook/RedLock.net

在这里插入图片描述
在这里插入图片描述

1、安装RedLock.net包

在这里插入图片描述
在这里插入图片描述

2、appsettings.json 添加 redis 配置

在这里插入图片描述
在这里插入图片描述

3、添加 ProductService.cs,模拟商品购买

代码语言:javascript
复制
public class ProductService
{
    // 有10个商品库存,如果同时启动多个API服务进行测试,这里改成存数据库或其他方式
    private static int stockCount = 10;
    public async Task<bool> BuyAsync()
    {
        // 模拟执行的逻辑代码花费的时间
        await Task.Delay(new Random().Next(100, 500));
        if (stockCount > 0)
        {
            stockCount--;
            return true;
        }
        return false;
    }
}
在这里插入图片描述
在这里插入图片描述

4、创建 RedLockFactory

代码语言:javascript
复制
using RedLockNet;
using RedLockNet.SERedis;
using RedLockNet.SERedis.Configuration;
using System.Net;
using WebApiTest;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

#region 创建分布式锁
//创建分布式锁
var redisUrl = builder.Configuration["RedisUrl"];
if (string.IsNullOrEmpty(redisUrl))
{
    throw new ArgumentException("RedisUrl 不能为空");
}
var urls = redisUrl.Split(",").ToList();
var endPoints = new List<RedLockEndPoint>();
foreach (var item in urls)
{
    var arr = item.Split(":");
    endPoints.Add(new DnsEndPoint(arr[0], Convert.ToInt32(arr[1])));
}
RedLockFactory lockFactory =RedLockFactory.Create(endPoints);

//注入锁和服务
builder.Services.AddSingleton(typeof(IDistributedLockFactory), lockFactory);
builder.Services.AddScoped(typeof(ProductService));
#endregion

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

#region 应用生命周期释放分布式锁
app.Lifetime.ApplicationStopping.Register(() =>
{
    lockFactory.Dispose();
});
#endregion
在这里插入图片描述
在这里插入图片描述

5、在HomeController添加方法DistributedLockTest

代码语言:javascript
复制
[ApiController]
[Route("[controller]/[action]")]
public class HomeController : ControllerBase
{
    private readonly IDistributedLockFactory _distributedLockFactory;
    private readonly ProductService _productService;

    public HomeController(IDistributedLockFactory distributedLockFactory,
      ProductService productService)
    {
        _distributedLockFactory = distributedLockFactory;
        _productService = productService;
    }

    [HttpGet]
    public async Task<bool> DistributedLockTest()
    {
        var productId = "id";
        // resource 锁定的对象
        // expiryTime 锁定过期时间,锁区域内的逻辑执行如果超过过期时间,锁将被释放
        // waitTime 等待时间,相同的 resource 如果当前的锁被其他线程占用,最多等待时间
        // retryTime 等待时间内,多久尝试获取一次
        using (var redLock = await _distributedLockFactory.CreateLockAsync(productId, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(20)))
        {
            if (redLock.IsAcquired)
            {
                var result = await _productService.BuyAsync();
                return result;
            }
            else
            {
                Console.WriteLine($"获取锁失败:{DateTime.Now}");
            }
        }
        return false;
    }
}
在这里插入图片描述
在这里插入图片描述

启动程序

在这里插入图片描述
在这里插入图片描述

6、调用接口

代码语言:javascript
复制
// See https://aka.ms/new-console-template for more information
using ConsoleTest;
using System.Diagnostics;

var test = new Test();
using (var httpClient = new HttpClient())
{
    Parallel.For(1, 50, (i) =>
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();

        var data = httpClient.GetAsync($"https://localhost:7002/Home/DistributedLockTest").Result.Content.ReadAsStringAsync().Result;
        stopwatch.Stop();
        Console.WriteLine($"ThreadId:{Thread.CurrentThread.ManagedThreadId}, Result:{data}, Time:{stopwatch.ElapsedMilliseconds}");
    });
}


Console.ReadKey();
在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-01-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、RedLock.net实现分布式锁
    • 1.秒杀业务模拟
      • 2.单机锁解决问题
        • 3.分布式锁解决问题
          • 3.1 方案介绍
          • 3.2 RedLock的概念
          • 3.2 RedLock.net的概念
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档