最近发现有客户在大量的请求我们的接口,出于性能考虑遂添加了请求频率限制。

  由于我们接口请求的是.Net Core写的API网关,所以可以直接添加一个中间件,中间件中使用请求的地址当key,通过配置中心读取对应的请求频率参数设置,然后通过设置redis的过期时间就能实现了。

  添加一个中间件ApiThrottleMiddleware,使用httpContext.Request.Path获取请求的接口,然后以次为key去读取配置中心设置的请求频率设置。(Ps:使用_configuration.GetSection(apiUrl).Get<ApiThrottleConfig>()不知为何返回值为null,这个还在查)

public class ApiThrottleMiddleware
{
private readonly RequestDelegate _next;
private IConfiguration _configuration;
private readonly IRedisRunConfigDatabaseProvider _redisRunConfigDatabaseProvider;
private readonly IDatabase _database; public ApiThrottleMiddleware(RequestDelegate next,
IConfiguration configuration,
IRedisRunConfigDatabaseProvider redisRunConfigDatabaseProvider)
{
_next = next;
_configuration = configuration;
_redisRunConfigDatabaseProvider = redisRunConfigDatabaseProvider;
_database = _redisRunConfigDatabaseProvider.GetDatabase();
} public async Task Invoke(HttpContext httpContext)
{
var middlewareContext = httpContext.GetOrCreateMiddlewareContext();
var apiUrl = httpContext.Request.Path.ToString(); var jsonValue= _configuration.GetSection(apiUrl).Value;
var apiThrottleConfig=JsonConvert.DeserializeObject<ApiThrottleConfig>(jsonValue);
//var apiThrottleConfig = _configuration.GetSection(apiUrl).Get<ApiThrottleConfig>(); await _next.Invoke(httpContext);
}
}

 我们使用的配置中心是Apollo,设置的格式如下,其中Duration为请求间隔/秒,Limit为调用次数。(下图设置为每分钟允许请求10次)

(Ps: 由于在API限流中间件前我们已经通过了一个接口签名验证的中间件了,所以我们可以拿到调用客户的具体信息)

如果请求地址没有配置请求频率控制,则直接跳过。否则先通过SortedSetLengthAsync获取对应key的记录数,其中key我们使用了 $"{客户Id}:{插件编码}:{请求地址}",以此来限制每个客户,每个插件对应的某个接口来控制请求频率。获取key对应集合,当前时间-配置的时间段到当前时间的记录。

 1         /// <summary>
2 /// 获取key
3 /// </summary>
4 /// <param name="signInfo"></param>
5 /// <param name="apiUrl">接口地址</param>
6 /// <returns></returns>
7 private string GetApiRecordKey(InterfaceSignInfo signInfo,string apiUrl)
8 {
9 var key = $"{signInfo.LicNo}:{signInfo.PluginCode}:{apiUrl}";
10 return key;
11 }
12
13 /// <summary>
14 /// 获取接口调用次数
15 /// </summary>
16 /// <param name="signInfo"></param>
17 /// <param name="apiUrl">接口地址</param>
18 /// <param name="duration">超时时间</param>
19 /// <returns></returns>
20 public async Task<long> GetApiRecordCountAsync(InterfaceSignInfo signInfo, string apiUrl, int duration)
21 {
22 var key = GetApiRecordKey(signInfo, apiUrl);
23 var nowTicks = DateTime.Now.Ticks;
24 return await _database.SortedSetLengthAsync(key, nowTicks - TimeSpan.FromSeconds(duration).Ticks, nowTicks);
25 }

如果请求次数大于等于我们设置的频率就直接返回接口调用频率超过限制错误,否则则在key对应的集合中添加一条记录,同时将对应key的过期时间设置为我们配置的限制时间。

        /// <summary>
/// 获取接口调用次数
/// </summary>
/// <param name="signInfo"></param>
/// <param name="apiUrl">接口地址</param>
/// <param name="duration">超时时间</param>
/// <returns></returns>
public async Task<long> GetApiRecordCountAsync(InterfaceSignInfo signInfo, string apiUrl, int duration)
{
var key = GetApiRecordKey(signInfo, apiUrl);
var nowTicks = DateTime.Now.Ticks;
return await _database.SortedSetLengthAsync(key, nowTicks - TimeSpan.FromSeconds(duration).Ticks, nowTicks);
}

 然后只需要在Startup中,在API签名验证中间件后调用我们这个API限流中间件就行了。

  以下为完整的代码

 1 using ApiGateway.Core.Configuration;
