Как Jetty обрабатывает потоки и пулы потоков, он использует слишком много памяти - PullRequest
0 голосов
/ 10 июля 2020

Работает микросервис сервера со схемой:

схема потоков приложения

Но я наблюдаю, что поток инициализации 24 затем увеличивает количество потоков запросов на 32, а когда обрабатываемые потоки сокращаются до 24 потоков.

Процесс закрытия потока медленный, что медленное высвобождение памяти тоже !!

Как решить эту проблему? Спасибо за просмотр.

Ответы [ 2 ]

0 голосов
/ 14 июля 2020

Спасибо @Joakim Erdfelt,

У меня есть обновленное изображение на диаграмме ниже:

введите описание изображения здесь

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

журнал:

2020-07-14 10: 43: 47.264: DBUG: oeji.ManagedSelector: dev.cash24-connector-15: Selector sun.nio.ch. KQueueSelectorImpl@a9a2d79 ожидание с 1 ключи 2020-07-14 10: 43: 47.264: DBUG: oeji.ManagedSelector: dev.cash24-connector-15: Selector sun.nio.ch. KQueueSelectorImpl@a9a2d79 проснулся от выбора, выбрано 1/1/1 2020-07 -14 10: 43: 47.264: DBUG: oeji.ManagedSelector: dev.cash24-connector-15: Selector sun.nio.ch. KQueueSelectorImpl@a9a2d79 обработка 1 ключей, 0 обновлений 2020-07-14 10: 43: 47.264: DBUG : oeji.ManagedSelector: dev.cash24-connector-15: выбран 1 sun.nio.ch. SelectionKeyImpl@45dd25a SocketChannelEndPoint@5a66441f {l = / 127.0.0.1: 86, r = / 127.0.0.1: 50974, OPEN, fill = FI, flush = -, to = 9/30000} {io = 1/1, kio = 1, kro = 1} -> HttpConnection@159cbb18 [p = HttpParser {s = START, 0 of -1} ,g=HttpGenerator@2443cd34 { s = START}] => HttpChannelOverHttp@54a1b123 {s=HttpChannelState@509e2f6f {s = IDLE rs = БЛОКИРОВКА os = OPEN is = IDLE awp = false se = false i = true al = 0}, r = 1, c = false / false , a = IDLE, uri = null, age = 0} 2020-0 7-14 10: 43: 47.264: DBUG: oeji.ChannelEndPoint: dev.cash24-connector-15: onSelected 1-> 0 r = true w = false для SocketChannelEndPoint@5a66441f {l = / 127.0.0.1: 86, r = /127.0.0.1:50974,OPEN,fill=FI,flush=-,to=9/30000}{io=1/0,kio=1,kro=1}->HttpConnection@159cbb18[p=HttpParser{s= START, 0 из -1} ,g=HttpGenerator@2443cd34 {s = START}] => HttpChannelOverHttp@54a1b123 {s=HttpChannelState@509e2f6f {s = IDLE rs = BLOCKING os = OPEN is = IDLE awp = false se = false i = true al = 0 }, r = 1, c = false / false, a = IDLE, uri = null, age = 0} 2020-07-14 10: 43: 47.265: DBUG: oeji.ChannelEndPoint: dev.cash24-connector-15: task CEP:SocketChannelEndPoint@5a66441f {l = / 127.0.0.1: 86, r = / 127.0.0.1: 50974, OPEN, fill = FI, flush = -, to = 9/30000} {io = 1/0, kio = 1, kro = 1} -> HttpConnection@159cbb18 [p = HttpParser {s = START, 0 из -1} ,g=HttpGenerator@2443cd34 {s = START}] => HttpChannelOverHttp@54a1b123 {s=HttpChannelState@509e2f6f {s = IDLE rs = БЛОКИРОВКА os = OPEN is = IDLE awp = false se = false i = true al = 0}, r = 1, c = false / false, a = IDLE, uri = null, age = 0}: runFillable: БЛОКИРОВКА 14.07.2020 10: 43: 47.265: DBUG: oejuts.EatWhatYouKill: dev.cash24-connector-15: EatWhatYouKill@3fb1549b/SelectorProducer@ea6147e / PRODUCING / p = false / QueuedThreadPool [dev.cash24-connector] @ 9d5509a {НАЧАЛО, 0 <= 6 <= 6, i = 1, r = 0, q = 0} [NO_TRY] [pc = 0, pic = 0, pec = 1, epc = 0] @ 2020-07-14T10: 43: 47.265 + 07: 00 m = PRODUCE_EX ECUTE_CONSUME t=CEP:SocketChannelEndPoint@5a66441f {l = / 127.0.0.1: 86, r = / 127.0.0.1: 50974, OPEN, fill = FI, flush = -, to = 10/30000} {io = 1/0, kio = 1, kro = 1} -> HttpConnection@159cbb18 [p = HttpParser {s = START, 0 из -1} ,g=HttpGenerator@2443cd34 {s = START}] => HttpChannelOverHttp@54a1b123 {s=HttpChannelState@509e2f6f {s = IDLE rs = БЛОКИРОВКА os = OPEN is = IDLE awp = false se = false i = true al = 0}, r = 1, c = false / false, a = IDLE, uri = null, age = 0}: runFillable: БЛОКИРОВКА / БЛОКИРОВКА 2020-07- 14 10: 43: 47.265: DBUG: oejut.QueuedThreadPool: dev.cash24-connector-15: queue CEP:SocketChannelEndPoint@5a66441f {l = / 127.0.0.1: 86, r = / 127.0.0.1: 50974, OPEN, fill = FI, flush = -, to = 10/30000} {io = 1/0, kio = 1, kro = 1} -> HttpConnection@159cbb18 [p = HttpParser {s = START, 0 из -1} ,g=HttpGenerator@2443cd34 {s = START}] => HttpChannelOverHttp@54a1b123 {s=HttpChannelState@509e2f6f {s = IDLE rs = БЛОКИРОВКА os = OPEN is = IDLE awp = false se = false i = true al = 0}, r = 1, c = false / false, a = IDLE, uri = null, age = 0}: runFillable: BLOCKING startThread = 0

