HttpClient, похоже, замедляет работу моего приложения каждые ~3 минуты, освобождая при этом тонну памяти

#c# #memory #memory-management #memory-leaks #dotnet-httpclient

Вопрос:

Таким образом, в основном я запускаю программу, которая способна отправлять в среднем до 7000 HTTP-запросов каждую секунду 24/7, чтобы как можно быстрее обнаружить последние изменения на веб-сайте.

Однако в среднем каждые 2,5-3 минуты моя программа замедляется примерно на 10-15 секунд и увеличивается с ~7 тыс. rq/с до менее 1000.

Вот журналы из моей программы, где вы можете видеть количество запросов, которые она отправляет каждую секунду: https://pastebin.com/029VLxZG

При прокрутке журналов вниз вы можете видеть, что он замедляется каждые ~3 минуты. Пример: https://i.imgur.com/US0wPzm.jpeg

Сначала я подумал, что это ethernet-соединение моего сервера переходит во временный «ограниченный» режим, и я даже попытался связаться со своим хостом по этому поводу. Но затем я запустил 2 экземпляра своей программы одновременно, просто чтобы посмотреть, что произойдет, и я заметил, что, хотя проблема (простои) происходила в обоих случаях, это не всегда происходило одновременно (в зависимости от того, когда программа была запущена, если вы понимаете, что я имею в виду), что означало, что проблема исходила не от подключения к Интернету, а от самой моей программы.

Я исследовал немного больше и обнаружил, что, как только моя программа увеличивается с ~7 тыс. rq/с до ~700, на моем сервере освобождается много оперативной памяти.

Я взял 2 Скриншоты из последовательных секунд до и после простоя происходит (в том числе оперативной памяти метрик), для сравнения, и вы можете посмотреть их здесь: https://imgur.com/a/sk2TYQZ (обратите внимание, что я использую меньше темы здесь, из-за чего средняя «нормальная» скорость ~2К Ри/с вместо ~7К как упоминалось ранее)

Если вы хотите увидеть больше об этом, вот полная запись проблемы в видео, которое длится около 40 секунд: https://i.imgur.com/z27FlVP.mp4 — Как вы можете видеть, после освобождения оперативной памяти ее использование снова медленно увеличивается, прежде чем тот же процесс повторится каждые ~3 минуты.

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

 public static async Task<bool> HasChangedAsync(string endpoint, HttpClient httpClient)
{
    const string baseAddress = "https://example.com/";

    string response = await httpClient.GetStringAsync(baseAddress   endpoint);

    return response.Contains("example");
}
 

Одна вещь , которую я сделал, — я попытался заменить весь метод к await Task.Delay(25) тому return false времени, и это устранило проблему, использование оперативной памяти почти не увеличивалось.

Это наводит меня на мысль, что проблема HttpClient в / моих HTTP-запросах, и хотя я попытался заменить GetStringAsync метод, GetAsync используя как a HttpRequestMessage , так и HttpResponseMessage (и избавившись от них using ), поведение в итоге оказалось точно таким же.

Итак, я здесь, отчаянно нуждаюсь в исправлении и не обладаю достаточными знаниями о памяти, сборщике мусора и т. Д. (Если это вообще здесь нужно), Чтобы самостоятельно это исправить.

Пожалуйста, Переполнение стека, у вас есть какие-нибудь идеи?

Большое спасибо.

Комментарии:

1. GetStringAsync Скорее всего, вы генерируете большие строки, которые в конечном итоге окажутся в куче больших объектов. При вашей скорости запросов, без сомнения, ваша система будет выполнять кучу полных GCS, которые замедлят работу всей вашей системы.

2. Привет, @JohanP, и большое спасибо за ваш ответ. Я тоже об этом подумал. Ответ веб-страницы содержит более 5 тысяч символов, и я думаю, что как только я получу его, чтобы сохранить в своей response строковой переменной, он никогда не будет удален из памяти после этого. Однако каков был бы хороший способ сделать так, чтобы это произошло? Я не думаю, что установка моей string response переменной на null будет работать, и у меня нет другой идеи. И в этом ли вообще проблема?

3. Другой (возможно, глупый) вопрос: Может baseAddress endpoint ли строка, которую я создаю и передаю в качестве параметра GetStringAsync методу, также заполнять мою оперативную память, никогда не удаляясь из нее? Указание «(может быть, глупо)», Потому что у меня действительно мало знаний о том, как работает память, GC и т. Д., Поэтому это может показаться вопросом для начинающих

Ответ №1:

Лучше всего было бы передать ответ в потоковом режиме, а затем использовать его фрагменты, чтобы найти то, что вы ищете. Примером реализации может быть что — то вроде следующего:

 using var response = await Client.GetAsync(BaseUrl, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
string line = null;
while ((line = await reader.ReadLineAsync()) != null)
{
    if(line.Contains("example"))// do whatever
}

 

Комментарии:

1. По-прежнему генерируется много строк, хотя они будут небольшими и в GC Gen0. Еще более эффективная реализация будет содержать byte[] буфер и считывать фрагмент за раз, проверяя эквивалентные байты по ходу. Очевидно, более сложный из-за возможных совпадений с разделением, поэтому, возможно, излишний

2. Привет, Йохан, спасибо за твой ответ. Я попробовал это, однако использование оперативной памяти остается примерно таким же, и та же самая проблема все еще существует . К сожалению, не похоже, что замена моего кода на ваш что-то изменила. Может быть, покупка сервера с большим объемом оперативной памяти (в настоящее время у меня всего 4 ГБ) может помочь?

3. Обновление: Я попытался использовать сервер объемом 6 ГБ, хотя диспетчер задач постоянно показывал «доступно 2,5 ГБ», возникла та же проблема, поэтому я предполагаю, что это произойдет даже на сервере с 16, 32 или даже 64 ГБ оперативной памяти. Я действительно отчаянно нуждаюсь в исправлении и даже дал бы кому-нибудь хорошие чаевые в криптовалюте, чтобы помочь мне, так как эта проблема сильно влияет на мой бизнес.

4. @Мэтт, как ты создаешь экземпляр HttpClient ?

5. @JohanP Я создаю несколько HttpClient экземпляров при запуске моей программы, по 1 на прокси (у меня 9000 прокси, поэтому HttpClient при запуске моей программы создается 9000 экземпляров), а затем повторно использую их навсегда. Кроме того, как я уже упоминал в своем первоначальном посте: Одна вещь , которую я сделал, — я попытался заменить весь метод к await Task.Delay(25) тому return false времени, и это устранило проблему, использование оперативной памяти почти не увеличивалось. Поэтому я не думаю, что проблема связана с тем, как я создаю свой HttpClient экземпляр s, если у вас нет оснований так полагать?