2 using ApiGateway.Core.Domain.Authentication;
3 using ApiGateway.Core.Domain.Configuration;
4 using ApiGateway.Core.Domain.Errors;
5 using Microsoft.AspNetCore.Http;
6 using Microsoft.Extensions.Configuration;
7 using Newtonsoft.Json;
8 using StackExchange.Redis;
9 using System;
10 using System.Threading.Tasks;
11
12 namespace ApiGateway.Core.Middleware.Api
13 {
14 /// <summary>
15 /// API限流中间件
16 /// </summary>
17 public class ApiThrottleMiddleware
18 {
19 private readonly RequestDelegate _next;
20 private IConfiguration _configuration;
21 private readonly IRedisRunConfigDatabaseProvider _redisRunConfigDatabaseProvider;
22 private readonly IDatabase _database;
23
24 public ApiThrottleMiddleware(RequestDelegate next,
25 IConfiguration configuration,
26 IRedisRunConfigDatabaseProvider redisRunConfigDatabaseProvider)
27 {
28 _next = next;
29 _configuration = configuration;
30 _redisRunConfigDatabaseProvider = redisRunConfigDatabaseProvider;
31 _database = _redisRunConfigDatabaseProvider.GetDatabase();
32 }
33
34 public async Task Invoke(HttpContext httpContext)
35 {
36 var middlewareContext = httpContext.GetOrCreateMiddlewareContext();
37 var apiUrl = httpContext.Request.Path.ToString();
38
39 var jsonValue= _configuration.GetSection(apiUrl).Value;
40 var apiThrottleConfig=JsonConvert.DeserializeObject<ApiThrottleConfig>(jsonValue);
41 //var apiThrottleConfig = _configuration.GetSection(apiUrl).Get<ApiThrottleConfig>();
42 if (apiThrottleConfig!=null)
43 {
44 var count = await GetApiRecordCountAsync(middlewareContext.InterfaceSignInfo, apiUrl, apiThrottleConfig.Duration);
45 if (count >= apiThrottleConfig.Limit)
46 {
47 middlewareContext.Errors.Add(new Error("接口调用频率超过限制", GatewayErrorCode.OverThrottleError));
48 }
49 else
50 {
51 await AddApiRecordCountAsync(middlewareContext.InterfaceSignInfo, apiUrl, apiThrottleConfig.Duration);
52 }
53 }
54
55 await _next.Invoke(httpContext);
56 }
57
58 /// <summary>
59 /// 获取接口调用次数
60 /// </summary>
61 /// <param name="signInfo"></param>
62 /// <param name="apiUrl">接口地址</param>
63 /// <param name="duration">超时时间</param>
64 /// <returns></returns>
65 public async Task<long> GetApiRecordCountAsync(InterfaceSignInfo signInfo, string apiUrl, int duration)
66 {
67 var key = GetApiRecordKey(signInfo, apiUrl);
68 var nowTicks = DateTime.Now.Ticks;
69 return await _database.SortedSetLengthAsync(key, nowTicks - TimeSpan.FromSeconds(duration).Ticks, nowTicks);
70 }
71
72 /// <summary>
73 /// 添加调用次数
74 /// </summary>
75 /// <param name="signInfo"></param>
76 /// <param name="apiUrl">接口地址</param>
77 /// <param name="duration">超时时间</param>
78 /// <returns></returns>
79 public async Task AddApiRecordCountAsync(InterfaceSignInfo signInfo, string apiUrl, int duration)
80 {
81 var key = GetApiRecordKey(signInfo, apiUrl);
82 var nowTicks = DateTime.Now.Ticks;
83 await _database.SortedSetAddAsync(key, nowTicks.ToString(), nowTicks);
84 await _database.KeyExpireAsync(key, TimeSpan.FromSeconds(duration));
85 }
86
87 /// <summary>
88 /// 获取key
89 /// </summary>
90 /// <param name="signInfo"></param>
91 /// <param name="apiUrl">接口地址</param>
92 /// <returns></returns>
93 private string GetApiRecordKey(InterfaceSignInfo signInfo,string apiUrl)
94 {
95 var key = $"{signInfo.LicNo}:{signInfo.PluginCode}:{apiUrl}";
96 return key;
97 }
98 }
99 }

