В настоящее время я работаю над микросервисом сервисной ткани, который должен иметь высокую пропускную способность.Azure Service Fabric InvokeWithRetryAsync огромные накладные расходы
Я задавался вопросом, почему я не могу получить более 500 сообщений в секунду на моей рабочей станции с использованием loopback.
Я удалил всю бизнес-логику и подключил профилировщик производительности, чтобы измерить производительность до конца.
Похоже, что 96% времени потрачено на разрешение Клиента и только ~ 2% на выполнение фактических запросов Http.
Я вызова "Отправить" в тугой петлей для теста:
private HttpCommunicationClientFactory factory = new HttpCommunicationClientFactory();
public async Task Send()
{
var client = new ServicePartitionClient<HttpCommunicationClient>(
factory,
new Uri("fabric:/MyApp/MyService"));
await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + "/test"));
}
Любые идеи по этому поводу? Согласно документации, как я называю Службы, кажется лучшей практикой Fabric.
UPDATE: Кэширование ServicePartioningClient делает улучшить производительность, но с использованием распределяли услуг, я не в состоянии кэшировать клиента, так как я не знаю раздел для поддавки PartitionKey.
ОБНОВЛЕНИЕ 2: Извините, что я не включил полную информацию в свой первоначальный вопрос. Мы заметили огромные накладные расходы InvokeWithRetry, когда первоначально осуществляли связь на основе сокетов.
Вы не заметите его так сильно, если используете HTTP-запросы. HTTP-запрос уже занимает ~ 1 мс, поэтому добавление 0,5 мс для InvokeWithRetry не является чем-то примечательным.
Но если вы используете сырые сокеты, которые занимают в нашем случае ~ 0,005 мс, накладные расходы на 0,5 мс для InvokeWithRetry огромны!
Вот пример HTTP, с InvokeAndRetry он принимает 3 раза до тех пор:
public async Task RunTest()
{
var factory = new HttpCommunicationClientFactory();
var uri = new Uri("fabric:/MyApp/MyService");
var count = 10000;
// Example 1: ~6000ms
for (var i = 0; i < count; i++)
{
var pClient1 = new ServicePartitionClient<HttpCommunicationClient>(factory, uri, new ServicePartitionKey(1));
await pClient1.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url));
}
// Example 2: ~1800ms
var pClient2 = new ServicePartitionClient<HttpCommunicationClient>(factory, uri, new ServicePartitionKey(1));
HttpCommunicationClient resolvedClient = null;
await pClient2.InvokeWithRetryAsync(
c =>
{
resolvedClient = c;
return Task.FromResult(true);
});
for (var i = 0; i < count; i++)
{
await resolvedClient.HttpClient.GetAsync(resolvedClient.Url);
}
}
Я знаю, что InvokeWithRetry добавляет некоторые интересные вещи, я не хочу пропустить от клиентов. Но нужно ли разрешать разделы для каждого вызова?
Спасибо за ваши подробные тесты! Они помогли мне обновить свой первоначальный вопрос и лучше решить проблему. Проблема заключается в поведении самого InvokeWithRetryAsync, а не в создании экземпляра ServicePartitionClient или нет. – coalmee
Я думаю, что вы являетесь главным виновником здесь InvokeWithRetryAsync. Глядя на то, что делает этот код (используя dotPeek или Reflector), показывает, что он делает намного больше, чем просто вызывает конечную точку HTTP. Дело в том, что если вы хотите, чтобы ваше сообщение было надежным, вам может понадобиться все это, или вы могли бы реализовать что-то легкое, которое соответствует вашим требованиям при связи с тройником. Кстати, я думаю, что причина, по которой он пытается разрешить раздел каждый раз, состоит в том, что первичный вариант мог быть изменен на реплику на другом узле. – yoape
Я боюсь, что я не получаю реализацию в каждом сценарии сбоя, и у меня нет времени, чтобы проверить ее задумчиво. В моем случае извлечение конечных точек один раз или когда FabricClient сообщает об изменении раздела, будет достаточно. Когда Клиент выдает исключение, из-за сбоя связи с узлом, то реквизит также был бы разумным. Но получать каждый «наносекунда» слишком много. – coalmee