Изучение промежуточного программного обеспечения сообщений ant (MsgBroker) для оптимизации YGC

задняя часть JVM алгоритм CMS
Оригинальное заявление: Автор этой статьи является оригинальным, спасибо человеку, средствам массовой информации, общественному номеру или веб-сайту без разрешения перепечатывать, нарушения в юридической ответственности.

Управляемое чтение


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

Что касается оптимизации сборщика мусора, первое и самое простое, о чем нужно подумать, — это настроить параметры JVM, влияющие на производительность сборщика мусора (такие как размер нового поколения и старого поколения, возраст перехода к старому поколению и даже тип Сборщик GC и т. д.), чтобы выжившие в старом поколении Количество объектов было как можно меньше, чтобы сократить время паузы GC. Однако, за исключением нескольких более общих методов настройки параметров, на которые можно ссылаться и которым можно следовать, в большинстве сценариев из-за разных размеров и жизненных циклов объектов, созданных разными приложениями, настройка параметров GC на самом деле является очень сложным и очень персонализированным процессом. ● Не существует универсальной стратегии настройки, подходящей для всех сценариев. В то же время, поскольку внутри виртуальной машины было сделано множество оптимизаций для минимизации времени паузы сборщика мусора, настройка параметров сборщика мусора может не дать ожидаемого эффекта и даже привести к обратным результатам.

Помимо шаблонной настройки параметров GC, в этой статье будет описан процесс оптимизации YGCT промежуточного программного обеспечения сообщений ant (MsgBroker) со 120 мс до 30 мс, а также обобщены более общие стратегии оптимизации YGC.

задний план


Когда дело доходит до GC, первая реакция многих людей заключается в том, что JVM приостановлена ​​​​на долгое время или FGC делает службу недоступной в течение длительного времени, но для базовых служб сообщений, таких как MsgBroker, это будет более чувствительно. к паузам сборщика мусора, а проблемы сборщика мусора, которые необходимо решить, более сложны. :

  1. Для обычных приложений, если время YGC меньше 100 мс, оптимизация, как правило, не требуется. Однако для базовых онлайн-сервисов, таких как MsgBroker, задержка, вызванная паузой GC, будет увеличиваться в несколько раз или даже в десятки раз в зависимости от сложности бизнеса Чрезмерное потребление времени YGC серьезно повредит производительности бизнеса в реальном времени и пользовательский опыт, поэтому его необходимо строго контролировать в течение 50 мс, и чем меньше, тем лучше. Однако с развитием новых функций и ростом объема сообщений мы обнаружили, что среднее время YGC для MsgBroker медленно увеличивалось до 50–60 мс, и даже среднее время YGC в некоторых компьютерных залах достигло 120 мс. .
  2. С одной стороны, чтобы обеспечить высокую надежность данных сообщений, MsgBroker использует БД для сохранения сообщений и использует кеш сообщений, чтобы уменьшить давление чтения на БД во время доставки сообщений; Чтобы строго гарантировать характер сообщений в реальном времени, MsgBroker использует модель push для доставки сообщений. Однако мы обнаружили, что когда возможности подписчика и отправителя не совпадают, возникает большое количество тайм-аутов доставки, что еще больше увеличивает нагрузку на память и сборщик мусора MsgBroker. Мощность потребления абонента повлияет на качество обслуживания MsgBroker, что неприемлемо в большинстве сценариев.
  3. В некоторых экстремальных сценариях (например, на стороне абонента есть проблема с пропускной способностью, большое количество сообщений продолжает доставляться с течением времени, и по мере увеличения отставания сообщений это может даже вызвать «лавину» нисходящих каналов, что приводит к длительному сбою восстановления), YGC занимает очень много времени, и также может произойти FULL GC, а причинами срабатывания в основном являются сбой продвижения и сбой параллельного режима, которые, как предполагается, вызваны чрезмерной фрагментацией памяти.

Следует отметить, что MsgBroker работает на обычной машине 4C8G с размером кучи 4G, поэтому он использует ParNew и сборщик мусора CMS.

Основы JVM


Чтобы лучше понять идеи и стратегии оптимизации YGC, упомянутые ниже, необходимо просмотреть базовые знания, связанные с GC.

Предположения поколений GC

