задний план
Я только утром приехал в компанию, и когда уже собирался отправиться на дневную рыбалку, мне вдруг пришло письмо из центра мониторинга.
Секрет в моем сердце нехорош, потому что система мониторинга никогда не скажет мне, что приложение идеальноbug
, на самом деле система довольно убогая.
Когда я открыл электронное письмо, мне сообщили, что очередь пула потоков приложения достигла порогового значения и вызвала тревогу.
Поскольку у этого приложения есть проблема, которая очень сильно влияет на работу пользователя, поэтому немедленно позвольте эксплуатации и обслуживанию сохранить сцену.dump
Поток и память одновременно перезапустили приложение, но, к счастью, после перезапуска оно вернулось в норму. Итак, начал устранять проблему.
анализировать
Прежде всего, давайте разберемся, о чем это приложение.
Проще говоря, изMQ
Данные извлекаются, а затем помещаются в следующий пул бизнес-потоков для конкретной бизнес-обработки.
Очередь сигналов тревоги — это в точности очередь этого пула потоков.
Код трассировки обнаружил, что пул потоков был построен следующим образом:
ThreadPoolExecutor executor = new ThreadPoolExecutor(coreSize, maxSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());;
put(poolName,executor);
используется по умолчаниюLinkedBlockingQueue
Размер не указан (это тоже яма), поэтому размер этой очереди по умолчаниюInteger.MAX_VALUE
.
Поскольку приложение было перезапущено, анализ можно выполнить только из оставшихся моментальных снимков потока и моментальных снимков памяти.
анализ памяти
использовать сначалаMAT
Проанализировав память, я получил следующий отчет.
Среди них есть два относительно больших объекта, один из которых является предыдущим пулом потоков для хранения задач.LinkedBlockingQueue
, а другой естьHashSet
.
Конечно, очередь занимает много памяти, поэтому сначала проверьте ее.HashSet
До скорого.
Поскольку размер очереди достаточно велик, учитывая текущую ситуацию, должно быть, обработка задач в пуле потоков происходит медленно, в результате чего задач в очереди становится все больше и больше.По крайней мере, такой вывод можно сделать уже.
анализ потоков
Давайте посмотрим на анализ потока, вот использованиеfastthread.ioЭтот сайт проводит анализ потоков.
Поскольку задачи в пуле потоков давно не выполнялись с точки зрения производительности, в основном нужно посмотреть, что они делают.
они все вRUNNABLEсостояние, а стек выглядит следующим образом:
Установлено, что речь идет как раз о вышеупомянутомHashSet
, см. этот стек запрашиваетkey
он существует. То же самое можно сказать и о строке 312 бизнес-кода.
Название темы здесь тоже ямка, что заставило меня долго искать.
должность
После анализа стеков памяти и потоков некоторые проблемы действительно были угаданы.
На самом деле есть предпосылка, которую я забыл упомянуть здесь:
Это предупреждение凌晨三点
Письмо отправлено, но нет напоминания по телефону и т.п., так что все не знают.
Узнал только когда пошел утром на работу и сразуdump
вышеуказанные доказательства.
Все с одним очень важным фактом:Эти бизнес-потоки запрашиваютHashSet
Работает уже 6-7 часов без возврата.
Это также видно из предыдущей кривой мониторинга:
ОС была под высокой нагрузкой, пока мы не увидели перезагрузку будильника утром, прежде чем он отключился.
В то же время обнаружено, что приложение, работающее в рабочей среде,JDK1.7
, поэтому я изначально думал, что его нужно вводить при запросе ключаHashMap
Круговой связанный списокCPU
Высокая нагрузка также вошла в бесконечный цикл.
Код был снова проверен, чтобы проверить эту проблему.
Псевдокод после завершения выглядит следующим образом:
//线程池
private ExecutorService executor;
private Set<String> set = new hashSet();
private void execute(){
while(true){
//从 MQ 中获取数据
String key = subMQ();
executor.excute(new Worker(key)) ;
}
}
public class Worker extends Thread{
private String key ;
public Worker(String key){
this.key = key;
}
@Override
private void run(){
if(!set.contains(key)){
//数据库查询
if(queryDB(key)){
set.add(key);
return;
}
}
//达到某种条件时清空 set
if(flag){
set = null ;
}
}
}
Общий процесс выглядит следующим образом:
- Постоянный поток данных получается из MQ.
- Бросьте данные в пул бизнес-потоков.
- Определить, были ли записаны данные
Set
. - Если нет, запросите базу данных.
- затем напишите
Set
середина.
Тут явная проблема,То есть Установить как общий ресурс не делает никакой синхронизации.
Одновременно будет работать несколько потоков, потому чтоHashSet
На самом деле, это по существуHashMap
, так что это определенно небезопасно для потоков, поэтому возникают две проблемы:
- Данные в наборе перезаписываются во время параллельной записи, что приводит к неточным данным.
- Круговой связанный список будет сформирован во время расширения.
Первый вопрос приемлем относительно второго.
Благодаря приведенному выше анализу памяти мы уже знаем, что в этом наборе много данных. В то же время, поскольку размер не указывается при инициализации, это всего лишь значение по умолчанию, поэтому он будет вызывать частое расширение при большом количестве одновременных операций записи, а при условии 1.7 может образовыватьсякруговой связанный список.
К сожалению, в коде также есть операции запроса (contains()
), обратите внимание на ситуацию со стеком выше:
обнаружено, что он работает наHashMap
465 строк, посмотрим, что там творится в 1.7:
Это уже очевидно. Здесь мы просматриваем связанный список, и это вызвано формированием кругового связанного списка.e.next
никогда не бывает пустым, поэтому цикл никогда не завершается.
Здесь проблема действительно найдена, но остается вопрос, почему очередь задач в пуле потоков будет накапливаться все больше и больше. Мой первый инстинкт заключается в том, что выполнение задачи слишком медленное.
Внимательно изучив код, я обнаружил, что есть только одно место, где он может быть медленным: этозапрос к базе данных.
Выносить этот SQL в продакшн-среду на выполнение действительно не быстро, и при проверке индекса обнаруживаются хиты.
Но я посмотрел на данные в таблице и обнаружил, что это почти так.7000WДанные.同时经过运维得知MySQL
этот серверIO
Давление тоже больше.
Итак, причина более очевидна:
Поскольку к базе данных необходимо обращаться каждый раз, когда потребляется часть данных, сама MySQL находится под большим давлением, а количество данных также велико, поэтому этот ответ ввода-вывода медленный, что приводит к более медленной обработке всей задачи.
Но есть еще одна причина, которую нельзя игнорировать: так как все бизнес-потоки вошли в бесконечный цикл в определенный момент времени, шансов завершить задачу нет вообще, а следующие данные все еще поступают непрерывно, поэтому эта очередь будет только быть больше и больше Чем больше вы нагромождаете!
На самом деле это старое приложение, и некоторые люди могут спросить, почему раньше не было проблем.
Это связано с тем, что раньше объем данных был относительно небольшим, и не было параллельного расширения для формирования кругового связанного списка даже для одновременных операций записи. Всплеск объема бизнеса в этот период только что выявил эту скрытую мину. Так что я все еще должен верить словам Мерфи.
Суммировать
На этом все расследование завершено, и наши последующие меры корректировки примерно следующие:
-
HashSet
не является потокобезопасным, замените его наConcurrentHashMap
заодно поставилvalue
Этого можно достичь, написав до смертиset
Эффект. - Согласно нашему более позднему мониторингу, инициализировать
ConcurrentHashMap
Размер должен быть как можно больше, чтобы избежать частого расширения. -
MySQL
Многие данные уже не используются, и проводится холодная термообработка. Минимизируйте количество данных в одной таблице. В то же время подтаблица будет рассмотрена позже. - Данные запроса корректируются в кэше запросов для повышения эффективности запросов.
- Имя пула потоков должно быть осмысленным, иначе оно усложнит вам задачу.
- Настройте размер очереди пула потоков на определенное значение в соответствии с мониторингом и примените политику отклонения.
- обновитесь до
JDK1.8
. - Во-вторых, электронное письмо с сигналом тревоги следует рассматривать как уведомление по телефону.
HashMap
Проблема бесконечного цикла Интернета бесконечна, но я не ожидал, что столкнусь с ней. В настоящее время это условие встречается довольно редко, например, ниже 1,8.JDK
С этим может не столкнуться большинство людей, что еще раз подтверждает закон Мерфи.
В то же время я обновлю статью здесь, чтобы вам было удобно читать и запрашивать.
Ваши лайки и репост - лучшая поддержка для меня