简体   繁体   English

如何限制并发外部API调用.Net Core Web API?

[英]How to limit concurrent external API calls in .Net Core Web API?

Currently i am working on a .net core web api project on which it is getting data from an external web api. Currently i am working on a .net core web api project on which it is getting data from an external web api. And they have a concurrent rate limiter of 25(25 concurrent api calls are permitted) at their end.他们有一个 25 个并发速率限制器(允许 25 个并发 api 调用)。 the 26th API call will be failed.第 26 次 API 调用将失败。

So i want to implement a concurrent API rate limiter on my web API project and need to track 26th API call which is failed and need to retry that(it may be get or post call). So i want to implement a concurrent API rate limiter on my web API project and need to track 26th API call which is failed and need to retry that(it may be get or post call). i have multiple get request as well as post request in my api code我的 api 代码中有多个获取请求和发布请求

following is my httpservice.cs in my web api以下是我 web api 中的 httpservice.cs

public HttpClient GetHttpClient()
{
    HttpClient client = new HttpClient
    {
        BaseAddress = new Uri(APIServer),
    };
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Add("Authorization", ("Bearer " + Access_Token));
    return client;
}
private HttpClient Client;
public async Task<Object> Get(string apiEndpoint)
{

    Client = GetHttpClient();
    HttpResponseMessage httpResponseMessage = await Client.GetAsync(apiEndpoint);
    if (httpResponseMessage.IsSuccessStatusCode)
    {
        Object response = await httpResponseMessage.Content.ReadAsStringAsync();
        return response;
    }
    else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
    {
        //need to track failed calls                    
        return StatusCode(httpResponseMessage.StatusCode.GetHashCode());
    }
}

public async Task<Object> Post(string apiEndpoint, Object request)
{
    Client = GetHttpClient();
    HttpResponseMessage httpResponseMessage = await Client.PostAsJsonAsync(apiEndpoint, request);
    if (httpResponseMessage.IsSuccessStatusCode)
    {
        return await httpResponseMessage.Content.ReadAsAsync<Object>();
    }

    else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
    {
        //need to track
        return StatusCode(httpResponseMessage.StatusCode.GetHashCode());
    }
} 

how can i limit concurrent api calls in above example在上面的示例中,我如何限制并发 api 调用

SemaphoreSlim _semaphoregate = new SemaphoreSlim(25);
await _semaphoregate.WaitAsync();      
_semaphoregate.Release();  

Will this work?这行得通吗?

AspNetCoreRateLimit nuget package is useful here? AspNetCoreRateLimit nuget package 在这里有用吗? will that limit concurrency on above sample?这会限制上述样本的并发性吗?

Please help.请帮忙。

The simplest solution I'm aware of in limiting the number of concurrent access to a piece of code is using a SemaphoreSlim object, in order to implement a throttling mechanism.在限制对一段代码的并发访问数量方面,我所知道的最简单的解决方案是使用SemaphoreSlim object,以实现节流机制。

You can consider the approach showed below, which you should adapt to your current scenario (the following code is simplistic and it is only meant to show you the general idea):你可以考虑下面显示的方法,你应该适应你当前的场景(下面的代码很简单,它只是为了向你展示一般的想法):

public class Program 
{
    private static async Task DoSomethingAsync()
    {
      // this is the code for which you want to limit the concurrent execution
    }

    // this is meant to guarantee at most 5 concurrent execution of the code in DoSomethingAsync
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); 

    // here we execute 100 calls to DoSomethingAsync, by ensuring that at most 5 calls are executed concurrently
    public static async Task Main(string[] args) 
    {
        var tasks = new List<Task>();
        
        for(int i = 0; i < 100; i++) 
        {
            tasks.Add(ThrottledDoSomethingAsync());
        }
        
        await Task.WhenAll(tasks);
    }

    private static async Task ThrottledDoSomethingAsync()
    {
      await _semaphore.WaitAsync();
      
      try
      {
        await DoSomethingAsync();
      }
      finally
      {
        _semaphore.Release();
      }
    }
}