но я вижу, что закрытие потока происходит медленно в мониторе, когда клиент получил ответ (поток 29 открыт, а затем закрыт)

журнал:

2020-07-14 10: 44: 47.275: DBUG: oejut.QueuedThreadPool: dev.cash24-connector-29: сжатие QueuedThreadPool [dev.cash24-connector] @ 9d550 9a {НАЧАЛО, 0 <= 6 <= 6, i = 1, r = 0, q = 0} [NO_TRY] 2020-07-14 10: 44: 47.276: DBUG: oejut.QueuedThreadPool: dev.cash24-connector- 29: поток [dev.cash24-connector-29,5, main] завершился для QueuedThreadPool [dev.cash24-connector] @ 9d5509a {НАЧАЛОСЬ, 0 <= 5 <= 6, i = 0, r = 0, q = 0 } [NO_TRY] </p>

0 голосов
/ 10 июля 2020

Ваша диаграмма неверна.

Jetty имеет один ThreadPool для всех операций.

Thread is a Thread is a Thread.

Нет различия между selector / acceptor / requests / asyn c processing / asyn c read / asyn c write / websocket / proxy / client / et c.

По последним подсчетам, в Jetty 9.4.30 .v20200611, в Jetty есть примерно 93 различных объекта, которые могут использовать поток из ThreadPool, и все зависит от того, что делает ваше приложение, какие сетевые протоколы вы используете и какие функции различных API в Jetty вы решите использовать. используйте.

Вернуться к диаграмме.

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

Когда поток используется для целей Acceptor, он вообще не участвует в обработке запросов / ответов. Он принимает соединение и передает его управляемому селектору для обработки фактического принятия и последующего управления селектором для этого нового соединения.

Селекторы не являются потоками. Есть Selector Manager, который использует поток, он управляет селекторами, с которыми работает слой NIO.

