.NetCore手写一个 API 限流组件

首先如果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 限流组件。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QGIS之二十三矢量线融合

效果 步骤 1、准备数据 现有线分段太多&#xff0c;需要将部分线按照某个字段融合起来 2、融合 运行 3、结果 线已经融合了 线相交处也添加了线的节点

OceanBase杨冰:完全自研,才能逢山开路遇水搭桥

11月16日&#xff0c;在OceanBase2023年度发布会上&#xff0c;OceanBase CEO杨冰介绍&#xff0c;中国数字经济的蓬勃发展催生了对分布式数据库的强大需求&#xff0c;这种需求也牵引了OceanBase坚定投入自主研发&#xff0c;从而推动树立了分布式数据库的四项新标准。 据了解…

学生用什么光的灯最好?适合学生光源的护眼台灯推荐

学生当然用全光谱的台灯最好。全光谱台灯主要还是以护眼台灯为主&#xff0c;因为不仅色谱丰富&#xff0c;贴近自然色的全光谱色彩&#xff0c;通常显色指数都能达到Ra95以上&#xff0c;显色能力特别强&#xff0c;而且还具有其他防辐射危害、提高光线舒适度的特性&#xff0…

YB2502单片集成带可设定输出电流开关型降压转换器

描述&#xff1a; YB2502单片集成带可设定输出电 流开关型降压转换器&#xff0c;可在宽输入电压范围提供1.2安培的持续输出电流&#xff0c;具有优良的负载和线性调整度。最大输出电流可通过外接高精度取样电阻来设定。安全保护机制包括每周期的峰值限流、内部软启动和温度保护…

elementPlus实现暗黑与白亮主题切换

elementPlus上面默认是支持黑白主题切换的。只需要给html标签添加dark类名并且在mian.ts中导入css变量即可 我们就按照它推荐的useDark这个hook来 useDark使用起来非常简单&#xff0c;只需引入使用即可。vue直接就可以使用&#xff0c;无需做什么其他操作 中文网地址&#…

[CSS] 文本折行

文本折行一般分为两种情况&#xff1a; CJK&#xff08;Chinese/Japanese/Korean&#xff09; 字符和非 CJK 字符。一般非 CJK 字符折行发生在两个单词的空格中间&#xff0c;见下图&#xff1a; 图中文本 “hello world” 包裹容器的宽度为 2rem&#xff0c;但是 hello 并没有…

审视现状,持续优化,数字化转型不简单

北京市首都公路发展集团有限公司&#xff08;简称“首发集团”&#xff09;为北京市国有独资公司&#xff0c;1999年9月成立&#xff0c;负责北京市高速公路、城市道路及配套设施的投融资、建设及运营管理。集团下属4家分公司、10家全资或控股二级子公司&#xff0c;参股中铁京…

电脑上怎么设置代理IP,有什么作用

代理IP是一种可以帮助你隐藏真实IP地址的技术&#xff0c;通过使用代理IP&#xff0c;你可以访问被封锁的网站或者突破地域限制。本文将介绍如何在电脑上设置代理IP以及其作用。 一、如何设置流冠代理IP 1. 手动设置代理 在电脑的浏览器中&#xff0c;找到“工具”菜单&…

SIMULIA|Simpack 2023x新功能

Simpack 核心模块的功能增强 Simpack Pre 求解器设置功能优化 旧版本中SolverSettings是必须保留的&#xff0c;该设置在2023版本中进行了调整&#xff0c;现在可以在模型中删除所有SolverSettings&#xff0c;避免了在仅使用子模型时使用失效的SolverSettings(比如Wizard 数据…

图解分布式事务实现原理(二)

参考 本文参考https://zhuanlan.zhihu.com/p/648556608&#xff0c;在小徐的基础上做了个人的笔记。 TCC 实现方案 TCC 概念简述 TCC&#xff08;Try-Confirm-Cancel&#xff09;是一种分布式事务处理模式&#xff0c;旨在保证分布式系统中的事务一致性。它的核心思想是将一…

