Вступление

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

Предпосылки

Примеры и пояснения в этой статье предполагают, что читатель имеет базовые знания о реактивных потоках и их выполнении.

Состав

В этой статье отладка делится на три основных раздела.

  1. Обнаружение и отладка кодов блокировки в вашем реактивном приложении.
  2. Ускоренный курс по этапам жизненного цикла реактора. (Что помогает понять инструменты отладки.)
  3. Отладка реактивных операций.

Избегание и обнаружение кодов блокировки

Хотя очевидно, что все мы используем реактивный стек для создания неблокирующего асинхронного конвейера операций, иногда блокирующие операции проникают в наши реактивные потоки; это происходит по нескольким причинам.

Как ошибка новичка в реактивном стеке

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

Тяжелые операции вне цепочки подписчиков.

В приведенном выше примере кода idValidator.isValid может выполнять тяжелую операцию. Задумайтесь на секунду, почему не стоит писать такого рода операции вне цепочки подписчиков.

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

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

В вышеупомянутых операциях участвуют два типа потоков, и мы можем идентифицировать их по их именам.

response-http-nio-x: это HTTP-поток реактора, который используется как клиентской, так и серверной частью реализации для обработки соединений.
Их всего несколько (если только вы специально настроили номер).

nt-loopGroup-x: это поток обработки работы, который получает делегированную работу из указанного выше потока. Эти потоки будут выполнять любую назначенную работу, и как только она будет завершена, они передадут результат через цикл обработки событий.

Программисту реактивных приложений никогда не следует блокировать эти общие потоки «response-http-nio-x» или назначать их для длительных задач.

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

Это один из способов заблокировать поток цикла событий.

Устаревшая библиотека вынуждает разработчика использовать блокирующий вызов.

Нравится нам это или нет, но нам приходится иметь дело с устаревшими библиотеками, которые используют блокирующие вызовы. Например, если у вас есть библиотека, которая обертывает сторонние вызовы REST API с блокирующим HTTP-клиентом. У вас нет выбора, чтобы использовать его.

Но если вы знаете о его модели исполнения, вы можете обойти это. Spring Reactor Netty предоставляет для этого хороший набор инструментов. Это не по теме статьи. Но иногда вы даже не знаете, что в вашем конвейере есть блокирующий вызов.

BlockHound на помощь.

В обоих вышеупомянутых сценариях вы можете использовать BlockHound. Это Java-агент для обнаружения блокирующих вызовов из неблокирующих потоков.

Вы можете установить BlockHound за два шага.
1. Добавьте в проект зависимость BlockHound.

<dependency>
   <groupId>io.projectreactor.tools</groupId>
   <artifactId>blockhound</artifactId>
   <version>1.0.1.RELEASE</version>
</dependency>

2. Установите агент в свое приложение, в приложение Spring; вы можете использовать для этого класс Application.

static {
   BlockHound.install();
}

Вызов конечной точки с помощью блокирующего вызова приведет к следующему.

Когда вы выполняете код, в котором есть блокирующий компонент, вы увидите нечто подобное.

Знайте свою реактивную экосистему

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

1. Время сборки.
2. Срок подписки.
3. Время работы.

Время сборки. Здесь реактор строит конвейер асинхронной обработки. Это декларативный этап, на котором создается объект Publisher.

Время подписки. На этом этапе создается подписчик и среда, на которую он получает подписку, т. е. объект подписки.

Время выполнения: На этом этапе подписка прошла успешно. Издатель начал отправлять элементы подписчику.

Обладая вышеуказанными знаниями, мы можем углубиться в инструменты и методы отладки.

У меня есть вопросы.

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

  1. Как узнать, что происходит, когда мы подписываемся на Издателя?
  2. Я хочу увидеть последовательность событий этого потока.
  3. Я хочу обнаружить сигнал об ошибке.
  4. Я хочу определить точную строку, в которой произошла ошибка.

На все эти вопросы можно ответить, используя следующие инструменты отладки. Однако некоторые из этих инструментов похожи на то, что мы называем отладкой пещерного человека, по сравнению со сложными инструментами отладки в других парадигмах.

Набор инструментов для отладки

Оператор .log ()

Этот оператор доступен как для Flux, так и для Mono для наблюдения за реактивными сигналами. Мы можем использовать его для наблюдения за подпиской, запросом на подписку с противодавлением или без него, за каждым элементом, который проходит через поток, и т. Д.

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

Оператор .checkpoint ()

Контрольно-пропускные пункты проводят проверку предметов, проходящих через них; Есть несколько вариантов этого оператора, а именно. Имеет несколько ароматов.

  1. Не принимает аргументов, кроме принудительного захвата трассировки стека сборки по умолчанию. Это означает, что мы платим высокую цену с точки зрения производительности (подробнее об этом будет рассказано в разделе «Отладочные ловушки»).
  2. Принимает аргумент описания и не заставляет объект записывать трассировку стека сборки.
  3. Делегируйте управление приведенными выше аргументами вызывающей стороне.

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

Особенностью этого оператора является возможность задействовать его выборочно по сравнению с onOperatorDebug () Hook.

Используйте тот же код с оператором checkpoint ().

Согласно новой информации о трассировке сборки, мы можем идентифицировать ошибку, возникшую после

.checkpoint("after toUpperCase")

но прежде

.checkpoint("after gate map");

поскольку только контрольно-пропускной пункт «after gate map» мог наблюдать и ошибаться.

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

Хук onOperatorDebug ()

Более надежный подход к получению сведений об отладке, чем два предыдущих оператора. Когда мы вызываем Hooks.onOperatorDebug (), он включает устройство записи, способное собирать информацию трассировки стека сборки при создании издателя.

Он может собирать ценную информацию, такую ​​как описание оператора, порядок цепочки и т. Д. Класс Hooks способен перехватывать операторов на различных этапах жизненного цикла оператора. Но это выходит за рамки данной статьи.

И, как и все остальное, Хуки также имеют свою стоимость; следовательно, следует использовать осторожно. OnOperatorDebug Hook фиксирует трассировку стека сборки для каждого оператора в цепочке, и это сильно влияет на производительность. Так что не для производственного использования. Но есть агентный подход, доступный сейчас как часть активной зоны реактора. Вы можете проверить это, если вам нужна эта возможность в производственной среде.

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

Заключение

В этой статье исследуются инструменты, доступные для отладки реактивных приложений в контексте Spring WebFlux. Он объяснил преимущества каждого инструмента. И возможные сценарии для использования при изложении технических аргументов в пользу пригодности. Я надеюсь, что это поможет любому разработчику Spring Reactive Backend получить хорошее представление о доступных инструментах для отладки.

использованная литература

Https://www.youtube.com/watch?v=0rnMIueRKNU&t=3015s

Https://www.youtube.com/watch?v=pyqIpqCt8PU

Https://www.youtube.com/watch?v=xCu73WVg8Ps

Https://www.youtube.com/watch?v=IZ2SoXUiS7M

Https://spring.io/blog/2019/03/06/flight-of-the-flux-1-assembly-vs-subscription