Я пишу API-интерфейс поиска, который объединяет результаты нескольких API-интерфейсов микросервисов, используя async-await и ожидающий Task.WhenAll.Самое большее, я делаю 11 вызовов API микросервиса за запрос к API поиска.Тем не менее, я наблюдаю непоследовательную производительность - иногда API поиска занимает 400 миллисекунд (приемлемо), а в других - более секунды (неприемлемо), даже когда я запускаю локально / без нагрузки.Кажется, что большая часть замедления происходит вокруг ожидающего Task.WhenAll.
Три наблюдения:
API поиска иногда запускает 1/3 или 1/2 вызововк API микросервиса, затем ждет 600-1000 мс, чтобы запустить следующую связку.Такое поведение пакетирования несовместимо.
API поиска иногда вызывает каждый API-интерфейс микросервиса, получает результаты и ожидает еще 500-1000 мс.Например, API поиска может занять 1000 мс, все запросы микросервисов начинаются в пределах 100 мс друг от друга и внутренне завершаются через 400 мс или меньше.Тем не менее, API поиска просто продолжает оставаться в состоянии «Когда» все еще в течение 500 мс после того, как все ответы отправлены обратно.
Это поведение не соответствует.Если я выполню это 30 раз, возможно, 5 или 10 из них займут сверхдлинные по сравнению с другими 25 или 20.
Все API написаны на ASP.NET Core MVC, работающем в LinuxDocker-контейнеры в одной 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);
}
SearchEngine
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);
}
Базовый класс 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;
}
Ожидание Каждый из вызовов API должен начинаться в течение 30 мс друг от друга, а общая продолжительность поиска приблизительно равна продолжительности самой длинной продолжительности API микросервиса.