задний план
Дело (авария) такое, вдруг приходит тревога, не выполняется бизнес-логика в онлайн-приложении, и в результате не обновляются какие-то данные в базе.
Хотя это код, написанный предшественниками, какBug maker&killer
Только зубы стиснул.
Поскольку раньше я не знакомился с логикой проблемы, я просто рассуждал следующим образом:
- Существует производственный поток, который продолжает записывать данные в очередь.
- Потребительский поток также постоянно извлекает данные и записывает их в последующий пул бизнес-потоков.
- Потоки в пуле бизнес-потоков будут выполнять операции хранения для каждой задачи.
Весь процесс относительно ясен, что является типичной моделью производитель-потребитель.
попытаться найти
Следующий шаг — попытаться найти проблему, сначала регулярно проверяйте следующее:
- Есть ли переполнение памяти?
- Есть ли исключения для приложения GC?
По логам и мониторингу установлено, что два вышеуказанных являются нормальными.
Затем сделайте дамп моментального снимка потока, чтобы увидеть, что делают потоки в пуле бизнес-потоков.
Получается, что все пулы бизнес-потоков находятся вwaiting
состояние, очередь также пуста.
В то же время очередь, используемая производителем, заполнена, и признаков потребления нет.
В сочетании с приведенной выше блок-схемой нетрудно обнаружить, что это должна быть очередь потребления.Consumer
Если что-то пойдет не так, восходящая очередь не сможет быть использована, а некоторым пулам бизнес-потоков нечего будет делать.
проверить код
Итак, я проверил бизнес-логику кода потребления, а также обнаружил, что поток потребления — этоодин поток.
В сочетании с предыдущими снимками потоков я обнаружил, что этот потребительский поток также находится в состоянии ожидания, что точно такое же, как и пул бизнес-потоков, стоящий за ним.
Что он сделал, так это в основном проанализировал сообщение, а затем бросил его в пул бизнес-потоков позади, и не нашел ничего особенного.
Но из-за того, что внутри так много ответвлений (switch case), это выглядит немного головной болью, поэтому после того, как я пообщался с одноклассником, который написал этот бизнес-код, он сказал мне, что действительно парсит только данные на входе, а все последующие бизнес-процессы логика была заброшена. Она обрабатывалась в пуле потоков, поэтому я взял эту предпосылку для расследования (закладывая предзнаменование).
Поскольку потребляемая здесь очередь на самом деле являетсяdisruptor
очередь, как обычноBlockQueue
Это не то же самое, он не обрабатывается разработчиком, настраивающим логику потребления, вместо этого в него напрямую забрасывается пул потоков при инициализации очереди, который будет использовать этот пул потоков для внутреннего потребления, и в то же время вызывать назад метод, в этом методе мы пишем нашу собственную логику потребления.
Так что для разработчиков эта логика потребления на самом деле является черным ящиком.
Итак, я повторяюreview
После того, как логика анализа данных в коде потребления обнаружила, что проблема вряд ли может быть проблемой, я начал задаваться вопросом, не было ли этоdisruptor
Его собственная проблема вызвала срабатывание потребляющего потока.
Перевернуть сноваdisruptor
Я все еще не нашел никаких проблем после исходного кодаdisruptor
Более зрелый @cafe latte с его помощью моделирует ту же ситуацию, что и производство на месте.
локальное моделирование
Локально также создается однопоточный пул потоков и соответственно выполняются две задачи.
- О первой задаче особо нечего сказать, то есть простая печать.
- Вторая задача накапливает число и выдает необработанное исключение, когда оно достигает 10.
Тогда давайте запустим его.
Обнаружено, что когда в задаче выдается неперехваченное исключение, поток в пуле потоков будет находиться вwaiting
состояние, в то время как все стеки соответствуют производству.
Внимательные друзья обнаружат, что имя потока, работающего нормально, и имя потока в состоянии ожидания после исключения отличаются, это последующий анализ.
Решать проблему
Как насчет добавления перехвата исключений?
Программа обязательно запустится нормально.
В то же время вы обнаружите, что все задачи выполняются одним потоком.
Хотя он просто добавил строчку кода, нам еще предстоит разобраться с дверью и дверью здесь.
Анализ исходного кода
Так что только напрямуюdebug
Исходный код пула потоков самый быстрый;
Только что через стек исключений мы входим вThreadPoolExecutor.java:1142
место.
- Выяснено, что пул потоков уже помог нам сделать захват исключений, но он все равно будет подброшен.
- существует
finally
блок будет выполнятьсяprocessWorkerExit(w, completedAbruptly)
метод.
видел раньше"Как элегантно использовать и понимать пулы потоков"Друзья все еще должны быть впечатлены.
Задачи в пуле потоков упаковываются как внутренниеWorker
Объект выполняется.
processWorkerExit
Это может быть просто понято как уничтожение текущего потока (workers.remove(w)
), при добавлении (addWorker()
)ОдинWorker
Затем объект обрабатывается;
Как будто деталь сломана и заменена новой, но задачи, за которую отвечает старая деталь, уже нет.
см. далееaddWorker()
Что сделал:
Посмотрите только на ту часть, которая больше беспокоит это время; после успешного добавления оно будет выполнено напрямуюstart()
Методы.
так какWorker
ДостигнутоRunnable
интерфейс, поэтому он, по сути, вызываетrunWorker()
метод.
существуетrunWorker()
На самом деле вышеизложенноеThreadPoolExecutor
Метод, вызывающий исключение.
Он будет продолжать получать задачи для выполнения из очереди, то естьgetTask()
;существуетgetTask
Также видно, что он всегда будет брать задачи из встроенной очереди.
И как только очередь опустеет, онаwaiting
существуетworkQueue.take()
, то есть 1067 строк кода, которые мы нашли в стеке.
изменение названия темы
Выше также упоминалось, что имя потока после исключения изменилось.addWorker()
можно увидеть в методеnew Worker()
Когда имя потока переименовывается, по умолчанию к числу суффикса добавляется 1.
Так что все можно объяснить, истина одна:
Когда в пуле потоков одного потока генерируется неперехваченное исключение, пул потоков перезапускает текущий поток и создает новый.
Worker
; Он также будет постоянно получать задачи из очереди для выполнения, но, поскольку это поток-потребитель, производитель не бросает в него задачи, поэтому он всегда будет ждать в том месте, где задачи получены из очереди, поэтому это вызовет поток В очереди нет потребления, и пул бизнес-потоков не выполняется.
Суммировать
Так что после этого проблема на линии и захват исключения стала нормальной, но я все равно немного озадачен:
Поскольку все последующие задачи выполняются в пуле потоков, который является чисто асинхронным, даже если возникнет исключение, оно не будет выброшено в поток-потребитель.
Разве это не опровергает мои предыдущие знания? Не верьте злу! После этого я попросил службу эксплуатации и обслуживания добавить онлайн-журнал ошибок после захвата исключений.
Выяснилось, что многие из перечисленныхswitch case
, последняя оказалась базой данных с прямым манипулированием, что привело к ошибке для непустого поля 🤬! !
Это также преподало мне урок, но увидеть значит поверить.
Хотя эта проблема была решена с небольшими изменениями, все еще есть много вещей, которые необходимо улучшить во всем процессе проверки:
- Имя потока потребительской очереди на самом деле совпадает с префиксом бизнес-потока, из-за чего я потратил много времени на его поиск, и имя необходимо скорректировать.
- Вам нужно выработать привычку разрабатывать спецификации и защитное программирование.
- Неизвестные технологические стеки требуют осторожности, например
disruptor
, предыдущая команда должна была просто использовать его сразу после прочтения введения о высокой производительности и не изучать его принцип; она не была уверена в нем после возникновения проблемы.
Пример кода:
Ваши лайки и репост - лучшая поддержка для меня