为什么我在 API 网关中遇到 Task.WhenAll、RestSharp 和 Async-Await 的性能不佳?

Swi*_*386 5 c# restsharp async-await docker asp.net-core-mvc

我正在编写一个搜索 API,它使用 async-await 和 awaiting Task.WhenAll 聚合来自许多微服务 API 的结果。我最多对 Search API 的每个请求进行 11 次微服务 API 调用。但是,我看到性能不一致 - 有时 Search API 需要 400 毫秒(可接受),而其他的则需要 1 秒以上(不可接受),即使我在本地运行/无负载时也是如此。大部分放缓似乎发生在 await Task.WhenAll 周围。

三个观察:

  • 搜索 API 有时会启动对微服务 API 的 1/3 或 1/2 调用,然后等待 600-1000 毫秒以启动下一组调用。这种批处理行为是不一致的。

  • 搜索 API 有时会调用每个微服务 API,获取结果并再等待 500-1000 毫秒。例如,Search API 可能需要 1000 毫秒,所有微服务请求都在 100 毫秒内开始,并在 400 毫秒或更短的时间内在内部完成。但是,在响应全部发回后,搜索 API 只会继续在 WhenAll 上停留 500 毫秒。

  • 这种行为是不一致的。如果我运行 30 次,可能其中 5 或 10 次相对于其他 25 或 20 次需要超长的时间。

所有 API 都是用 ASP.NET Core MVC 编写的,在单个 docker 网络上的 Linux Docker 容器中运行(应该是彼此本地的),并且是异步的。

在每个 API 中,我都会记录传入请求的持续时间,因此我可以全面了解跨应用程序的整个请求的时间和持续时间。

我试过的步骤

  • http 客户端是 RestSharp,但我尝试用 .NET 的 HttpClient 替换,没有任何实质性区别。

  • 尝试使用 Parallel.ForEach 实际上速度较慢。

  • 尝试将单个微服务 API 控制器更改为不进行任何处理 - 立即返回一个没有正文的 200 - 但仍然获得相同的行为。

控制器

[HttpGet("get-something")]
public async Task<IEnumerable<int>> GetSomethingAsync(int id)
{
    //search engine is a scoped dependency injected via DI
    return await _searchEngine.Search(id);
}
Run Code Online (Sandbox Code Playgroud)

搜索引擎

public async Task<IEnumerable<int>> Search(int id)
{
    //both clients are transient dependencies injected via DI
    var iceTasks = _iceClient.FetchTasksAsync(id).ToList();
    var fireTasks = _fireClient.FetchTasksAsync(id).ToList();

    var results = await Task.WhenAll(iceTasks.Union(fireTasks));

    return results.SelectMany(r => r.Data);
}
Run Code Online (Sandbox Code Playgroud)

IceClient 和 FireClient 基类

public List<Task<IRestResponse<IEnumerable<int>>>> FetchTasksAsync(int id)  
{
    var taskList = new List<Task<IRestResponse<IEnumerable<int>>>>();
    var restClient = new RestClient()
    {
        Timeout = 20000 //in ms
    };

    foreach (var url in _urlFactory.Create())
    {
        var task = restClient.ExecuteGetTaskAsync<IEnumerable<int>>(new RestRequest(url));

        taskList.Add(task);
    }

    return taskList;
}
Run Code Online (Sandbox Code Playgroud)

期望 每个 API 调用都应该在 30 ms 内彼此启动,并且总搜索持续时间大约是最长的微服务 API 持续时间的持续时间。