AspNetCore添加API限流的更多相关文章

  1. 服务接口API限流 Rate Limit 续

    一.前言 上一篇文章中粗浅的介绍使用Redis和基于令牌桶算法进行对服务接口API限流,本文介绍另一种算法---漏桶算法的应用.Nginx想必大家都有所了解是一个高性能的 HTTP 和反向代理服务器, ...

  2. Springboot中使用redis进行api限流

    api限流的场景 限流的需求出现在许多常见的场景中 秒杀活动,有人使用软件恶意刷单抢货,需要限流防止机器参与活动 某api被各式各样系统广泛调用,严重消耗网络.内存等资源,需要合理限流 淘宝获取ip所 ...

  3. 服务接口API限流 Rate Limit

    一.场景描述 很多做服务接口的人或多或少的遇到这样的场景,由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统. 也就是面对大流量时,如何进行流量控制? 服务接口的流量 ...

  4. java 服务接口API限流 Rate Limit

    一.场景描述 很多做服务接口的人或多或少的遇到这样的场景,由于业务应用系统的负载能力有限,为了防止非预期的请求对系统压力过大而拖垮业务应用系统. 也就是面对大流量时,如何进行流量控制? 服务接口的流量 ...

  5. 两个简单的API限流实现方案

    1, Ngnix限流 Nginx在架构中起到请求转发与负载均衡器的作用.外部req首先到Nginx监听的80端口,然后Nginx将req交给到监听8080端口的APP服务器处理.处理结果再经由Ngin ...

  6. Guava RateLimiter实现接口API限流

    一.简介 Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率.RateLimit二的原理类似与令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的 ...

  7. 令牌桶算法实现API限流

    令牌桶算法( Token Bucket )和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100 ,则间隔是 10 ...

  8. redis实现api限流

    redis官方给出了参考文档:INCR 这里参考第一种方法,使用token bucket实现:每个用户每秒有一个Counter: func RateLimiter(uid string, rlType ...

  9. AspNetCore 限流中间件IpRateLimitMiddleware 介绍

    IpRateLimitMiddleware(Github: AspNetCoreRateLimit) 是ASPNETCore的一个限流的中间件,用于控制客户端调用API的频次, 如果客户端频繁访问服务 ...

随机推荐

  1. Zabbix5.0微信报警

    3.1.注测企业微信: 3.2.企业微信注册成功后进入后台管理: 3.3.添加一个部门,并记住部门id: #我这里添加的子部门ID为2 3.4.添加一个用户到上面创建的部门里面(这里采取直接将管理员添 ...

  2. 7.1、controller节点配置

    0.配置openstack版本yum源: yum install centos-release-openstack-rocky 1.安装 OpenStack 客户端: yum install pyth ...

  3. iOS如何提升,首先得知自己的处境!(欢迎评论留言)

    聊聊行情?为什么总有人在乎旁人的看法而忽略自己的初衷?虽然iOS开发市场说不上好但也绝不算坏,想没想过那些煽风点火说iOS不行的人在做什么?真的转行从头开始?错.大错特错!在劝退你的同时他们会默默的学 ...

  4. 12.10File类

    要点提示:File类包含了获得一个文件/目录的属性,以及对文件/目录进行改名和删除的方法. 在文件系统中,每个文件都存放在一个目录下.绝对文件名,是由文件名和它的完整路径以及驱动器字母组成.例如,c: ...

  5. CRM企业管理系统一年多少钱?

    CRM系统一年多少钱?这是很多企业管理者比较关心的问题.目前市面上的CRM系统分为本地部署型和云部署型.云部署型CRM费用相对较低,只需要按需购买账号,连接网络即可使用:本地部署型CRM费用较高,企业 ...

  6. 使用Hugo框架搭建博客的过程 - 前期准备

    前言 这篇教程介绍了如何搭建这样效果的博客. 所需步骤 可以从这样的角度出发: 注册域名. 使用CDN加快网站访问速度. 网站内容需要部署在服务器或对象存储平台上. 重要的是放什么内容.博客需要选择框 ...

  7. WPF特效:流光动画效果

    今天有人问我想实现一个流光效果.我简单的录了一下他想实现效果的GIF. 实际效果如下:因为今天有事情,只是大概实现了一个效果.主要使用PointAnimationUsingKeyFrames动画效果来 ...

  8. ARTS起始篇

    ARTS简要说明(每周需要完成以下四项): Algorithm:每周至少做一道 leetcode 的算法题,编程训练.刻意练习. Review:需要阅读并点评至少一篇英文技术文章,这个是四项里面对我最 ...

  9. 「CF505E」 Mr. Kitayuta vs. Bamboos

    「CF505E」 Mr. Kitayuta vs. Bamboos 传送门 如果没有每轮只能进行 \(k\) 次修改的限制或者没有竹子长度必须大于 \(0\) 的限制那么直接贪心就完事了. 但是很遗憾 ...

  10. 从源码角度谈谈MySQL "Too many open files"错误的根本原因

    "Too many open files"是一个比较常见的错误,不仅仅是在 MySQL 中.只要是在 Linux 中启动的进程,都有可能遇到这个错误. 究其原因,是进程打开的文件描 ...