API Java Message Service (JMS) был разработан Sun Microsystems во времена Java EE. Он предоставляет простые абстракции для обмена сообщениями, в том числе производителя сообщений (Message Producer), потребителя сообщений (Message Consumer) и т.д.
API JMS дает возможность помещать сообщение в “очередь” и получать сообщения, помещенные в указанную очередь. Это полезно для систем с высокой пропускной способностью: вместо того чтобы тратить время пользователя на выполнение медленной операции в режиме реального времени, корпоративное приложение может отправить сообщение. Такой неблокирующий подход обеспечивает высокую пропускную способность, поддерживая надежность в большом масштабе.
Сообщение содержит транзакционный контекст, который обеспечивает гарантии доставки. В результате мы можем опубликовать сообщение в методе, а затем просто вернуть его, что обеспечивает гарантии, аналогичные тем, которые мы имеем при записи в базу данных ACID.
Мы можем рассматривать обмен сообщениями в некотором роде как почтовую рассылку по сообществу. Вы отправляете сообщение на адрес электронной почты, который олицетворяет собой некий список. Каждый, кто подписывается на этот список, получает данное сообщение. В этом случае тема сообщения — это адрес списка рассылки сообщества. Вы можете отправить ему сообщение, и обработчик Java Message Service с помощью прослушивателя сообщений получит указанное событие.
Важно отметить, что в JMS существуют две модели обмена сообщениями: модель публикации и подписки (которую мы обсуждали здесь), а также обмен сообщениями “точка-точка”, который позволяет отправлять сообщение определенному адресату.
Рассмотрим короткий пример.
Простая демонстрация
Чтобы отладить вызовы Java Message Service, я создал простое демонстрационное приложение, исходный код которого можно найти здесь.
Эта демонстрация JMS представляет собой простой API ведения журнала базы данных — это микросервис, который вы можете использовать для помещения в журнал записи, которая затем асинхронно записывается в базу данных. RESTful-приложения могут затем воспользоваться этим API логирования в базу данных для того, чтобы дополнять журнал базы данных без дополнительных затрат на доступ к самой базе.
Этот код реализует основной веб-сервис:
@RestController
@RequiredArgsConstructor
public class EventRequest {
private final JmsTemplate jmsTemplate;
private final EventService eventService;
private final Moshi moshi = new Moshi.Builder().build();
@PostMapping("/add")
public void event(@RequestBody EventDTO event) {
String json = moshi.adapter(EventDTO.class).toJson(event);
jmsTemplate.send("event", session ->
session.createTextMessage(json));
}
@GetMapping("/list")
public List<EventDTO> listEvents() {
return eventService.listEvents();
}
}
Обратите внимание на метод event()
, который отправляет сообщение в тему события. До сих пор я не упоминал тела сообщений, чтобы упростить задачу, но в этом случае я просто передаю в качестве тела строковый JSON. Хотя JMS поддерживает сериализацию объектов, ее использование имеет свои сложности, и я хочу, чтобы мой код оставался простым.
Чтобы дополнить основной веб-сервис, нужно создать прослушиватель, который обрабатывает входящее сообщение:
@Component
@RequiredArgsConstructor
public class EventListener {
private final EventService eventService;
private final Moshi moshi = new Moshi.Builder().build();
@JmsListener(destination = "event")
public void handleMessage(String eventDTOJSON) throws IOException {
eventService.storeEvent(moshi.adapter(EventDTO.class).fromJson(eventDTOJSON));
}
}
Прослушиватель вызывается отправленной к нему строкой JSON: мы анализируем ее и пересылаем в сервис.
Отладка скрытого кода
В таких абстракциях, как Spring и JMS, не нужно писать много шаблонного кода. К сожалению, ориентированное на сообщения промежуточное ПО этого типа скрывает множество хрупких деталей реализации, которые могут привести к сбою.
Это особенно неприятно в производственном сценарии, где трудно понять, возникла ли проблема из-за того, что сообщение не было отправлено должным образом. Вот тут-то и вступает в дело Lightrun.
Вы можете размещать действия Lightrun (снапшоты, логирование и т.д.) непосредственно внутри API платформы и реализации сервисов обмена сообщениями. Это позволяет определить, работают ли селекторы сообщений должным образом и запущен ли прослушиватель сообщений.
Со Spring и поддержкой JMS, как показано выше, мы можем открыть JmsTemplate
и добавить снапшот непосредственно в методе execute
:
Действие вызывается при отправке в тему. Дальше можно просмотреть фрейм стека, увидеть тему, куда доставлено сообщение, и воспользоваться условиями, чтобы сузить круг подходящих обработчиков сообщений.
Можно также поместить соответствующий снапшот в источник сообщений, чтобы отслеживать поток. Например, снимок в EventRequest
может оказаться полезным источником информации. Доступны и другие направления поиска.
В приведенном выше стеке можно заметить, что метод execute
вызывается методом send
на строке 584. Метод execute
обертывает вызывающий объект, поэтому операция будет асинхронной. Продвигаемся дальше: переходим к замыканию и помещаем там снапшот.
Обратите внимание: здесь можно определить условие для конкретной темы и ограничить себе круг работы.
Заключение
Системы обмена сообщениями помогают повысить надежность приложения. Однако корпоративные системы обмена сообщениями сложно отлаживать в процессе производства, что противоречит понятию надежности. Логи доступны в целевой точке сообщений, но что если в нее не попасть?
Lightrun позволяет поместить действия на разных уровнях приложения. Это помогает сузить круг потенциальных проблем независимо от стандарта обмена сообщениями и платформы. И все это — на бесплатном аккаунте.
Читайте также:
- Как отобразить индикатор выполнения на стандартной консоли с помощью Java
- В ожидании Java 16: Stream.toList() и другие методы преобразования
- Незаслуженно забытый ForkJoinPool
Читайте нас в Telegram, VK и Дзен
Перевод статьи Shai Almog: Debugging the Java Message Service (JMS) API using Lightrun