Для традиционных, базовых реализаций сборщика мусора, поскольку они «останавливают мир» в течение всего рабочего процесса сборщика мусора, очень важно, как сократить время работы сборщика мусора. Для того, чтобы сократить время одной коллекции, большинство современных алгоритмов GC выполняют обработку генерации в куче памяти, размещая объекты разного возраста в разных пространствах памяти и используя их в соответствии с характеристиками объектов в разных пространствах Более эффективный сбор алгоритмы собираются отдельно, и это в основном основано на следующих предположениях о поколениях:

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

Основываясь на этом предположении, JVM делит память на молодое поколение и старое поколение, так что вновь созданные объекты выделяются из молодого поколения, и очень мало объектов нужно продвигать в старое поколение. Так как все молодое поколение обычно относительно невелико, оно занимает всего 1/3~1/2 всей памяти кучи, а выживаемость объектов в нем очень низкая.Очень подходит для рециркуляции с использованием алгоритма копирования, который может эффективно сократить время паузы во время YGC, уменьшить влияние на приложение.

Однако, если объекты в приложении не соответствуют вышеупомянутым предположениям о поколениях, например большое количество объектов со средним жизненным циклом, это серьезно повлияет на эффективность YGC.

Основной процесс YGC

YGC, основанный на алгоритме маркировки-копии, условно делится на следующие этапы:

  1. Находите и отмечайте живые объекты из GC Roots
  2. Скопируйте уцелевшие объекты в области Эдема и из области в область к.
  3. Очистите Эдем и районы

YGC трудоемкий анализ

При использовании сборщика G1 наиболее подробный лог GC можно сгенерировать через параметр -XX:+PrintGCDetails, по этому подробному логу можно просмотреть времязатратность каждого этапа GC, что удобно для оптимизации GC. Однако, если вы используете сборщики мусора ParNew и CMS, на самом деле нет официального способа просмотреть время, затрачиваемое на каждый этап сборки мусора. К счастью, AliJDK предоставляет аналогичную функцию, которая может распечатать подробные данные о затратах времени ParNew и CMS через PrintGCRootsTraceTime. Журнал сведений GC MsgBroker выглядит следующим образом:




Как видно из приведенных выше подробных логов, YGC в основном имеет следующие этапы:

  • Различные этапы корней: отметьте уцелевшие объекты из различных типов корневых объектов.
  • Сканирование старого поколения: сканирует ссылки от старого поколения в молодого поколения и копий выживших объектов в Эдем и из областей до района
  • другое: потребность в продвижении объекта копируется со старого на новое поколение

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

этап сканирования старого поколения

Чтобы оптимизировать затраты времени на фазу сканирования старого поколения целенаправленным образом, необходимо сначала понять, почему существует фаза сканирования старого поколения.

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

Для YGC при обходе и маркировке всех уцелевших объектов из GC Roots он в старости откажется от отслеживания объектов.Поскольку количество проходимых объектов уменьшается, эффективность GC можно значительно повысить. Но это создаст проблему: если объект молодого поколения не может пройти через GC Roots, а объект старого поколения ссылается на объект молодого поколения, как правильно пометить объект?

Чтобы решить эту проблему, одна из самых интуитивных идей состоит в том, чтобы обойти все старое поколение, чтобы найти объекты, которые содержат ссылки на молодое поколение, но это, очевидно, слишком дорого и идет вразрез с дизайном генерационного GC. Поэтому сборщик мусора должен уметь точно находить и отслеживать объекты, находящиеся в старом поколении, и с высокой эффективностью удерживать ссылки на молодое поколение, а также размещать эти объекты в той же позиции, что и корни сборщика мусора. этап сканирования истории.

На следующей диаграмме примерно показано, как YGC отслеживает и помечает живые объекты. Стрелки на рисунке представляют эталонную связь между объектами, в которой красная стрелка представляет собой ссылку от старого поколения к молодому поколению, эта часть объекта будет добавлена ​​к сканированию старого поколения, а синяя стрелка представляет собой Корни GC или объект молодого поколения для ссылок на старое поколение, эту часть объекта на самом деле не нужно отслеживать на этапе YGC.



Card marking

Вспоминая об упомянутых ранее поколенческих предположениях, одно из них состоит в том, что существует небольшое количество объектов, которые могут жить долго и вряд ли будут использовать молодые объекты. Это означает, что только очень небольшое количество объектов в старом поколении будет содержать ссылки на объекты в молодом поколении, очевидно, очень неэффективно находить эти объекты, обходя все старое поколение.

