Имея дело с масштабируемостью, существует ряд дорогих (с точки зрения производительности и / или масштабируемости) классов для их создания (например: Microsoft.ServiceBus.Messaging.QueueClient, Microsoft.Azure.Documents.Client.DocumentClient, StackExchange.Redis .ConnectionMultiplexer и System.Net.Http.HttpClient и т. Д.)

Класс, о котором я хочу поговорить, это: System.Net.Http.HttpClient, который используется для HTTP-вызовов внешних API-интерфейсов из нашего кода C #, и создание экземпляра этого класса считается одним из самых дорогих.

Итак, вот простой класс HttpClientHelper, который позволяет выполнять HTTP-вызовы:

namespace HttpClientInstantiationTest.Tests 
{ 
public class HttpClientHelper 
{ 
     public async Task<HttpResponseMessage> CallHttpClientGetAsync(string requestUri, string authHeader) 
     { 
       using (var client = new HttpClient()) 
       { 
          client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authHeader); 
          var response = await client.GetAsync(requestUri); 
          return response; 
       } 
    } 
} 
}

В веб-приложении такой способ создания экземпляра HttpClient не считается масштабируемым при большой нагрузке запросов. Судя по опыту людей, это может привести к некоторым SocketExceptions, создающим объекты.

Чтобы увидеть поведение этого метода в действии, у нас может быть простой метод модульного тестирования, который вызывает метод GET следующим образом:

[TestMethod] 
public async Task GetAsyncTestNotScalable() 
{ 
   var sut = new HttpClientHelper(); 
   var authHeader = Guid.NewGuid().ToString(); 
   var result = await sut.CallHttpClientGetAsync(endpoint, authHeader); 
   var content = await result.Content.ReadAsStringAsync();  
   Assert.AreEqual(authHeader,content.Replace("\"", "")); 
}

Теперь, чтобы выполнить нагрузочный тест для этого метода, вы можете щелкнуть правой кнопкой мыши на тестовом проекте и выбрать Добавить, и вы можете добавить нагрузочный тест из меню, которое вы видите ниже:

Когда вы щелкаете по нагрузочному тесту, пройдите через мастер и выберите метод теста «GetAsyncTestNotScalable», чтобы создать нагрузочный тест, а затем запустите нагрузочный тест. Я провел тест со всеми значениями по умолчанию и шагами для загрузки пользователей, и вот результат нагрузочного теста.

GetAsyncTestNotScalable: Среднее время тестирования (миллисекунда): 140 мс

Ключевые показатели:

Легендарные помощники по ключевым показателям:

И в этом случае нет результата неудачного теста, что означает, что во всех тестах, какой бы ключ мы ни передали в качестве заголовка авторизации, мы получили тот же ключ обратно.

Теперь мы попробуем создать масштабируемый класс для создания экземпляра HttpClient следующим образом:

namespace HttpClientInstantiationTest.Tests 
{ 
   public class HttpClientHelperScalable 
   { 
      private static readonly HttpClient httpClient; 
      static HttpClientHelperScalable () 
      { 
         httpClient = new HttpClient(); 
      } 
      
      public async Task<HttpResponseMessage> CallHttpClientGetNoLockUsingRequestAsync(string requestUri, string authHeader) { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri); 
      request.Headers.Authorization = new AuthenticationHeaderValue(authHeader); 
  
       var response = httpClient.SendAsync(request); 
    
       return await response; 
      } 
} 
}

Как видите, подход состоит в том, чтобы иметь экземпляр HttpClient в качестве статического члена класса и создавать его.

Примечание. Здесь очень важно знать, что для реализации со статическим конструктором мы использовали метод httpClient.SendAsync (), чтобы мы могли передать авторизацию заголовок как закрытый член метода, так что, поскольку httpClient является статическим членом, заголовки авторизации для разных потоков в многопоточных сценариях не заменяются другими запросами.

Теперь мы повторяем описанные выше шаги и создаем тестовый метод для вызова этого нового масштабируемого метода, и вот как он будет выглядеть:

[TestMethod] 
public async Task GetAsyncTestScalable() 
{ 
    var sut = new HttpClientHelperScalable(); 
    var authHeader = Guid.NewGuid().ToString(); 
    var result = await sut.CallHttpClientGetNoLockUsingRequestAsync(endpoint, authHeader); 
    var content = await result.Content.ReadAsStringAsync();   
    Assert.AreEqual(authHeader, content.Replace("\"", "")); 
}

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

GetAsyncTestScalable: Среднее время тестирования (миллисекунда): 12 мс

Ключевые показатели:

Легендарные помощники по ключевым показателям:

да, как видите, сравнение производительности очевидно: 140 мс для немасштабируемого против 12 мс для масштабируемого.

Вы можете найти исходный код этого теста на Github здесь: https://github.com/aramkoukia/HttpClient-Instantiation-Test

Надеюсь, это сделает ваш клиентский код немного быстрее и более масштабируемым с таким огромным приростом производительности!