Here you can find the documentation for the SemaphoreSlim class. 在这里您可以找到SemaphoreSlim class 的文档。

If you want something like a ForEachAsync method, you can consider reading my own question on the subject.如果您想要类似ForEachAsync方法的东西,可以考虑阅读我自己关于该主题的问题。

If you are looking for an elegant solution to use a SemaphoreSlim as a throttling mechanism for your service, you can consider defining an interface for the service itself and use the decorator pattern .如果您正在寻找一个优雅的解决方案来使用SemaphoreSlim作为服务的节流机制,您可以考虑为服务本身定义一个接口并使用装饰器模式 In the decorator you can implement the throttling logic by using the SemaphoreSlim as showed above, while leaving the service logic simple and untouched in the core implementation of the service.在装饰器中,您可以使用如上所示的SemaphoreSlim来实现节流逻辑,同时在服务的核心实现中保持服务逻辑简单且不受影响。 This is not strictly related with your question, it's just a tip to write down the actual implementation for your HTTP service.这与您的问题并不严格相关,它只是写下您的 HTTP 服务的实际实现的提示。 The core idea of the SemaphoreSlim used as a throttling mechanism is the one showed in the code above.用作节流机制的SemaphoreSlim的核心思想是上面代码中显示的思想。

The bare minimum to adapt your code is which follows:调整代码的最低要求如下:

public sealed class HttpService
{
    // this must be static in order to be shared between different instances
    // this code is based on a max of 25 concurrent requests to the API
    // both GET and POST requests are taken into account (they are globally capped to a maximum of 25 concurrent requests to the API)
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(25);

    public HttpClient GetHttpClient()
    {
        HttpClient client = new HttpClient
        {
            BaseAddress = new Uri(APIServer),
        };
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Add("Authorization", ("Bearer " + Access_Token));
        return client;
    }

    private HttpClient Client;

    public async Task<Object> Get(string apiEndpoint)
    {

        Client = GetHttpClient();
        HttpResponseMessage httpResponseMessage = await this.ExecuteGetRequest(apiEndpoint);
        if (httpResponseMessage.IsSuccessStatusCode)
        {
            Object response = await httpResponseMessage.Content.ReadAsStringAsync();
            return response;
        }
        else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            //need to track failed calls                    
            return StatusCode(httpResponseMessage.StatusCode.GetHashCode());
        }
    }

    private async Task<HttpResponseMessage> ExecuteGetRequest(string url)
    {
        await _semaphore.WaitAsync();

        try
        {
            return await this.Client.GetAsync(url);
        }
        finally
        {
            _semaphore.Release();
        }
    }

    public async Task<Object> Post(string apiEndpoint, Object request)
    {
        Client = GetHttpClient();
        HttpResponseMessage httpResponseMessage = await this.ExecutePostRequest(apiEndpoint, request);
        if (httpResponseMessage.IsSuccessStatusCode)
        {
            return await httpResponseMessage.Content.ReadAsAsync<Object>();
        }

        else if (httpResponseMessage.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        {
            //need to track
            return StatusCode(httpResponseMessage.StatusCode.GetHashCode());
        }
    }

    private async Task<HttpResponseMessage> ExecutePostRequest(string url, Object request)
    {
        await _semaphore.WaitAsync();

        try
        {
            return await this.Client.PostAsJsonAsync(url, request);
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

IMPORTANT NOTE : the code you posted creates a brand new HttpClient instance each time you need to perform an HTTP request to your API.重要说明:每次您需要对 API 执行 HTTP 请求时,您发布的代码都会创建一个全新的HttpClient实例。 This is problematic for reasons that go beyond the scope of your question.这是有问题的,因为 go 超出了您问题的 scope 。 I strongly suggest you to read this article and this one too.我强烈建议你阅读这篇文章这篇文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM