.NetCore手写一个 API 限流组件

news2025/1/23 11:23:33

首先如果APP 拥有游客模式,用户模式以及其他特殊权限。那就意味着需要 IP 限流、用户限流以及特殊权限的情况。

那我们直接实操一下,以 IP 限流作为参考案例,当然要以组件的形式编写,支持扩展。

首先我们创建一个抽象类接口,定义一些限流行为和属性,我们需要针对限流的最小的单位,比如 IP、账号、设备号或者其他。使其每一个流量进来都需要记录访问者信息并且检查是否被限流。

public interface IRateLimiting
{
    //限流唯一键
    string Key { get; }
    // 访问+1
    void Visit();
    //检查是否限流
    bool Check();
}

上面定义的这个对象,只是一些简约的处理限流的行为,在我们面对复杂多变的业务场景时,IRateLimiting 不一定能够满足我们,在面对持续变化的业务,我们最好不要直接在这个对象里进行更改,而是新增加一个新的对象。

比如 ICodeRateLimiting,或者 IAccountRateLimiting 这种根据授权码,账号来满足。

但是对象一旦多起来了,我们总是需要使用它们,也就是实例化,我们能不能使用一个创建者来管理他们,我们需要哪个对象,就像创建者要就行。

public interface IRateLimitingCreator
    {
        /// <summary>
        /// 创建限流对象
        /// </summary>
        /// <param name="context">请求信息</param>
        /// <param name="max">最大次数</param>
        IRateLimitingInfo Create(HttpContext context, int max);
    }

将 IRateLimitingInfo 对象通过 Create 创建,接收 HttpContext 和 max 参数,分别是用户请求过来的上下文,里面包含了用户的信息,和另外一个参数 max ,告诉我们此次限流的最大是多少啊。

其次,我们需要一个处理执行者,来执行 IRateLimiting 对象信息。

这个执行者需要针对每一个流程进来时候更新信息到存储并且告诉我们是否被限流了。

public interface IRateLimitingExecute
{
    /// <summary>
    /// 更新限流信息并返回是否需要限流
    /// </summary>
    bool UpdateAndCheck(IRateLimitingInfo info);
}

接下来我们就要来实现 IRateLimiting 这个接口需要做的内容了,为了保持足够的扩展性,我们使用 abstract 来声明抽象类,比如说我实现了一套 IRateLimiting 通用的逻辑,你想要在我的基础之上进行修改符合自己业务的逻辑,就可以基础我的 abstract 类来进行扩展。

以上 RateLimiting 对象实现了 IRateLimiting 抽象接口要做的内容。

它记录了上次的信息和本次的信息,并且检查是否限流。

public abstract class RateLimiting : IRateLimiting
    {
        /// <summary>
        /// 当前请求次数
        /// </summary>
        private int _current_times;
        /// <summary>
        /// 当前值
        /// </summary>
        private int _current_value;
        /// <summary>
        /// 上次检测结果
        /// </summary>
        private int _last_times;

        /// <summary>
        /// 上一次请求时间
        /// </summary>
        private DateTime _lasttime = DateTime.Now;

        /// <summary>
        /// 访问量上限
        /// </summary>
        private int _limit;

        /// <summary>
        /// 当前key
        /// </summary>
        private string _key;

        /// <summary>
        /// 当前key
        /// </summary>
        public string Key => _key;

        public RateLimitingInfo(string key, int limit = 1200)
        {
            _limit = limit;
            _key = key;
        }

        /// <summary>
        /// 判断是否需要限流
        /// </summary>
        /// <returns>true:限流,false:不限流</returns>
        public bool Check()
        {
            if (_last_times <= _limit)
            {
                return _current_times <= _limit;
            }
            return false;
        }

        /// <summary>
        /// 当前访问次数+1
        /// </summary>
        public void Visit()
        {
            int currentValue = GetCurrentValue();
            if (_current_value != currentValue)
            {
                _last_times = _current_times;
                _current_value = currentValue;
                _current_times = 1;
            }
            else
            {
                Interlocked.Increment(ref _current_times);
            }
        }
        /// <summary>
        /// 获取当前时间范围值
        /// </summary>
        public abstract int GetCurrentValue();
    }

比如我突然有一个业务需求就是,我想要通过按分钟来限流,每分钟单个 IP 限流 100 次。  

那我就可以在抽象类 RateLimiting 基础之上进行实现。

