.NET5 ASP.NET Core 添加API限流


前言

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

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

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

C# 全选
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次)

 .NET5 ASP.NET Core 添加API限流

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

如果请求地址没有配置请求频率控制,则直接跳过。

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

C# 全选
/// <summary>
/// 获取key
/// </summary>
/// <param name="signInfo"></param>
/// <param name="apiUrl">接口地址</param>
/// <returns></returns>
private string GetApiRecordKey(InterfaceSignInfo signInfo,string apiUrl)
{
	var key = $"{signInfo.LicNo}:{signInfo.PluginCode}:{apiUrl}";
	return 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);
}

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

C# 全选
/// <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限流中间件就行了。

以下为完整的代码

C# 全选
using ApiGateway.Core.Configuration;
using ApiGateway.Core.Domain.Authentication;
using ApiGateway.Core.Domain.Configuration;
using ApiGateway.Core.Domain.Errors;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Threading.Tasks;

namespace ApiGateway.Core.Middleware.Api
{
    /// <summary>
    /// API限流中间件
    /// </summary>
    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;
            if (!string.IsNullOrEmpty(jsonValue))
            {
                var apiThrottleConfig = JsonConvert.DeserializeObject<ApiThrottleConfig>(jsonValue);
                //var apiThrottleConfig = _configuration.GetSection(apiUrl).Get<ApiThrottleConfig>();
                var count = await GetApiRecordCountAsync(middlewareContext.InterfaceSignInfo, apiUrl, apiThrottleConfig.Duration);
                if (count >= apiThrottleConfig.Limit)
                {
                    middlewareContext.Errors.Add(new Error("接口调用频率超过限制", GatewayErrorCode.OverThrottleError));
                }
                else
                {
                    await AddApiRecordCountAsync(middlewareContext.InterfaceSignInfo, apiUrl, apiThrottleConfig.Duration);
                }
            }
            
            await _next.Invoke(httpContext);
        }

        /// <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);
        }

        /// <summary>
        /// 添加调用次数
        /// </summary>
        /// <param name="signInfo"></param>
        /// <param name="apiUrl">接口地址</param>
        /// <param name="duration">超时时间</param>
        /// <returns></returns>
        public async Task AddApiRecordCountAsync(InterfaceSignInfo signInfo, string apiUrl, int duration)
        {
            var key = GetApiRecordKey(signInfo, apiUrl);
            var nowTicks = DateTime.Now.Ticks;
            await _database.SortedSetAddAsync(key, nowTicks.ToString(), nowTicks);
            await _database.KeyExpireAsync(key, TimeSpan.FromSeconds(duration));
        }

        /// <summary>
        /// 获取key
        /// </summary>
        /// <param name="signInfo"></param>
        /// <param name="apiUrl">接口地址</param>
        /// <returns></returns>
        private string GetApiRecordKey(InterfaceSignInfo signInfo,string apiUrl)
        {
            var key = $"_api_throttle:{signInfo.LicNo}:{signInfo.PluginCode}:{apiUrl}";
            return key;
        }
    }
}

 

文章来源: https://www.cnblogs.com/Cyril-hcj/p/15136026.html

 

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
管理员
上一篇:HTML IMG 图片链接404错误,引起alt超长影响界面美观
下一篇:Visual Studio (VS) 使用Gulp报错 ReferenceError: primordials is not defined
评论列表

发表评论

评论内容
昵称:
关联文章

.NET5 ASP.NET Core 添加API
使用.NET 6开发TodoList应用(23)——实现请求
asp.net core MVC路由添加.html伪静态url时报错
ASP.NET Core web API中使用Swagger/OpenAPI(Swashbuckle)
ASP.NET Core官网教程,资料查找
ASP.NET Core 使用 LESS
.Net Core 5.x Api开发笔记 -- 基础日志(Log4Net)(八)
asp.net - 在 ASP.NET Core MVC 中嵌套 TagHelper
【推荐】Razor文件编译 ASP.NET Core
ASP.NET Core开发者学习路线图
ASP.NET+MVC入门踩坑笔记 (一) 创建项目 项目配置运行 以及简单的Api搭建
C# ASP.NET Core开发学生信息管理系统(一)
.NET Core,.NET5 固定输出目录,不要版本目录
asp.net core 断点调试无法修改代码
ASP.NET MVC和ASP.NET Core MVC中获取当前URL/Controller/Action
asp.net TagHelper根据条件向元素添加class
ASP.NET Core MVC中的路由约束
C# ASP.NET Core开发学生信息管理系统(三)
C# ASP.NET Core开发学生信息管理系统(二)
ASP.NET Core 中读取Post Request.Body 的正确姿势

联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码