В общем, следующие две ситуации заставят объекты в старом поколении содержать ссылки на молодое поколение:
  • Объекты, содержащие ссылки на другие объекты молодого поколения, продвигаются к старому поколению.
  • Ссылка, хранящаяся в объекте старого поколения, изменяется, чтобы указывать на объект молодого поколения.

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

В реализации HotSpot JVM ParNew использует алгоритм маркировки карт для идентификации модификаций ссылок, хранящихся в объектах старого поколения. В этом алгоритме пространство старого поколения разделено на несколько карт размером 512 байт, а JVM использует массив для поддержания отношения отображения, и каждый бит в массиве представляет собой карту. Всякий раз, когда ссылка на объект в куче изменяется, соответствующая карта становится грязной. При выполнении YGC, просто сначала просканировав массив карт, вы можете быстро определить, в какой части пространства может находиться объект старого поколения, содержащий ссылку на объект молодого поколения, и избежать сканирования всего старого поколения, изменив пространство на время.




Оптимизация YGC
PargccardsPerstrideChunk Параметр

Поскольку сканирование старого поколения занимает большую часть времени в YGC, что является основной причиной высокой трудоемкости YGC, первое, что приходит на ум, — можно ли ускорить скорость сканирования сканирования старого поколения, настроив параметры ?

В фазе сканирования старого генерала старческий будет отсортирован в ряд равных зон, каждый из которых несет ответственность за обработку некоторых из них, включая сканирование соответствующих массивов карт и пространственное пространство, помеченное как грязное. Поскольку время обработки, необходимое для борьбы с различными старческими регионами, может быть большим, для предотвращения слишком простоя некоторых рабочих потоках количество старческих регионов, как правило, отсортировано, необходимо больше, чем количество рабочих потоков, а параметр PargccardsPerStrideChunk используется для управления нарезанной площадью области.

По умолчанию значение ParGCCardsPerStrideChunk равно 256. Поскольку каждой карте соответствует 512 байт пространства старого поколения, размер каждой области при сканировании составляет 128 КБ.Для кучи 4 ГБ будет более 30 000 областей, что сложнее, чем количество потоков на 4 порядка выше. На рисунке ниже показан параметр ParGCCardsPerStrideChunk, установленный на 256, 2K, 4K и 8K для запуска теста GC [1].Результаты показывают, что значение по умолчанию 256 в любом случае немного мало, и чем больше куча, тем длиннее время паузы GC больше, чем другие значения.




Учитывая, что размер кучи MsgBroker составляет 4 Гб, установка для параметра ParGCCardsPerStrideChunk значения 4 КБ является достаточно большой. Однако после модификации ParGCCardsPerStrideChunk ожидаемого эффекта достигнуто не было, фактически время YGC MsgBroker не уменьшилось никак. Это показывает, что может быть слишком много карт, которые настроены как грязные, что разрушает предположение поколения о сборщике мусора, делая саму задачу сканирования слишком тяжелой, а затраты времени намного больше, чем накладные расходы на частое переключение областей сканирования рабочими потоками. .

Оптимизация кеша сообщений

Основываясь на приведенных выше догадках, мы сосредоточим нашу оптимизацию на кэшировании сообщений. Чтобы избежать OOM, вызванного слишком большим количеством сообщений в кеше сообщений, MsgBroker реализует LRU-кэш и FIFO-кэш на основе LinkedHashMap. Как мы все знаем, LinkedHashMap является подклассом HashMap и дополнительно поддерживает двусвязный список для поддержания порядка итераций, однако это может привести к следующим трем проблемам:

  • В кеше сообщений могут быть какие-то недоставленные сообщения, и эти объекты сообщений находятся в старом поколении, в то же время при получении запроса сообщения от отправителя MsgBroker вставит сообщение в кеш, а эти объекты сообщений находятся в молодое поколение. Когда новые элементы постоянно вставляются в кэш сообщений, ссылочное отношение внутреннего двусвязного списка будет часто меняться, и во время YGC будет запущено крупномасштабное сканирование старых сообщений.
  • Когда возникает проблема на стороне подписчика, большое количество недоставленных сообщений будет кэшироваться.Даже если существует механизм устранения, такой как LRU, исключенные сообщения, скорее всего, будут повышены до старости.Будет ли это давление копирования и продвижения во время YGC, Будь то частота CMS GC, она значительно увеличится.
  • Размер сообщений, отправляемых разными сервисами, очень разный, при возникновении проблемы на стороне абонента большое количество сообщений будет повышено до старости, что может привести к большому объему фрагментации памяти и даже срабатыванию FGC.