keepalived安装配置(服务器主备、负载均衡)

系统拓扑 安装keepalived 主备服务器上都需要安装 在线安装 yum install -y keepalived 离线安装 # todo 服务器准备 虚拟机ip&#xff1a;192.168.11.56 主服务器&#xff1a;192.168.11.53 备服务器&#xff1a;192.168.11.54 配置文件修改 keepalived安装之后&…

k8s pod常用运维命令

1. 概述 kubectl 命令是操作 Kubernetes 集群的最直接和最高效的途径&#xff0c;熟练掌握命令的使用能起到事半功倍的效果&#xff0c;整理命令有助于加深记忆&#xff0c;该文仅记录关于pod常用的操作运维命令。 2. 查看namespaces 查看k8s集群中目前存在的namespaces kub…

Python PyQt 程序设置图标

源码运行时图标 第一步&#xff1a;阿里巴巴是两图标库下载喜欢的图标 iconfont-阿里巴巴矢量图标库 第二步&#xff1a;转化png为ico https://www.aconvert.com/cn/icon/png-to-ico/ 256x256为大图标 默认的32x32很小&#xff08;不建议用) 转化后右键点击文件链接&…

创邻科技亮相ISWC 2023,国际舞台见证知识图谱领域研究突破

近日&#xff0c;第22届国际语义网大会 ISWC 2023 在雅典希腊召开&#xff0c;通过线上线下的形式&#xff0c;聚集了全球的顶级研究人员、从业人员和行业专家&#xff0c;讨论、发展和塑造语义网和知识图谱技术的未来。创邻科技CEO张晨博士作为知识图谱行业专家受邀参会&#…

十八数藏:数字创新之美,文化传承的璀璨明星

在当代数字时代&#xff0c;十八数藏如同一颗璀璨之星&#xff0c;闪耀在文化传承的广袤天空。其数字创新之美&#xff0c;不仅在传统工艺中绽放&#xff0c;更为文化守护开辟了崭新的篇章。 十八数藏的数字创新&#xff0c;宛如一场艺术之舞。在传统工艺的基础上&#xff0c;数…

部署LCM(Latent Consistency Models)实现快速出图

LCM 的全称是 Latent Consistency Models&#xff08;潜在一致性模型&#xff09;&#xff0c;由清华大学交叉信息研究院的研究者们构建。通过一些创新性的方法&#xff0c;LCM 只用少数的几步推理就能生成高分辨率图像&#xff0c;将主流文生图模型的效率提高 5-10 倍&#xf…

pd19虚拟机win系统镜像(m1/intel)

入手了Mac电脑后&#xff0c;由于需要用到Windows软件&#xff0c;又嫌安装双系统太复杂&#xff0c;这时候Mac就用到了安装虚拟机&#xff0c;目前最好用的虚拟机是Parallels Desktop&#xff0c;win镜像版本要根据自己的喜好选对&#xff0c;在此提供分别兼容M1和Intel的win1…

inner join left join 什么情况效果相同

效果不同的情况 SELECT g.name AS groupName, g.root_code AS rootCode, g.data_sort AS groupDataSort, l.* FROM wise_system_point_group g LEFT JOIN wise_system_point_list l ON g.code l.group_code WHERE g.code "drug" ORDER BY g.data_sort, l.data_s…

最新完美版积分商城系统-奇偶商城系统源码+独立代理后台+附搭建教程

源码简介&#xff1a; 最新完美版积分商城系统&#xff0c;网购商城系统源码&#xff0c;是更新的奇偶商城系统源码&#xff0c;它拥有独立代理后台&#xff0c;而且内附搭建教程。 1.演示环境&#xff1a;Linux Centos7以上版本 宝塔 2.Nginx 1.18.0 PHP7.0 Mysql5.6 3…

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

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