public class MinuteRateLimiting : RateLimiting
    {
        public MinuteRateLimiting(string key, int limit = 100)
            : base(key, limit)
        {
        }
        /// <summary>
        /// 当前分钟
        /// </summary>
        public override int GetCurrentValue()
        {
            return DateTime.Now.Minute;
        }
    

MinuteRateLimiting 类对抽象类 RateLimiting 的GetCurrentValue 方法进行实现。

按照分钟的维度来限流,同时对 key 和 limit 赋值。

也可以按照小时或天数:HourRateLimiting和DayRateLimiting 来实现。

接下来我们使用IRateLimitingCreator来实例化我们实现的 MinuteRateLimiting 对象。

public class MinuteIpRateLimitingCreator : IRateLimitingCreator
    {
        public IRateLimitingInfo Create(HttpContext context, int max)
        {
            if (max <= 0)
            {
                max = int.MaxValue;
            }
            return new MinuteRateLimitingInfo(context.GetRemoteIp(), max);
        }
    }
public class MinuteIpPathRateLimitingCreator : IRateLimitingCreator
    {
        //创建对象
        public IRateLimitingInfo Create(HttpContext context, int max)
        {
            if (max <= 0)
            {
                max = int.MaxValue;
            }
            return new MinuteRateLimitingInfo($"{context.GetRemoteIp()}:{context.GetCurrentClientId()}:{context.Request.Path}", max);
        }
    }

以上给了两个实现方式。分别是按照 IP 进行限流,或者按照请求的IP+ 请求ID+请求接口的路径来限流。

以上功能的整体就是,每个 IP 对应客户端 ID 针对某个请求路径,没分钟最大允许 {max} 次,超过即为启动限流。

在面对每次进来的流量时,我们总是需要记录这些流量,一般可以采用数据库记录,Redis,MemoryCache,本地缓存等等。

但是流量比较庞大的话,库表记录一般不是最佳的方式,更优选则是采用缓存的机制,例如 Redis,MemoryCache 等。

这里为了保证程序的轻量级和演示方便,这里采用 ConcurrentDictionary 线程安全的字典存储,并且允许 2 GB 的存储量。

public class RateLimitingCache : IRateLimiting
    {
        private ConcurrentDictionary<string, IRateLimitingInfo> _cache = new ConcurrentDictionary<string, IRateLimitingInfo>();

        public bool UpdateAndCheck(IRateLimitingInfo info)
        {
            if (_cache.TryGetValue(info.Key, out var temp))
            {
                temp.Visit();
                _cache.AddOrUpdate(info.Key, temp, (string k, IRateLimitingInfo old) => temp);
                return temp.Check();
            }
            _cache.AddOrUpdate(info.Key, info, (string k, IRateLimitingInfo old) => info);
            return true;
        }
    }

ConcurrentDictionary:所有这些操作都是原子操作,都是线程安全的。

唯一的例外是接受委托的方法,即AddOrUpdate和GetOrAdd。

对于对字典的修改和写入操作,ConcurrentDictionary 请使用精细锁定来确保线程安全。字典上的读取操作以无锁方式执行。

但是,这些方法的委托在锁外部调用,以避免在锁下执行未知代码时可能出现的问题。因此,这些委托执行的代码不受操作原子性的约束。

以上内容基本上实现了功能,当然 RateLimitingCache 完全可以按照自己业务方式进行替换方案。

接下来我们将这套限流功能封装为一个组件,并且以中间件的方式进行注入。

public static class RateLimitingHelper
    {
        /// <summary>
        /// 配置默认限流
        /// 默认使用按每分钟ip访问次数进行限制
        /// </summary>
        public static void AddAIpRateLimiting(this IServiceCollection services, IConfiguration config)
        {
            services.AddDefaultRateLimiting(config);
            services.AddTransient<IRateLimitingCreator, MinuteIpRateLimitingCreator>();
        }

        private static void AddDefaultRateLimiting(this IServiceCollection services, IConfiguration config)
        {
            services.Configure<RateLimitingOption>(config.GetSection("RateLimiting"));
            services.AddSingleton<IRateLimiting, RateLimitingCache>();
        }
    }

这里我们采用每分钟 ip 访问次数进行限制,进行封装。

另外,我们将 max 限流的次数的设置暴露出去进行配置。

public class RateLimitingOption
    {
        /// <summary>
        /// 对应间隔最大的访问次数
        /// </summary>
        public int Times { get; set; } = 500;
    }

我们将其功能以中间件的形式进行配置。

/// <summary>
    /// 限流中间件
    /// </summary>
    public class RateLimitingMiddleware
    {
        /// <summary>
        /// 请求管道
        /// </summary>
        private readonly RequestDelegate _next;
        /// <summary>
        /// 日志记录
        /// </summary>
        private ILogger<RateLimitingMiddleware> _logger;
        /// <summary>
        /// 创建key
        /// </summary>
        private IRateLimitingCreator _creator;
        /// <summary>
        /// 限流接口
        /// </summary>
        private IRateLimiting _ratelimiting;
        /// <summary>
        /// 限流配置
        /// </summary>
        private RateLimitingOption _option;
        /// <summary>
        /// 日志记录中间件,用于记录访问日志
        /// </summary>
        public RateLimitingMiddleware(ILogger<RateLimitingMiddleware> log, IOptions<RateLimitingOption> options, IRateLimitingCreator creator, IRateLimiting ratelimiting, RequestDelegate next)
        {
            _logger = log;
            _next = next;
            _creator = creator;
            _ratelimiting = ratelimiting;
            _option = options.Value;
        }
        /// <summary>
        /// 记录访问日志
        /// 先执行方法,后对执行的结果以及请求信息通过IVisitLogger进行日志记录
        /// </summary>
        public async Task Invoke(HttpContext context)
        {
            IRateLimitingInfo rateLimitingInfo = _creator.Create(context, _option.Times);
            if (!_ratelimiting.UpdateAndCheck(rateLimitingInfo))
            {
                _logger.LogDebug("触发限流:" + rateLimitingInfo.Key);
                context.Response.StatusCode = 429;
            }
            else
            {
                await _next(context);
            }
        }
    }

最后,我们在 service 中注入服务:

//配置限流
services.AddAIpRateLimiting(Configuration);

注入中间件:

app.UseMiddleware<RateLimitingMiddleware>(Array.Empty<object>());

添加 appsettings 配置:

  "RateLimiting": {
    "Times": 5000
  },

到这里,即已经完成手写一套限流组件,为了适应业务的变化,我们的组件也完全适变化进行扩展。

图片

测试允许之后,如果设定时间内,超过了限定次数则接口会返回相应的限制信息。

图片

到这里我们就实现了一个手写且易扩展的 API 限流组件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1214614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

门禁管理超级麻烦,你方式用对了吗?

随着社会的不断进步和科技的飞速发展&#xff0c;安全管理成为我们日常生活和工作中至关重要的一环。在这个背景下&#xff0c;门禁监控系统逐渐崭露头角&#xff0c;成为保障各类场所安全的关键工具。 客户案例 企业办公楼 在现代企业中&#xff0c;保护办公场所的安全至关重…

线程锁的应用与示例代码

为了解决这个问题&#xff0c;可以使用线程锁来确保在提取zip文件中的每个文件时&#xff0c;同一时间只有一个线程可以访问文件。这样可以避免多个线程同时访问和写入文件&#xff0c;从而解决race condition的问题。以下是修改后的示例代码&#xff1a; python import reque…

<C++> 反向迭代器

我们知道正向迭代器的设计&#xff1a;begin迭代器指向第一个数据&#xff0c;end迭代器指向最后一个数据的下一个位置 。移向下一个数据&#xff0c;解引用得到数据的值&#xff0c;并根据容器储存方式的不同&#xff0c;容器有不同类型的迭代器。 注意&#xff1a;rbegin迭代…

Python Flask: 构建轻量级、灵活的Web应用

Flask是一个流行的Python Web框架&#xff0c;以其轻量级、灵活和易学的特性受到开发者的喜爱。本文将深入探讨Flask框架的各个方面&#xff0c;通过详实的示例代码&#xff0c;帮助大家更全面地了解和掌握这一强大的工具。 1. 安装与基本用法 首先&#xff0c;需要安装Flask。…

使用requests库解决Session对象设置超时的问题

在requests库的IRC频道中&#xff0c;提出了一个问题&#xff0c;即Session对象在requests库中没有一个可以全局设置的timeout属性&#xff0c;而是需要为每个请求传递timeout值&#xff0c;或者创建一个自定义子类来实现。 为了解决这个问题&#xff0c;可以向Session对象添加…

docker中怎么启动容器

1、首先在linux中使用以下命令来启动 Docker 服务&#xff1a; sudo systemctl start docker2、然后下面的命令显示所有的容器列表&#xff0c;包括正在运行和已停止的容器。 docker ps -a然后找到容器ID 3、使用 docker start 启动一个已停止的容器&#xff1a; docker s…

2024 年如何成为一名成功的漏洞赏金猎人?成长总结以及相关资料推荐

2024 年如何成为一名成功的漏洞赏金猎人?成长总结以及相关资料推荐。 很多狂热的黑客新手都很好奇,如何才能成为一名黑客。其实黑客也有黑帽、白帽、灰帽或红帽之类的称呼。黑帽黑客,指的是专门制造病毒木马、通过操作系统寻找漏洞牟取暴利,并且以个人意志为出发点,肆意攻…

X86 bios 中断大全

1、显示服务(Video Service——INT 10H) 00H —设置显示器模式 0CH —写图形象素 01H —设置光标形状 0DH —读图形象素 02H —设置光标位置 0EH —在Teletype模式下显示字符 03H —读取光标信息 0FH —读取显示器模式 04H —读取光笔位置 10H —颜色…

AI创作系统ChatGPT网站源码+详细搭建部署教程+支持DALL-E3文生图/支持最新GPT-4-Turbo-With-Vision-128K多模态模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

孩子看书用白光还是暖白光?最适合写作业台灯推荐

孩子看书用4000K的暖白光最好。要不是亲眼所见&#xff0c;真的很难想象一个台灯用处如此大&#xff0c;护眼效果非常明显。说起来很久没有用过护眼灯具了&#xff0c;这次用过之后有着明显的反差&#xff0c;如果能给孩子用&#xff0c;那将大大保障了孩子的用眼、护眼问题。我…

海康Visionmaster-环境配置:MFC 二次开发环境配置方法

1 新建 MFC 工程&#xff0c;拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0 \ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 2 通过配置 C目录和链接器的方式配置 VM 环境 2.1 C目录下添加附加…

2023年9月 少儿编程 中国电子学会图形化编程等级考试Scratch编程一级真题解析(判断题)

2023年9月scratch编程等级考试一级真题 判断题(共10题,每题2分,共20分) 26、在给舞台背景编写程序时,可以使用运动模块中的积木 答案:错 考点分析:考查运动积木相关知识,再舞台背景中编写程序,不能使用运动模块中的积木,所以错误 27、在位图模式下绘制的角色,无论…

arcgis--二维点、线转三维

利用【3D Analyst工具】-【3D要素】-【依据属性实现要素转3D】将点要素转换成三维点。二维点属性中需含有其点高程信息。原始输入数据点属性表如下&#xff1a; 利用【依据属性实现要素转3D】工具&#xff0c;将其转成三维点。参数设置如下&#xff1a; 点转三维后&#xff0c;…

QGIS结合CityEngine制作卫星图地形模型

参考 https://blog.csdn.net/qq_17523181/article/details/134136379 https://blog.csdn.net/qq_17523181/article/details/134306063 安装QGIS软件与CityEngine软件 一、QGIS获取卫星图 QGIS新建工程安装插件 加入卫星图&#xff08;需要科学上网&#xff09;&#xff0c;目…

Spring6(三):面向切面AOP

文章目录 4. 面向切面&#xff1a;AOP4.1 场景模拟4.1.1 声明接口4.1.2 创建实现类4.1.3 创建带日志功能的实现类4.1.4 提出问题 4.2 代理模式4.2.1 概念4.2.2 静态代理4.2.3 动态代理4.2.4 测试 4.3 AOP概念4.3.1 相关术语①横切关注点②通知&#xff08;增强&#xff09;③切…

结构工程师软件 Naviate Core MEP for Revit 3.4 Crk

Naviate Fabrication - 先进的建模和制造命令&#xff0c;可提高 VDC 设计师、细节设计师和承包商的生产力和收入。 Naviate MEP - 通过 MEP 工程师和设计师的建模和参数提高效率 导航架构 Naviate Architecture 完全集成到 Revit 平台中&#xff0c;增强了 BIM 提供的协作可能…

ROC 曲线:健康背景下的应用和解释

一、介绍 在医疗保健领域&#xff0c;做出明智的决策对于改善患者治疗结果、有效分配资源和设计有效的诊断测试至关重要。受试者工作特征 (ROC) 曲线是一个强大的工具&#xff0c;在评估诊断测试的性能、区分健康个体和患病个体以及优化医疗保健干预方面发挥着至关重要的作用。…

Vue3-admin-template 框架实现表单身份证获取到 出生年月、性别

一. 首先需效验输入身份证信息是否正确&#xff1a; const sfzhChange () > {// 效验身份证号格式const reg /^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/; }; 二.绑定输入框 input 事件&#xff1a; <el-form-item label&q…

2023 年 数维杯(A题)国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2021年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看数维杯A题&#xff01; 问题重述 1、俯仰力矩和俯…

day23_mysql

今日内容 零、 复习昨日 一、函数[了解,会用] 二、事务[重点,理解,面试] 三、数据库范式 零、 复习昨日 见晨考 一、函数 字符串函数数学函数日期函数日期-字符串转换函数流程函数 1.1 字符串函数 函数解释CONCAT (string2 [,... ]) 连接字串LENGTH (string )string长度REPLAC…