Первая и вторая проблемы, описанные выше, увеличат затраты на сканирование и копирование на этапе сканирования старого поколения во время YGC, и на другом этапе будет продвигаться больше объектов. Вероятность этого возрастает.

Поскольку вставка, запрос, удаление и уничтожение кеша сообщений контролируются MsgBroker, если эта часть памяти больше не доверена JVM, но жизненный цикл полностью управляется MsgBroker, вышеуказанные проблемы GC могут быть решены. . . .

Когда дело доходит до невидимости JVM, наиболее интуитивно понятной идеей является использование решения вне кучи. Однако в описанном выше сценарии простое перемещение сообщения за пределы кучи не решит проблему полностью. Если вы хотите решить все вышеперечисленные проблемы, вам нужна структура данных, подобная LinkedHashMap, которая работает полностью за пределами кучи и должна иметь хорошие возможности параллельного доступа без потери производительности.

ОХК является достаточно простым, навязчивым 外 外 库 库 库 方案 方案 方案 方案 方案 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽抽抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽 抽Также можно использовать Поскольку OHC обеспечивает полную реализацию кэша без кучи, он поддерживает параллельную запись и запрос без блокировки, а также поддерживает LRU. Это очень близко к требованию MsGBroker. По принципу не повторять колесо, мы решаем кэшировать.

По сравнению с кэшем сообщений On-Heap, использование кэша сообщения Off-Heak, укажется еще одной копией копии памяти. Однако из фактических данных тестовых данных под заданной пропускной способностью RT в кэш-памяти не ухудшается, не ухудшается, только CPU Util немного улучшен (от 60% до 63%), что совершенно приемлемо. В пределах диапазона.
Благодаря приведенной выше оптимизации кэша сообщений и установке для параметра ParGCCardsPerStrideChunk значения 4K время YGC для большинства онлайн-компьютеров сокращается с 60 мс до примерно 30 мс, а также значительно снижается частота CMS GC.

Однако для тех машин в компьютерном зале, где время YGC особенно велико, даже за счет оптимизации кеша сообщений время YGC сокращается только со 120 мс до примерно 80 мс, что все еще много, а этап сканирования старого поколения по-прежнему занимает много времени. большое количество времени часть времени.


Справочник по объектам сообщений и оптимизация жизненного цикла

Наблюдая и обобщая ситуацию с GC онлайн-машин, мы обнаружили, что количество соединений машин со временем YGC около 50 мс относительно нормально, а количество соединений в основном поддерживается на уровне около 5000, в то время как машины со временем YGC около Подключено 120 мс.Числа приближаются или даже превышают 20 000. Основываясь на этих выводах, вполне вероятно, что проблема YGC тесно связана с коммуникационным уровнем.

Первоначально уровень сетевых коммуникаций MsgBroker использует сетевую структуру Gecko, разработанную самостоятельно. Gecko по умолчанию выделяет 64 КБ памяти для каждого сетевого подключения. GC, что сильно ограничивает производительность MsgBroker. В этом контексте MsgBroker использует сетевую структуру собственной разработки Bolt (на основе Netty) для реконструкции сетевого уровня и по умолчанию выделяет память, используемую сетевым подключением, за пределы кучи, что решает проблему производительности при больших числах. соединений. В то же время тест производительности Bolt также показывает, что даже при 100 000 подключений производительность сервера не зависит от количества подключений.

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

Как показано в следующем коде, при декодировании запроса сообщения RequestDecoder сначала попытается проанализировать часть заголовка сообщения.Если в byteBuf достаточно данных, RequestDecoder полностью проанализирует заголовок и сохранит его в requestCommand. Таким образом, если данных в byteBuf недостаточно для разбора основной части сообщения, следующее декодирование также может начаться непосредственно с основной части, что снижает накладные расходы на повторное чтение.





