возникает проблема
Кратко представим предысторию нашего сервиса. Наш сервис представляет собой микросервисную архитектуру, в которой используется инфраструктура RPC, подобная dubbo, и несколько сегментов семейства Spring. Общая структура может относиться к следующему рисунку.
Служба Java использует сборщик мусора CMS.
Однажды я неожиданно получил предупреждение о том, что экземпляр (то есть Java-приложение) сгенерировал журнал FullGC.Как показано в сервисе, отмеченном красным на рисунке выше, информация журнала FullGC выглядит следующим образом:
2020-07-25T14:55:07.481+0800: 155286.031: [GC (CMS Initial Mark) [1 CMS-initial-mark: 13965759K(13965760K)] 16377019K(16496128K), 0.1756459 secs] [Times: user=2.53 sys=0.00, real=0.18 secs]
2020-07-25T14:55:07.657+0800: 155286.207: [CMS-concurrent-mark-start]
2020-07-25T14:55:08.507+0800: 155287.057: [Full GC (Allocation Failure) 2020-07-25T14:55:08.507+0800: 155287.057: [CMS2020-07-25T14:55:11.932+0800: 155290.482: [CMS-concurrent-mark: 4.268/4.275 secs] [Times: user=40.27 sys=1.78, real=4.27 secs]
(concurrent mode failure): 13965759K->13965759K(13965760K), 18.4763209 secs] 16496127K->14470127K(16496128K), [Metaspace: 190291K->190291K(1234944K)], 18.4769704 secs] [Times: user=43.71 sys=2.18, real=18.47 secs]
Видно, что это связано с тем, что когда старое поколение CMS повторно использует параллельную метку, оно обнаруживает, что памяти старого поколения недостаточно, и принудительно запускает FullGC.Заварудо, и потому что приложение STW, но запросы все еще накапливаются, что приводит к непрерывному FullGC, без самовосстановления
Общий процесс утилизации старости CMS показан на следующем рисунке.
Стоп-лосс и устранение неполадок
остановить потери
Метод стоп-лосс очень простой, естественно перезапускаем приложение, при перезапуске приложение удаляется из реестра, а трафик распределяется по другим сервисам. Перезапустите его и пейте больше воды. (PS: На самом деле есть место для оптимизации. Например, некий механизм обнаруживает, что служба возьмет на себя инициативу удалить его из реестра при выполнении FullGC, а затем присоединится к реестру, чтобы принять запрос после FullGC завершил самовосстановление. Весь процесс выполняется автоматически без ручного вмешательства)
Поиск проблемы
Естественно, проблему следует довести до конца, а потом начнется расследование.
Почему FullGC?
Прямые причины FullGC могут относиться к следующим категориям:
- Активно вызывается в приложении
System.gc()
Это в принципе невозможно по этой причине.Во-первых, никто не будет вызывать этот метод на досуге, да и глобальный поиск кода тоже подтверждает результат; во-вторых, даже если кто-то действительно вызовет этот методSystem.gc()
идти спредложениеJVM для поддержки FullGC, наш сервис включает этот параметр в параметрах запуска-XX:ExplicitGCInvokesConcurrent
, смысл этого параметра в том, чтобы отображать вызывающийSystem.gc()
Преобразован в параллельный GC CMS, поэтому он не будет запускать FullGC - Недостаточно места в области метода (метапространство)
Общая причина этого заключается в том, что в коде используется большое количество динамических прокси-серверов и генерируется большое количество прокси-классов, занимающих область методов.Если причина в этом, все службы должны сообщать о проблеме FullGC.Однако, старая память других машин очень стабильна.Так что проверяйте - Недостаточно места в прямой памяти
Недостаток прямого пространства памяти обычно вызван использованием кода, такого как nio - Недостаточно места для старости
Объект родился в новом поколении, успешно дожил до старости, пережив минорные GC, и продолжал питаться и ждать в старости, пока большое количество объектов не было съедено и не умерло в старости и старость не наполнилась , вызовет FullGC
почему только одно исключение экземпляра
Такая проблема есть только у одного сервиса, скорее всего она вызвана не тайм-аутом внешних зависимостей или нехваткой места в области методов, а тем, чтоНегабаритный запрос, который попадает в этот сервисЭто занимает много памяти и занимает много времени, что было вызвано старостью.
журнал gc разговаривает со мной
Первый FullGC произошел в2020-07-25 14:51:58
, наблюдая за предыдущим журналом, вы можете обнаружить, что исторически одновременный перезапуск CMS обычно стабилизирует память кучи на уровне3608329K->1344447K
, примерно с 3,6 ГБ до примерно 1,3 ГБ, но с определенного момента времениЭффективность переработки ухудшилась.
2020-07-25T14:51:50.866+0800: 155089.417: [GC (Allocation Failure) 2020-07-25T14:51:50.866+0800: 155089.417: [ParNew: 2530368K->281088K(2530368K), 0.9268687 secs] 5001197K->3500201K(16496128K), 0.9272222 secs] [Times: user=15.37 sys=1.02, real=0.92 secs]
Видно, что эта переработка идет от 5G до 3,5G, и после переработки еще так много занято! этот раз2020-07-25 14:51:50
Что-то должно было произойти вокруг, заставив старость держать группуЛао Лай.
Большой поиск, чтобы найти подозреваемых
После определения временной точки следующей проблемой является обнаружение больших запросов, отнимающих много времени вблизи этой временной точки.К счастью, у нашего сервиса есть подробный журнал запросов, обернутый в АОП, который точно записывает соответствующую информацию о каждом запросе. подозреваемый из журнала, а параметры запроса запредельные (один запрос изменяет свойства 1000 папок, чтобы скрыть бизнес-логику компании, папка используется как пример).
Однако это само по себе не может его уличить, потому что по опыту такой длинный параметр запроса не является редкостью.Вы можете вернуться к первой структурной диаграмме, чтобы увидеть, что наш сервис получает запросы как из веба, так и из API.Если пользователь в вебе не имеет такой записи для работы 1000, но API - это код, написанный пользователем, очень вероятно, что такой запрос произойдет, и это обычный запрос (порог для пакетного редактирования как раз 1000).
Соберите доказательства, осужденный
Но моя интуиция подсказывает мне, что все не так просто, я проследил все логи, связанные с этим запросом, и обнаружил, что данные, запрашиваемые этим запросом модификации, возмутительны. Если папок всего 1000, модификация требует всего 3 шага:
- Узнайте свойства этих 1000 папок на основе идентификатора первичного ключа
- Измените свойства этих 1000 папок.
- Выполнить операцию модификации, зафиксировать транзакцию
И есть объем запроса журнала sql, который смехотворно велик, и форма после сокрытия бизнес-логики выглядит следующим образом:
select xxx, yyy from file where userid=123 and file_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?...)
И grep в стек исключений, связанный с этим запросом, который представляет собой исключение отката, вызванное выполнением тайм-аута sql.AbstractFallbackSQLExceptionTranslator.java
, найденный из стека исключенийПричины возмутительного объема запросов
Восстановление уголовного процесса
Оказалось, что такая логика заложена в бизнес-коде, который изначально модифицировал некий атрибут 1000 папок, но бизнес-логика такова, что если модифицировать специальный атрибут, то онКаскад для изменения всех файлов в этих папках, отношения между папками и файлами примерно следующие
Поэтому на самом деле модифицируется запрос на 1000 папок, а 1000 папок + файлы 100w обрабатываются за кадром.Из-за ограничения используемого нами фреймворка файлы 100w будут проверять основную таблицу атрибутов + все файлы перед модификацией. таблица атрибутов (память объединяется в соответствии с идентификатором первичного ключа), запрос занимает 90 с, в результате чегомного объектов много времениЗастрял в куче памяти и пережил волны сухого старения minorGC и CMS GC, и, наконец, спровоцировал эту проблему.
идеи оптимизации
Проблема обнаружена, и следующим шагом является идея оптимизации проблемы:
- Ограничить модификацию таких полей, для таких нуждкаскадПроверьте при изменении, что пользователям API не разрешено загружать слишком много папок (1000 --> 100)
- Идея микросервисов состоит в том, чтобы создать службу распределения на верхнем уровне службы.Для таких каскадных запросов на модификации 1000 модификаций разбиваются на 10 запросов на каждую модификацию по 100, чтобы одновременно запрашивать машины ниже, и давление равномерно распределенный
- Асинхронная очередь, возврат сразу после модификации свойств самой папки, последующие каскадные запросы на модификацию разбиваются на n кусков и ставятся в очередь, а другие сервисы подписываются на запрос и выполняют его
- Могут быть методы мониторинга, чтобы запустить центр регистрации при применении FullGC, а затем присоединиться после самовосстановления FullGC вместо ручного вмешательства для перезапуска.
- Оптимизируйте структуру ORM, даже если вы измените определенный атрибут файлов 100w, вам не нужно запрашивать все атрибуты этих файлов, просто запросите основную таблицу + таблицу, в которой находятся атрибуты, которые нужно изменить.