Наличие более одного настроенного селектора полезно только в том случае, если вы приближаетесь к 60000 активных одновременных событий селектора на многоядерной машине с более чем 8 ядрами, выделенными для Jetty. (не делайте ошибки, приравнивая одновременные соединения к параллельным событиям селектора, вы можете легко иметь 200 000 одновременных соединений с максимумом 16 событий параллельного селектора. Вам необходимо контролировать свою JVM в производственной среде, чтобы знать, какова на самом деле нагрузка селектора приложения)

Jetty использует стратегию выполнения потока «Eat What You Kill», что означает, что «очередь пула потоков» не так проста, как на вашей диаграмме (с fetch / pu sh).

Создание потоков - это дорогостоящая вещь (с точки зрения времени), поэтому они сохраняются в пуле так долго, как могут. Создание потока на правильно настроенных JVM часто может занять больше времени, чем операция G C. (Да, мы знаем, что это спорное утверждение, но наш опыт на разных машинах, сред и виртуальных машинах на протяжении последних 20 лет показали, что это будет последовательно верно, даже на современных виртуальных машинах, как OpenJDK 14)

1024 * Создание потока в пуле потоков может происходить пакетами, в зависимости от нагрузки. «Нагрузкой» могут быть новые соединения, c общий трафик по соединениям или даже такие простые, как требования, которые вы предъявляете к различным API-интерфейсам в вашем приложении.

Удаление холостого потока намеренно не выполняется время для снижения и / или устранения чрезмерных затрат на создание потоков, наблюдаемых во время всплесков нагрузки.

Jetty использует интерфейс org.eclipse.jetty.util.thread.ThreadPool для работы с пулами потоков.

Каждый ThreadPool имеет a ThreadPoolBudget, в котором участвуют различные API-интерфейсы Jetty, чтобы указать необходимые операционные требования к потокам. В Jetty есть много API-интерфейсов, которые, как только вы начнете их использовать, они автоматически вызывают необходимость «зарезервировать» X потоков в ThreadPool, чтобы они всегда были доступны для этого API. Пример: у вас есть новое соединение HTTP / 2, оно увеличит счетчик «зарезервированных потоков» в пуле потоков на 1 на время существования физического соединения (для обработки сеансов HTTP / 2 для различных подзапросов), наличие физического соединения не означает, что поток используется в пуле потоков, только когда его запускает селектор и / или использование API, а затем он просто обычно использует пул потоков, используя любой поток, доступный в настоящее время. Это позволяет реализации ThreadPool управлять этой потребностью в «зарезервированных потоках». Это гарантирует, что всегда есть поток для обработки низкоуровневого поведения сеансов и подзапросов HTTP / 2 (в этом примере проще представить физическое соединение как вспомогательный селектор для сеансов HTTP / 2, которыми он управляет). Эта концепция «зарезервированного потока» имеет решающее значение для правильной работы во многих критических задачах, в противном случае вы столкнетесь с нехваткой потоков и множеством критических задач.

Минимальный и максимальный потоки, необходимые вашему приложению, зависят от поведения вашего приложения и загружается, а не какая-то произвольная конфигурация запуска.

В зависимости от выбранной вами реализации org.eclipse.jetty.util.thread.ThreadPool у вас есть разные варианты настройки того, как он обрабатывает такие вещи, как незанятые потоки и удаление незанятых потоков.

В QueuedThreadPool (наиболее часто используемом ThreadPool в мире Jetty) тайм-аут простоя управляет, когда поток в пуле идентифицируется как «бездействующий» и «подходящий для остановки».

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

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

Еще один предмет разногласий с Разработчики, желающие настроить потоки в Jetty, заключаются в том, что нет абсолютно никакой связи между одним потоком и одним обменом запрос / ответ. Один обмен запрос / ответ может обрабатываться во многих потоках (а иногда и в нескольких потоках, в зависимости от используемых вами API).

Попытка контролировать что-либо с помощью обработки запроса / ответа путем манипулирования пулом потоков приведет к не работают (например, попытка ограничить количество активных одновременных запросов, установив низкое максимальное количество потоков).

Также будьте осторожны с использованием ThreadLocal в ваших приложениях, они работают, но вы должны понимать объем нити, к которой вы его прикрепляете, чтобы добиться с ними долгосрочного успеха.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...