0

В настоящее время я работаю над микросервисом сервисной ткани, который должен иметь высокую пропускную способность.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")); 
} 

Profiler output

Любые идеи по этому поводу? Согласно документации, как я называю Службы, кажется лучшей практикой 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 добавляет некоторые интересные вещи, я не хочу пропустить от клиентов. Но нужно ли разрешать разделы для каждого вызова?

ответ

2

Я подумал, что было бы неплохо фактически сравнить это и посмотреть, какая разница на самом деле.Я создаю основные настройки с Stateful службой, которая открывает HttpListener и клиентом, который вызывает эту службу три различные способов:

  • Создание нового клиента для каждого вызова и делать все вызовы в последовательности

    for (var i = 0; i < count; i++) 
    { 
        var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1)); 
        var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}")); 
    } 
    
  • Создание клиента только один раз и использовать его для каждого вызова в последовательности

    var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1)); 
    for (var i = 0; i < count; i++) 
    { 
        var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}")); 
    } 
    
  • Создание нового клиента для каждого вызова и запустить аль л звонки в parallell

    var tasks = new List<Task>(); 
    for (var i = 0; i < count; i++) 
    { 
        tasks.Add(Task.Run(async() => 
        { 
         var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1)); 
         var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}")); 
        })); 
    } 
    Task.WaitAll(tasks.ToArray()); 
    

Я побежал тест на количество импульсов, чтобы получить форму среднем:

Per call duration for different client and call models

Теперь, это должно быть принято для чего это это не полный и всесторонний тест в контролируемой среде, есть ряд факторов, которые повлияют на эту производительность, такие как размер кластера, то, что на самом деле вызывает вызываемая служба (в данном случае ничего действительно), а также размер и сложность полезная нагрузка (в данном случае очень короткая строка).

В этом тесте я также хотел увидеть, как ведет себя транспортный транспорт и производительность была похожа на транспорт HTTP (честно говоря, я ожидал немного лучшего, но это может быть не видно в этом тривиальном сценарии).

Стоит отметить, что для параллельного выполнения 10 000 вызовов производительность была значительно снижена. Вероятно, это связано с тем, что в сервисе заканчивается рабочая память. Последствием этого может быть то, что некоторые вызовы клиентов сбой и повторные попытки (для проверки) после задержки. То, как я измеряю продолжительность, - это общее время до все звонки завершены. В то же время следует отметить, что тест на самом деле не позволяет службе использовать более одного узла, поскольку все вызовы направляются в один и тот же раздел.

В заключение следует отметить, что эффект повторного использования клиента является номинальным, а для тривиальных вызовов HTTP выполняет аналогичную передачу Fabric Transport.

+0

Спасибо за ваши подробные тесты! Они помогли мне обновить свой первоначальный вопрос и лучше решить проблему. Проблема заключается в поведении самого InvokeWithRetryAsync, а не в создании экземпляра ServicePartitionClient или нет. – coalmee

+0

Я думаю, что вы являетесь главным виновником здесь InvokeWithRetryAsync. Глядя на то, что делает этот код (используя dotPeek или Reflector), показывает, что он делает намного больше, чем просто вызывает конечную точку HTTP. Дело в том, что если вы хотите, чтобы ваше сообщение было надежным, вам может понадобиться все это, или вы могли бы реализовать что-то легкое, которое соответствует вашим требованиям при связи с тройником. Кстати, я думаю, что причина, по которой он пытается разрешить раздел каждый раз, состоит в том, что первичный вариант мог быть изменен на реплику на другом узле. – yoape

+0

Я боюсь, что я не получаю реализацию в каждом сценарии сбоя, и у меня нет времени, чтобы проверить ее задумчиво. В моем случае извлечение конечных точек один раз или когда FabricClient сообщает об изменении раздела, будет достаточно. Когда Клиент выдает исключение, из-за сбоя связи с узлом, то реквизит также был бы разумным. Но получать каждый «наносекунда» слишком много. – coalmee

Смежные вопросы