проблемное явление
В какой-то рабочий день я внезапно получил сигнал тревоги онлайн-сервиса, и большое количество запросов было задержано.Когда я проверил онлайн-сервис, я обнаружил, что в основном время соединения с базой данных истекло, и время воздействия составляло всего 3-4 секунды. , и обслуживание возобновилось. нормально. Через несколько минут снова появилось большое количество будильников, но эффект пришел в норму через 3-4 секунды. Поскольку мы являемся службой низкого уровня и на нее полагаются многие службы верхнего уровня, такие частые аномальные колебания серьезно повлияли на использование бизнеса. Начать устранение неполадок
Процесс устранения неполадок
Влияние БД?
- Когда генерируется первый аварийный сигнал, первая реакция заключается в том, что может быть большое количество интерфейсных вызовов в службе верхнего уровня и задействованы некоторые сложные SQL-запросы, что приводит к недостаточному количеству подключений к базе данных.Очевидных изменений нет, за исключением влияние всплеска трафика
- Запросите ситуацию с БД, загрузка хорошая, медленный запрос отсутствует, и влияние, вызванное БД, исключено.
Влияние контейнера или JVM?
После исключения влияния БД проверить влияние контейнера вверх Мы снова посмотрели на аномальные тревоги и обнаружили, что в период каждой волны тревог они в основном генерировались одним и тем же IP-адресом контейнера.В настоящее время вероятность того, что это проблема GC, составляет 80%. Убедитесь, что загрузка ЦП контейнера в период тревоги в норме. Глядя на ситуацию с памятью и GC JVM, можно обнаружить, что вся кривая использования памяти выглядит следующим образом:
Heap
Old Gen
Из приведенного выше рисунка видно, что в памяти есть объекты, на которые ссылались в течение длительного времени и которые не могут быть восстановлены YongGC, а размер объектов увеличивается. Объект не будет восстановлен до тех пор, пока не будет запущена полная сборка мусора после того, как старая сборка будет заполнена, а одна полная сборка мусора занимает очень много времени.
Временные меры
Теперь, когда проблема обнаружена, только 3 экземпляра запустили FullGC, но при рассмотрении использования памяти другими экземплярами обнаруживается, что в основном все экземпляры Old Gen приближаются к критической точке. Таким образом, временное решение состоит в том, чтобы оставить один экземпляр на месте и перезапустить все остальные экземпляры поочередно, чтобы избежать одновременного выполнения FullGC большим количеством экземпляров. В противном случае это может вызвать лавину обслуживания.
В исходном сервисе были установлены алармы мониторинга JVM.Теоретически, когда использование памяти достигало определенного значения, должно было быть уведомление о тревоге.Однако из-за миграции службы конфигурация будильника была недействительной, и проблема не была обнаружена заранее, авансом.
анализ проблемы
какие объекты не перерабатываются
Что я узнал на данный момент: Память не может быть восстановлена YoungGC и увеличивается бесконечно.Только FullGC может восстановить эту партию объектов, а FullGC занимает много времени.
jmap -histo:live pid
Во-первых, я просто наблюдал за волной онлайн, второй HashMap$Node выглядит ненормально, но больше деталей я не вижу. Лучший способ — сделать дамп снимка памяти и использовать MAT для анализа волны.
jmap -dump:format=b,file=filename pid
После использования MAT для открытия вы можете обнаружить очевидные проблемы:
class com.mysql.cj.jdbc.AbandonedConnectionCleanupThread
Этот класс занимает более 80% памяти, так что же делает этот класс? Как вы можете видеть из имени класса, это должен быть поток в драйвере MySQL, используемый для очистки просроченных соединений. Давайте посмотрим на исходный код:
Этот класс представляет собой синглтон, который открывает только один поток для очистки соединений с базой данных, которые не были закрыты явно.
Вы можете видеть, что этот класс поддерживает Set
private static final Set<ConnectionFinalizerPhantomReference> connectionFinalizerPhantomRefs = ConcurrentHashMap.newKeySet();
В соответствии с HashMap$Node со второй по величине скоростью использования памяти, которую мы видели выше, можно в основном определить, что существует высокая вероятность того, что здесь есть утечка памяти. Подтвердите выстрел с помощью list_object на MAT:
Конечно же, виновник найден! Так что же в нем? Почему он продолжает расти и не может быть переработан YoungGC? см. имяConnectionFinalizerPhantomReferenceМы можем предположить, что он должен содержать фантомную ссылку на соединение с базой данных.
чтоphantom reference? Когда объект имеет толькоphantom referenceКогда на него ссылаются, он будет повторно использован во время GC виртуальной машины и будетphantom referenceобъект вreferenceQueueсередина.
Давайте проследим исходный код, чтобы подтвердить
Конечно жеPhantomReference, в котором хранится созданное соединение с MySQL, посмотрите, куда оно было помещено:
Как видите, каждый раз, когда создается новое соединение с базой данных, созданное соединение оборачивается какPhantomReferenceпоставить послеconnectionFinalizerPhantomRefs, то этот поток очистки получит соединение в referenceQueue и закроет его в бесконечном цикле.
только вобъект соединенияДругих ссылок нет, толькоphantom referenceможно GCed и положить вreferenceQueueсередина
Почему Connection растет бесконечно?
Теперь, когда проблема обнаружена, после создания соединения с базой данных она поместитconnectionFinalizerPhantomRefsВо, но по какой-то причине нормальное использование предварительно подключенных, после того, как ряд мелких GC не были восстановлены, продвинуты на старую эру. Но через какое-то время по какой-то причине соединение не удается, в результате чего пул соединений и новые соединения.
В нашем проекте используется пул соединений с базой данных Druid, конфигурация пула соединений следующая:
Видно, что он установленkeepAlive,а такжеminEvictableIdleTimeMillisПараметр — 5 минут, после инициализации соединения, когда количество запросов к БД не колеблется часто, пул соединений должен поддерживать минимум 30 соединений, а операция keepAlive будет выполняться, когда время простоя соединения превышает 5 минут: Теоретически пул соединений не будет часто создавать соединения, если активных соединений мало и есть колебания, а операция keepAlive не вступает в силу.Когда пул соединений выполняет операцию keepAlive, соединение MySQL уже не удалось, тогда оно будет будет отброшено. Это недействительное соединение будет восстановлено в следующий раз.
Чтобы проверить эту гипотезу, нужно проверить следующее: сначала мы проверяем количество активных подключений и обнаруживаем, что в большинстве случаев количество активных подключений к базе данных с одним экземпляром равно 3.Есть около 20 колебаний, и в бизнесе есть запланированные задачи, каждые 30 минут.За 1 час будет много запросов к БД.
Поскольку у Друида пульсация каждые 5 минут, почему не удается установить соединение? Самая большая вероятность - это работа сервера MySQL.Время ожидания по умолчанию для сервера MySQL составляет 8 часов.Есть ли изменения в соответствующей конфигурации?
show global variables like '%timeout%'
Конечно же, время ожидания базы данных было установлено на 5 минут! Тогда проблема очевидна
краткое изложение проблемы
- Частая перестройка соединений с БД приводит к сбою повторного использования, что приводит к чрезмерно быстрому увеличению объема памяти.
- Во время minorGC, так как соединение все еще активно с сильной ссылкой, что приводит кconnectionFinalizerPhantomRefsБесконечный рост до тех пор, пока не будет опустошен FullGC, так что время FullGC слишком велико.
В заключение
- Бездействующие соединения полагаются на временную задачу Druid keepAlive для обнаружения сердцебиения и keepAlive.По умолчанию задача определения времени обнаруживается каждые 60 секунд и только тогда, когда время простоя соединения превышаетminEvictableIdleTimeMillisобнаружение сердцебиения.
- из-заminEvictableIdleTimeMillisОн установлен на 5 минут.Теоретически незанятые соединения будут выполнять обнаружение пульса в течение временного интервала 5 минут ± 60 секунд. Однако, поскольку время ожидания сервера MySQL составляет всего 5 минут, существует высокая вероятность того, что соединение прервалось, когда Druid выполняет операцию keepAlive.
- Поскольку активные соединения с базой данных колеблются, иmin-idleПараметр равен 30. Когда активное соединение находится на пике, необходимо создать и поддерживать большое количество соединений в пуле соединений. Но когда активность падает до нижней точки, большое количество соединений удаляется из пула соединений из-за сбоев keepAlive. Неделя за неделей.
- Каждый раз, когда создается соединение, объект Connection помещается вconnectionFinalizerPhantomRefs, и поскольку соединение активно после его создания, оно не будет восстановлено miniorGC в течение короткого времени, пока оно не будет переведено в старое состояние, в результате чего SET становится все больше и больше, что приводит к длительной паузе FullGC.
решить
Зная причину проблемы, решение простое:
- Уменьшите количество недопустимых подключений и уменьшите скорость роста памяти: будетminEvictableIdleTimeMillisУстановите на 3 минуты, чтобы обеспечить действительность Keepalive и избежать повторного подключения все время.
- FullGC сокращает время восстановления: Замените сборщик мусора на G1.