<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ASP.NET Core 添加API限流

          共 16562字,需瀏覽 34分鐘

           ·

          2021-10-02 23:28


          前言


          最近發(fā)現(xiàn)有客戶在大量的請(qǐng)求我們的接口,出于性能考慮遂添加了請(qǐng)求頻率限制。


          由于我們接口請(qǐng)求的是.Net Core寫的API網(wǎng)關(guān),所以可以直接添加一個(gè)中間件,中間件中使用請(qǐng)求的地址當(dāng)key,通過配置中心讀取對(duì)應(yīng)的請(qǐng)求頻率參數(shù)設(shè)置,然后通過設(shè)置redis的過期時(shí)間就能實(shí)現(xiàn)了。


          添加一個(gè)中間件ApiThrottleMiddleware,使用httpContext.Request.Path獲取請(qǐng)求的接口,然后以次為key去讀取配置中心設(shè)置的請(qǐng)求頻率設(shè)置。(Ps:使用_configuration.GetSection(apiUrl).Get()不知為何返回值為null,這個(gè)還在查)

           1     public class ApiThrottleMiddleware
           2     {
           3         private readonly RequestDelegate _next;
           4         private IConfiguration _configuration;
           5         private readonly IRedisRunConfigDatabaseProvider _redisRunConfigDatabaseProvider;
           6         private readonly IDatabase _database;
           7 
           8         public ApiThrottleMiddleware(RequestDelegate next,
           9             IConfiguration configuration,
          10             IRedisRunConfigDatabaseProvider redisRunConfigDatabaseProvider
          )
          11
                   {
          12             _next = next;
          13             _configuration = configuration;
          14             _redisRunConfigDatabaseProvider = redisRunConfigDatabaseProvider;
          15             _database = _redisRunConfigDatabaseProvider.GetDatabase();
          16         }
          17 
          18         public async Task Invoke(HttpContext httpContext)
          19
                   {
          20             var middlewareContext = httpContext.GetOrCreateMiddlewareContext();
          21             var apiUrl = httpContext.Request.Path.ToString();
          22 
          23             var jsonValue= _configuration.GetSection(apiUrl).Value;
          24             var apiThrottleConfig=JsonConvert.DeserializeObject<ApiThrottleConfig>(jsonValue);
          25             //var apiThrottleConfig = _configuration.GetSection(apiUrl).Get<ApiThrottleConfig>();
          26             
          27             await _next.Invoke(httpContext);
          28         }
          29 }

          我們使用的配置中心是Apollo,設(shè)置的格式如下,其中Duration為請(qǐng)求間隔/秒,Limit為調(diào)用次數(shù)。(下圖設(shè)置為每分鐘允許請(qǐng)求10次)

          (Ps:由于在API限流中間件前我們已經(jīng)通過了一個(gè)接口簽名驗(yàn)證的中間件了,所以我們可以拿到調(diào)用客戶的具體信息)

          如果請(qǐng)求地址沒有配置請(qǐng)求頻率控制,則直接跳過。

          否則先通過SortedSetLengthAsync獲取對(duì)應(yīng)key的記錄數(shù),其中key我們使用了 $"{客戶Id}:{插件編碼}:{請(qǐng)求地址}",以此來限制每個(gè)客戶,每個(gè)插件對(duì)應(yīng)的某個(gè)接口來控制請(qǐng)求頻率。獲取key對(duì)應(yīng)集合,當(dāng)前時(shí)間-配置的時(shí)間段到當(dāng)前時(shí)間的記錄。

           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         /// 獲取接口調(diào)用次數(shù)
          15         /// </summary>
          16         /// <param name="signInfo"></param>
          17         /// <param name="apiUrl">接口地址</param>
          18         /// <param name="duration">超時(shí)時(shí)間</param>
          19         /// <returns></returns>
          20         public async Task<longGetApiRecordCountAsync(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         }        

          如果請(qǐng)求次數(shù)大于等于我們?cè)O(shè)置的頻率就直接返回接口調(diào)用頻率超過限制錯(cuò)誤,否則則在key對(duì)應(yīng)的集合中添加一條記錄,同時(shí)將對(duì)應(yīng)key的過期時(shí)間設(shè)置為我們配置的限制時(shí)間。

                  /// <summary>
                  /// 獲取接口調(diào)用次數(shù)
                  /// </summary>
                  /// <param name="signInfo"></param>
                  /// <param name="apiUrl">接口地址</param>
                  /// <param name="duration">超時(shí)時(shí)間</param>
                  /// <returns></returns>
                  public async Task<longGetApiRecordCountAsync(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簽名驗(yàn)證中間件后調(diào)用我們這個(gè)API限流中間件就行了。

          以下為完整的代碼

          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("接口調(diào)用頻率超過限制", GatewayErrorCode.OverThrottleError));
                          }
                          else
                          {
                              await AddApiRecordCountAsync(middlewareContext.InterfaceSignInfo, apiUrl, apiThrottleConfig.Duration);
                          }
                      }
                      
                      await _next.Invoke(httpContext);
                  }

                  /// <summary>
                  /// 獲取接口調(diào)用次數(shù)
                  /// </summary>
                  /// <param name="signInfo"></param>
                  /// <param name="apiUrl">接口地址</param>
                  /// <param name="duration">超時(shí)時(shí)間</param>
                  /// <returns></returns>
                  public async Task<longGetApiRecordCountAsync(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>
                  /// 添加調(diào)用次數(shù)
                  /// </summary>
                  /// <param name="signInfo"></param>
                  /// <param name="apiUrl">接口地址</param>
                  /// <param name="duration">超時(shí)時(shí)間</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;
                  }
              }
          }


          轉(zhuǎn)自:Cyril

          鏈接:cnblogs.com/Cyril-hcj/p/15136026.html

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色s色片 | 中文字幕在线视频网站国产免费 | 国产婷婷色一区二区三区 | 亚洲国产精品成人无码区 | 黄色A片视频 |