Введение в Spring Cloud Stream
1. обзор
Spring Cloud Stream - это фреймворк, построенный на основе Spring Boot и Spring Integration, которыйhelps in creating event-driven or message-driven microservices.
В этой статье мы познакомим вас с концепциями и конструкциями Spring Cloud Stream на нескольких простых примерах.
2. Maven Зависимости
Для начала нам нужно добавить зависимостьSpring Cloud Starter Stream with the broker RabbitMQ Maven в качестве промежуточного программного обеспечения для обмена сообщениями в нашpom.xml:
org.springframework.cloud
spring-cloud-starter-stream-rabbit
1.3.0.RELEASE
И мы добавимmodule dependency from Maven Central, чтобы также включить поддержку JUnit:
org.springframework.cloud
spring-cloud-stream-test-support
1.3.0.RELEASE
test
3. Основные понятия
Архитектура микросервисов работает по принципу «https://martinfowler.com/articles/microservices.html#SmartEndpointsAndDumbPipes[smart конечные точки и тупые каналы]». Связь между конечными точками осуществляется сторонними поставщиками программного обеспечения, такими как RabbitMQ или Apache Kafka. Services communicate by publishing domain events via these endpoints or channels.
Давайте рассмотрим концепции, составляющие структуру Spring Cloud Stream, а также основные парадигмы, о которых мы должны знать, чтобы создавать сервисы, управляемые сообщениями.
3.1. Формирует
Давайте посмотрим на простой сервис в Spring Cloud Stream, который прослушивает привязкуinput и отправляет ответ на привязкуoutput:
@SpringBootApplication
@EnableBinding(Processor.class)
public class MyLoggerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyLoggerServiceApplication.class, args);
}
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public LogMessage enrichLogMessage(LogMessage log) {
return new LogMessage(String.format("[1]: %s", log.getMessage()));
}
}
Аннотация@EnableBinding настраивает приложение для привязки каналовINPUT иOUTPUT, определенных в интерфейсеProcessor. Both channels are bindings that can be configured to use a concrete messaging-middleware or binder.с
Давайте посмотрим на определение всех этих понятий:
-
Bindings - набор интерфейсов, декларативно идентифицирующих входные и выходные каналы
-
Binder - реализация промежуточного программного обеспечения для обмена сообщениями, такого как Kafka или RabbitMQ
-
Channel - представляет собой канал связи между промежуточным программным обеспечением обмена сообщениями и приложением
-
StreamListeners - методы обработки сообщений в bean-компонентах, которые будут автоматически вызываться для сообщения из канала после того, какMessageConverter выполнит сериализацию / десериализацию между событиями, связанными с промежуточным программным обеспечением, и типами объектов домена / POJO
-
MessageSchemas - используются для сериализации и десериализации сообщений, эти схемы могут быть статически считаны из местоположения или загружены динамически, поддерживая эволюцию типов объектов домена
3.2. Шаблоны общения
Messages designated to destinations are delivered by the Publish-Subscribe messaging pattern. Издатели распределяют сообщения по темам, каждая из которых идентифицируется по имени. Подписчики проявляют интерес к одной или нескольким темам. Промежуточное программное обеспечение фильтрует сообщения, доставляя подписчикам интересные темы.
Теперь подписчики могут быть сгруппированы. consumer group - это набор подписчиков или потребителей, обозначенныхgroup id, в пределах которых сообщения из темы или раздела темы доставляются с балансировкой нагрузки.
4. Модель программирования
В этом разделе описываются основы создания приложений Spring Cloud Stream.
4.1. Функциональное тестирование
Поддержка тестирования является связующей реализацией, которая позволяет взаимодействовать с каналами и проверять сообщения.
Давайте отправим сообщение в указанный выше сервисenrichLogMessage и проверим, содержит ли ответ текст“[1]: “ в начале сообщения:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyLoggerServiceApplication.class)
@DirtiesContext
public class MyLoggerApplicationTests {
@Autowired
private Processor pipe;
@Autowired
private MessageCollector messageCollector;
@Test
public void whenSendMessage_thenResponseShouldUpdateText() {
pipe.input()
.send(MessageBuilder.withPayload(new LogMessage("This is my message"))
.build());
Object payload = messageCollector.forChannel(pipe.output())
.poll()
.getPayload();
assertEquals("[1]: This is my message", payload.toString());
}
}
4.2. Клиентские каналы
В приведенном выше примере мы использовали интерфейсProcessor, предоставляемый Spring Cloud, который имеет только один входной и один выходной канал.
Если нам нужно что-то другое, например, один входной и два выходных канала, мы можем создать собственный процессор:
public interface MyProcessor {
String INPUT = "myInput";
@Input
SubscribableChannel myInput();
@Output("myOutput")
MessageChannel anOutput();
@Output
MessageChannel anotherOutput();
}
Spring обеспечит правильную реализацию этого интерфейса для нас. Имена каналов можно задать с помощью аннотаций, например@Output(“myOutput”).
В противном случае Spring будет использовать имена методов в качестве имен каналов. Таким образом, у нас есть три канала:myInput,myOutput иanotherOutput.
Теперь представим, что мы хотим направить сообщения на один выход, если значение меньше 10, и на другой выход, если значение больше или равно 10:
@Autowired
private MyProcessor processor;
@StreamListener(MyProcessor.INPUT)
public void routeValues(Integer val) {
if (val < 10) {
processor.anOutput().send(message(val));
} else {
processor.anotherOutput().send(message(val));
}
}
private static final Message message(T val) {
return MessageBuilder.withPayload(val).build();
}
4.3. Условная отправка
Используя аннотацию@StreamListener, мы также можемfilter the messages we expect in the consumer, используя любое условие, которое мы определяем с помощьюSpEL expressions.
В качестве примера мы могли бы использовать условную диспетчеризацию в качестве другого подхода для маршрутизации сообщений на разные выходные данные:
@Autowired
private MyProcessor processor;
@StreamListener(
target = MyProcessor.INPUT,
condition = "payload < 10")
public void routeValuesToAnOutput(Integer val) {
processor.anOutput().send(message(val));
}
@StreamListener(
target = MyProcessor.INPUT,
condition = "payload >= 10")
public void routeValuesToAnotherOutput(Integer val) {
processor.anotherOutput().send(message(val));
}
Единственныйlimitation of this approach is that these methods must not return a value.
5. Настроить
Давайте настроим приложение, которое будет обрабатывать сообщение от брокера RabbitMQ.
5.1. Конфигурация связующего
Мы можем настроить наше приложение на использование реализации связующего по умолчанию черезMETA-INF/spring.binders:
rabbit:\
org.springframework.cloud.stream.binder.rabbit.config.RabbitMessageChannelBinderConfiguration
Или мы можем добавить библиотеку связывателей для RabbitMQ в путь к классам, включивthis dependency:
org.springframework.cloud
spring-cloud-stream-binder-rabbit
1.3.0.RELEASE
Если реализация связывателя не предоставлена, Spring будет использовать прямую передачу сообщений между каналами.
5.2. Конфигурация RabbitMQ
Чтобы настроить пример из раздела 3.1 для использования связывателя RabbitMQ, нам нужно обновитьapplication.yml, расположенный вsrc/main/resources:
spring:
cloud:
stream:
bindings:
input:
destination: queue.log.messages
binder: local_rabbit
output:
destination: queue.pretty.log.messages
binder: local_rabbit
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host:
port: 5672
username:
password:
virtual-host: /
Привязкаinput будет использовать обмен под названиемqueue.log.messages, а привязкаoutput будет использовать обменqueue.pretty.log.messages. Обе привязки будут использовать связкуlocal_rabbit.
Обратите внимание, что нам не нужно заранее создавать обмены RabbitMQ или очереди. При запуске приложенияboth exchanges are automatically created.
Чтобы протестировать приложение, мы можем использовать сайт управления RabbitMQ для публикации сообщения. В панелиPublish Message обменаqueue.log.messages нам нужно ввести запрос в формате JSON.
5.3. Настройка преобразования сообщений
Spring Cloud Stream позволяет нам применять преобразование сообщений для определенных типов контента. В приведенном выше примере вместо использования формата JSON мы хотим предоставить простой текст.
Для этого нужноapply a custom transformation to LogMessage using a MessageConverter:
@SpringBootApplication
@EnableBinding(Processor.class)
public class MyLoggerServiceApplication {
//...
@Bean
public MessageConverter providesTextPlainMessageConverter() {
return new TextPlainMessageConverter();
}
//...
}
public class TextPlainMessageConverter extends AbstractMessageConverter {
public TextPlainMessageConverter() {
super(new MimeType("text", "plain"));
}
@Override
protected boolean supports(Class> clazz) {
return (LogMessage.class == clazz);
}
@Override
protected Object convertFromInternal(Message> message,
Class> targetClass, Object conversionHint) {
Object payload = message.getPayload();
String text = payload instanceof String
? (String) payload
: new String((byte[]) payload);
return new LogMessage(text);
}
}
После применения этих изменений, возвращаясь к панелиPublish Message, если мы установим заголовок «contentTypes» на «text/plain» и полезную нагрузку на «Hello World», он должен работать как раньше.
5.4. Группы потребителей
При запуске нескольких экземпляров нашего приложенияevery time there is a new message in an input channel, all subscribers will be notified.
В большинстве случаев нам нужно, чтобы сообщение было обработано только один раз. Spring Cloud Stream реализует это поведение через группы потребителей.
Чтобы включить такое поведение, каждая привязка потребителя может использовать свойствоspring.cloud.stream.bindings.<CHANNEL>.group для указания имени группы:
spring:
cloud:
stream:
bindings:
input:
destination: queue.log.messages
binder: local_rabbit
group: logMessageConsumers
...
6. Микросервисы, управляемые сообщениями
В этом разделе мы представляем все необходимые функции для запуска наших приложений Spring Cloud Stream в контексте микросервисов.
6.1. Увеличение масштаба
Когда работает несколько приложений, важно обеспечить правильное распределение данных между потребителями. Для этого Spring Cloud Stream предоставляет два свойства:
-
spring.cloud.stream.instanceCount - количество запущенных приложений
-
spring.cloud.stream.instanceIndex - индекс текущего приложения
Например, если мы развернули два экземпляра указанного выше приложенияMyLoggerServiceApplication, свойствоspring.cloud.stream.instanceCount должно быть 2 для обоих приложений, а свойствоspring.cloud.stream.instanceIndex должно быть 0 и 1 соответственно.
Эти свойства устанавливаются автоматически, если мы развертываем приложения Spring Cloud Stream с помощью Spring Data Flow, как описано вthis article.
6.2. Разметка
Событиями домена могут быть сообщенияPartitioned. Это помогает, когда мыscaling up the storage and improving application performance.
Событие домена обычно имеет ключ раздела, поэтому он попадает в тот же раздел со связанными сообщениями.
Предположим, мы хотим, чтобы сообщения журнала были разделены по первой букве сообщения, которая будет ключом раздела, и сгруппированы в два раздела.
Будет один раздел для сообщений журнала, которые начинаются сA-M, и другой раздел дляN-Z.. Это можно настроить с помощью двух свойств:
-
spring.cloud.stream.bindings.output.producer.partitionKeyExpression - выражение для разделения полезной нагрузки
-
spring.cloud.stream.bindings.output.producer.partitionCount - количество групп
Sometimes the expression to partition is too complex to write it in only one line. Для этих случаев мы можем написать нашу собственную стратегию разделения, используя свойствоspring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass.
6.3. Индикатор здоровья
В контексте микросервисов нам также потребуетсяdetect when a service is down or starts failing. Spring Cloud Stream предоставляет свойствоmanagement.health.binders.enabled для включения индикаторов работоспособности для связывателей.
При запуске приложения мы можем запросить состояние работоспособности вhttp://<host>:<port>/health.
7. Заключение
В этом руководстве мы представили основные концепции Spring Cloud Stream и показали, как его использовать, на нескольких простых примерах в RabbitMQ. Более подробную информацию о Spring Cloud Stream можно найти наhere.
Исходный код этой статьи можно найти вover on GitHub.