RequestDecoder содержит ссылку на RequestCommand, которая предназначена для предотвращения повторного чтения byteBuf. Однако это создает следующие проблемы:

  • RequestDecoder в основном относится к старому поколению, а RequestCommand — к молодому поколению. Когда соединение на сервере продолжает получать запросы сообщений, эталонные отношения между старым поколением и молодым поколением будут продолжать меняться, что увеличит давление сканирования старого поколения во время YGC.Чем больше соединений, тем больше давление.
  • Для соединения с небольшим количеством сообщений, несмотря на то, что эталонное отношение не будет часто меняться, поскольку RequestDecoder будет хранить ссылку на RequestCommand в течение длительного времени, сообщение не может быть своевременно переработано, и его легко повысить до старость из-за достижения определенного возраста.Увеличьте давление копирования во время YGC. Аналогично, чем больше количество соединений, тем выше давление.

На самом деле решение тоже очень простое, так что RequestDecoder больше не содержит ссылку на RequestCommand. Если во время декодирования доступное для чтения содержимое в byteBuf недостаточно полно для разбора сообщения, индекс чтения будет отброшен к исходной позиции, и операция декодирования будет прекращена до тех пор, пока в byteBuf не будет достаточно данных. Хотя возможны повторные чтения, эти накладные расходы вполне приемлемы по сравнению с GC.

Благодаря приведенной выше оптимизации, даже на машинах с особенно большим количеством подключений время YGC было дополнительно сокращено с 80 мс до 30 мс.


Самозащита в нештатных ситуациях на стороне подписки

В качестве промежуточного программного обеспечения сообщений в режиме push MsgBroker может эффективно обеспечивать доставку сообщений в режиме реального времени в любой ситуации. Однако, если подписчик имеет частые узкие места GC, CPU или IO, или даже RT нисходящего канала становится высоким, скорость потребления сообщений не может соответствовать скорости производства сообщений, и легко вызвать большое количество реальных ошибок. время подтолкнуло сообщения для накопления в обработке сообщений подписчика.В очереди пула потоков, и большинство сообщений могут не иметь возможности быть выполненным потоком до того, как они будут вне очереди, они были оценены как время ожидания доставки MsgBroker, что приводит к большому количеству ошибок времени ожидания доставки, что приводит к необходимости повторного воспроизведения большого количества сообщений.

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

Хотя приведенные выше оптимизации могут эффективно решить проблему фрагментации памяти и больших затрат времени YGC в обычных сценариях. Однако в аномальных сценариях потребление времени YGC по-прежнему велико (в сценарии тайм-аута, построенном лабораторией, хотя количество подключений сохраняется на однозначном уровне, среднее потребление времени YGC также возрастает до 147 мс), а через выше методы оптимизации, время потребления YGC уменьшено только со 147 мс до 83 мс. В ходе дальнейшего анализа мы обнаружили:

  • Поскольку время ожидания доставки по умолчанию для MsgBroker составляет 10 секунд, в отличие от других сбоев доставки, при возникновении большого количества тайм-аутов доставки сообщения будут оставаться в памяти MsgBroker не менее 10 секунд, что окажет большое давление на YGC.
  • Поскольку операция обновления после сбоя доставки является асинхронной, и чтобы избежать влияния операции обновления сообщения на новое сообщение, операция обновления обычно не требует слишком много ресурсов потока. При большом количестве сбоев доставки операции обновления сообщения, скорее всего, задерживаются в памяти из-за большого количества задач.


Чтобы решить вышеуказанные проблемы, MsgBroker реализует адаптивный алгоритм ограничения тока доставки, как показано на следующем рисунке. Основная идея алгоритма заключается в том, что сервер будет непрерывно оценивать мощность потребления абонента по результатам потребления абонента, и выполнять доставку с ограничением по току в соответствии с предполагаемым потреблением абонента.Останьтесь еще на 10 с, и нет необходимости выполнять операции обновления БД. Таким образом защищен подписчик, что способствует быстрому перевариванию накопившихся сообщений, а также сервер защищен от влияния подписчика, что дополнительно снижает нагрузку на БД.




Внедрив адаптивное ограничение тока доставки, в среде лабораторных испытаний время YGC MsgBroker в ненормальных сценариях было дополнительно сокращено с 83 мс до 40 мс, вернувшись к нормальному уровню.

Сводка по оптимизации YGC

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

reference


  • Garbage collection in the HotSpot JVM (https://www.ibm.com/developerworks/library/j-jtp11253/)
  • Secret HotSpot option improving GC pauses on large heaps (http://blog.ragozin.info/2012/03/secret-hotspot-option-improving-gc.html)
  • OHC - An off-heap-cache (https://github.com/snazy/ohc/)

Официальная учетная запись: распределенная архитектура финансового уровня (Antfin_SOFA)