Вызывает ли повторное использование HttpClient с одновременными запросами их очередь? - PullRequest
0 голосов
/ 14 апреля 2020

Я пишу адаптивный API. Мы должны обрабатывать 10 запросов в секунду.

Проблема в том, что отправка ответа занимает полсекунды. Таким образом, вы можете себе представить, что сервер перегружен быстро.

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

Однако у меня есть сомнения по поводу использования один экземпляр HttpClient является правильным подходом. Совет, как только кто-то упоминает HttpClient, всегда создает один его экземпляр.

У меня есть его статический экземпляр c. Хотя это потокобезопасно, по крайней мере для PostAsync, я должен действительно создать 10 HttpClients (или пул HttpClients), чтобы иметь возможность быстрее отправлять данные?

Я предполагаю, что в течение полсекунды он отправляет, что он не позволит вам отправить другие 'postasyn c'. Однако я не могу подтвердить это поведение.

Большинство тестов и ресурсов просто смотрят на отправку запросов синхронно, то есть один за другим (т.е. await postasync)

Однако для моего случая использования мне нужно отправлять несколько одновременно, т.е. из отдельных потоков. Единственный способ ответить на 10 сообщений в секунду, которые занимают по полсекунды каждый, - это отправить пять одновременных сообщений назад - не пять в очередь на go из одного за другим, а пять одновременных сообщений.

Я не могу найдите любую документацию о том, как HttpClient справляется с этим. Я видел только несколько упоминаний о том, что у него есть пул соединений, но неясно, будет ли он на самом деле выполнять несколько соединений одновременно, или мне нужно создать небольшой пул из 5 httpclients для ротации.

Вопрос: Поддерживает ли один экземпляр HttpClient несколько соединений одновременно?

И я не имею в виду просто позволить вам вызывать postasyn c много раз в поточно-ориентированном виде, прежде чем он закончил, но я имею в виду, действительно ли мы открываем пять одновременных подключений и отправляем данные через каждое из них в одно и то же время?

Например, вы отправляете на Луну пятьдесят 10-байтовых файлов, и есть задержка 10 секунд. Ваша программа подхватывает все пятьдесят файлов и делает почти пятьдесят мгновенных обращений к HttpClient.PostAsyn c.

Предполагая, что служба прослушивания может это поддерживать, будут ли вызовы из нескольких потоков в HttpClient.PostAsyn c открывать пятьдесят подключений (или что-то еще, с некоторым ограничением, но больше 1) и отправкой данных, а это означает, что сервер получает все пятьдесят файлов ~ 10 секунд спустя?

Или это будет внутренняя очередь их в очередь, и вы в конечном итоге ожидание 10x50 = 500 секунд?

Ответы [ 2 ]

1 голос
/ 14 апреля 2020

Однако я не могу подтвердить это поведение.

Почему бы и нет? Просто создайте webapi с задержкой в ​​несколько секунд и протестируйте вызов с HttpClient. Или вы можете использовать такой сервис, как Медленно .

static async Task Main(string[] args)
{
    var stopwatch = Stopwatch.StartNew();
    await Serial(stopwatch);
    Console.WriteLine($"Serial took {stopwatch.Elapsed}");
    stopwatch.Restart();
    await Concurrent(stopwatch);
    Console.WriteLine($"Concurrent took {stopwatch.Elapsed}");
}

private static async Task Serial(Stopwatch stopwatch)
{
    for (var i = 0; i != 5; ++i)
    {
        var client = new HttpClient();
        await MakeRequest(stopwatch, client);
    }
}

private static async Task Concurrent(Stopwatch stopwatch)
{
    var client = new HttpClient();
    var tasks = Enumerable.Range(0, 5).Select(async _ => { await MakeRequest(stopwatch, client); }).ToList();
    await Task.WhenAll(tasks);
}

private static async Task MakeRequest(Stopwatch stopwatch, HttpClient client)
{
    Console.WriteLine($"{stopwatch.Elapsed}: Issuing request.");
    var response = await client.GetStringAsync("http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.google.com");
    Console.WriteLine($"{stopwatch.Elapsed}: Received {response.Length} bytes.");
}

Выход для меня (из США):

00:00:00.0463664: Issuing request.
00:00:04.2560734: Received 49237 bytes.
00:00:04.2562498: Issuing request.
00:00:07.6731908: Received 49247 bytes.
00:00:07.6734158: Issuing request.
00:00:11.0882322: Received 49364 bytes.
00:00:11.0883803: Issuing request.
00:00:14.4990981: Received 49294 bytes.
00:00:14.4993977: Issuing request.
00:00:17.9082167: Received 49328 bytes.
Serial took 00:00:17.9083969
00:00:00.0025096: Issuing request.
00:00:00.0252402: Issuing request.
00:00:00.0422682: Issuing request.
00:00:00.0588887: Issuing request.
00:00:00.0755351: Issuing request.
00:00:03.4631815: Received 49278 bytes.
00:00:03.4632073: Received 49293 bytes.
00:00:03.4844698: Received 49313 bytes.
00:00:03.4913929: Received 49308 bytes.
00:00:03.4915415: Received 49280 bytes.
Concurrent took 00:00:03.4917199

Вопрос: Экземпляр HttpClient поддерживает несколько соединений одновременно?

Да.

1 голос
/ 14 апреля 2020

Кажется, что нет предела, или, по крайней мере, он высокий.

Я сделал приложение веб-API по умолчанию, изменил метод контроллера шаблона:

// GET api/values
public async Task<IEnumerable<string>> Get()
{
    Debug.Print("Called");
    Thread.Sleep(100000);
    return new string[] { "value1", "value2" };
}

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

List<Task> tasks = new List<Task>();

var task1 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task2 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task3 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task4 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task5 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task6 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task7 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task8 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var task9 = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskA = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskB = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskC = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskD = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));
var taskE = Task.Run(() => httpClient.GetAsync("http://localhost:57984/api/values"));


await Task.WhenAll(task1, task2, task3, task4, task5, task6, task7, task8, task9, taskA, taskB, taskC, taskD);

Я запустил их, и слово 'Called' было зарегистрировано 14 раз.

Поскольку Thread.Sleep будет блокировать ответ, это должно означать, что было одновременных 14 соединений.

Есть два свойства, которые могут повлиять на максимальное количество соединений, что я ' мы нашли, посмотрев в Google:

ServicePointManager.DefaultConnectionLimit, который по умолчанию равен 2

, и HttpClientHandler.MaxConnectionsPerServer, который также равен 2.

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

Я заметил, после многих остановок и запуска моих тестовых проектов, что новые подключения выполнялись гораздо медленнее. Я предполагаю, что я переполнил пул соединений.

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

...