Einführung in Spring Cloud Stream
1. Überblick
Spring Cloud Stream ist ein Framework, das auf Spring Boot und Spring Integrationhelps in creating event-driven or message-driven microservices basiert.
In diesem Artikel werden Konzepte und Konstrukte von Spring Cloud Stream anhand einiger einfacher Beispiele vorgestellt.
2. Maven-Abhängigkeiten
Um zu beginnen, müssen wir dieSpring Cloud Starter Stream with the broker RabbitMQ Maven-Abhängigkeit als Messaging-Middleware zu unserenpom.xml hinzufügen:
org.springframework.cloud
spring-cloud-starter-stream-rabbit
1.3.0.RELEASE
Und wir werden diemodule dependency from Maven Centralhinzufügen, um auch die JUnit-Unterstützung zu aktivieren:
org.springframework.cloud
spring-cloud-stream-test-support
1.3.0.RELEASE
test
3. Hauptkonzepte
Die Microservices-Architektur folgt dem Prinzip „https://martinfowler.com/articles/microservices.html#SmartEndpointsAndDumbPipes[smart endpoints and dumb pipes]“. Die Kommunikation zwischen Endpunkten wird von Messaging-Middleware-Parteien wie RabbitMQ oder Apache Kafka gesteuert. Services communicate by publishing domain events via these endpoints or channels.
Lassen Sie uns die Konzepte des Spring Cloud Stream-Frameworks zusammen mit den wesentlichen Paradigmen durchgehen, die wir kennen müssen, um nachrichtengesteuerte Dienste zu erstellen.
3.1. Konstruiert
Schauen wir uns einen einfachen Dienst in Spring Cloud Stream an, der die Bindung voninputabhört und eine Antwort auf die Bindung vonoutputendet:
@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()));
}
}
Die Annotation@EnableBinding konfiguriert die Anwendung so, dass die in der SchnittstelleProcessor definierten KanäleINPUT undOUTPUT gebunden werden. Both channels are bindings that can be configured to use a concrete messaging-middleware or binder.
Werfen wir einen Blick auf die Definition all dieser Konzepte:
-
Bindings - eine Sammlung von Schnittstellen, die die Eingangs- und Ausgangskanäle deklarativ identifizieren
-
Binder - Implementierung von Messaging-Middleware wie Kafka oder RabbitMQ
-
Channel - repräsentiert die Kommunikationsleitung zwischen Messaging-Middleware und der Anwendung
-
StreamListeners - Nachrichtenbehandlungsmethoden in Beans, die automatisch für eine Nachricht aus dem Kanal aufgerufen werden, nachdemMessageConverter die Serialisierung / Deserialisierung zwischen Middleware-spezifischen Ereignissen und Domänenobjekttypen / POJOs durchgeführt hat
-
MessageSchemas - Diese Schemas werden für die Serialisierung und Deserialisierung von Nachrichten verwendet. Sie können statisch von einem Speicherort gelesen oder dynamisch geladen werden, um die Entwicklung von Domänenobjekttypen zu unterstützen
3.2. Kommunikationsmuster
Messages designated to destinations are delivered by the Publish-Subscribe messaging pattern. Publisher kategorisieren Nachrichten in Themen, die jeweils durch einen Namen gekennzeichnet sind. Abonnenten zeigen Interesse an einem oder mehreren Themen. Die Middleware filtert die Nachrichten und übermittelt diese zu den interessanten Themen an die Abonnenten.
Jetzt konnten die Abonnenten gruppiert werden. Einconsumer group ist eine Gruppe von Abonnenten oder Verbrauchern, die durch eingroup id gekennzeichnet sind und in denen Nachrichten von einem Thema oder einer Themenpartition auf eine lastausgeglichene Weise übermittelt werden.
4. Programmiermodell
In diesem Abschnitt werden die Grundlagen zum Erstellen von Spring Cloud Stream-Anwendungen beschrieben.
4.1. Funktionsprüfung
Die Testunterstützung ist eine Binder-Implementierung, die die Interaktion mit den Kanälen und das Überprüfen von Nachrichten ermöglicht.
Senden wir eine Nachricht an den oben genanntenenrichLogMessage-Dienst und prüfen, ob die Antwort den Text“[1]: “ am Anfang der Nachricht enthält:
@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. Benutzerdefinierte Kanäle
Im obigen Beispiel haben wir die von Spring Cloud bereitgestellteProcessor-Schnittstelle verwendet, die nur einen Eingangs- und einen Ausgangskanal hat.
Wenn wir etwas anderes benötigen, wie einen Ein- und zwei Ausgangskanäle, können wir einen benutzerdefinierten Prozessor erstellen:
public interface MyProcessor {
String INPUT = "myInput";
@Input
SubscribableChannel myInput();
@Output("myOutput")
MessageChannel anOutput();
@Output
MessageChannel anotherOutput();
}
Spring wird die ordnungsgemäße Implementierung dieser Schnittstelle für uns bereitstellen. Die Kanalnamen können mit Anmerkungen wie in@Output(“myOutput”) festgelegt werden.
Andernfalls verwendet Spring die Methodennamen als Kanalnamen. Daher haben wir drei Kanäle mit den NamenmyInput,myOutput undanotherOutput.
Stellen wir uns nun vor, wir möchten die Nachrichten an eine Ausgabe weiterleiten, wenn der Wert kleiner als 10 ist, und an eine andere Ausgabe, wenn der Wert größer oder gleich 10 ist:
@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. Bedingter Versand
Mit der Annotation@StreamListener können wir auchfilter the messages we expect in the consumer mit jeder Bedingung definieren, die wir mitSpEL expressions definieren.
Als Beispiel könnten wir das bedingte Dispatching als einen anderen Ansatz verwenden, um Nachrichten an verschiedene Ausgaben weiterzuleiten:
@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));
}
Die einzigenlimitation of this approach is that these methods must not return a value.
5. Konfiguration
Richten Sie die Anwendung ein, die die Nachricht vom RabbitMQ-Broker verarbeitet.
5.1. Binder-Konfiguration
Wir können unsere Anwendung so konfigurieren, dass die Standard-Binder-Implementierung überMETA-INF/spring.binders verwendet wird:
rabbit:\
org.springframework.cloud.stream.binder.rabbit.config.RabbitMessageChannelBinderConfiguration
Oder wir können die Binder-Bibliothek für RabbitMQ zum Klassenpfad hinzufügen, indem wirthis dependency einschließen:
org.springframework.cloud
spring-cloud-stream-binder-rabbit
1.3.0.RELEASE
Wenn keine Binder-Implementierung bereitgestellt wird, verwendet Spring die direkte Nachrichtenkommunikation zwischen den Kanälen.
5.2. RabbitMQ-Konfiguration
Um das Beispiel in Abschnitt 3.1 für die Verwendung des RabbitMQ-Binders zu konfigurieren, müssen Sie dieapplication.yml aktualisieren, die sich beisrc/main/resources befinden:
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: /
Dieinput-Bindung verwendet den Austauschqueue.log.messages, und dieoutput-Bindung verwendet den Austauschqueue.pretty.log.messages. Beide Bindungen verwenden das Bindemittellocal_rabbit.
Beachten Sie, dass wir die RabbitMQ-Börsen oder -Warteschlangen nicht im Voraus erstellen müssen. Beim Ausführen der Anwendungboth exchanges are automatically created.
Zum Testen der Anwendung können wir die RabbitMQ-Verwaltungssite verwenden, um eine Nachricht zu veröffentlichen. ImPublish Message-Bereich des Exchangequeue.log.messages müssen wir die Anforderung im JSON-Format eingeben.
5.3. Anpassen der Nachrichtenkonvertierung
Mit Spring Cloud Stream können wir die Nachrichtenkonvertierung für bestimmte Inhaltstypen anwenden. Im obigen Beispiel möchten wir anstelle des JSON-Formats einfachen Text bereitstellen.
Dazu gehen wir zuapply 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);
}
}
Wenn Sie nach dem Anwenden dieser Änderungen zum BereichPublish Message zurückkehren und den Header "contentTypes" auf "text/plain" und die Nutzlast auf "Hello World" setzen, sollte dies der Fall sein arbeite wie zuvor.
5.4. Verbrauchergruppen
Wenn Sie mehrere Instanzen unserer Anwendung ausführen,every time there is a new message in an input channel, all subscribers will be notified.
In den meisten Fällen muss die Nachricht nur einmal verarbeitet werden. Spring Cloud Stream implementiert dieses Verhalten über Konsumentengruppen.
Um dieses Verhalten zu aktivieren, kann jede Verbraucherbindung die Eigenschaftspring.cloud.stream.bindings.<CHANNEL>.groupverwenden, um einen Gruppennamen anzugeben:
spring:
cloud:
stream:
bindings:
input:
destination: queue.log.messages
binder: local_rabbit
group: logMessageConsumers
...
6. Nachrichtengesteuerte Microservices
In diesem Abschnitt werden alle erforderlichen Funktionen zum Ausführen unserer Spring Cloud Stream-Anwendungen in einem Microservices-Kontext vorgestellt.
6.1. Hochskalieren
Wenn mehrere Anwendungen ausgeführt werden, ist es wichtig sicherzustellen, dass die Daten ordnungsgemäß auf die Verbraucher aufgeteilt werden. Zu diesem Zweck bietet Spring Cloud Stream zwei Eigenschaften:
-
spring.cloud.stream.instanceCount - Anzahl der laufenden Anwendungen
-
spring.cloud.stream.instanceIndex - Index der aktuellen Anwendung
Wenn wir beispielsweise zwei Instanzen der obigen AnwendungMyLoggerServiceApplication bereitgestellt haben, sollte die Eigenschaftspring.cloud.stream.instanceCount für beide Anwendungen 2 und die Eigenschaftspring.cloud.stream.instanceIndex 0 bzw. 1 sein.
Diese Eigenschaften werden automatisch festgelegt, wenn wir die Spring Cloud Stream-Anwendungen mithilfe von Spring Data Flow bereitstellen, wie inthis article beschrieben.
6.2. Partitionierung
Die Domänenereignisse könnenPartitioned Nachrichten sein. Dies hilft, wenn wirscaling up the storage and improving application performance sind.
Das Domänenereignis verfügt normalerweise über einen Partitionsschlüssel, sodass es in derselben Partition mit zugehörigen Nachrichten endet.
Angenommen, wir möchten, dass die Protokollnachrichten nach dem ersten Buchstaben in der Nachricht, dem Partitionsschlüssel, partitioniert und in zwei Partitionen gruppiert werden.
Es würde eine Partition für die Protokollnachrichten geben, die mitA-M beginnen, und eine andere Partition fürN-Z.. Dies kann mithilfe von zwei Eigenschaften konfiguriert werden:
-
spring.cloud.stream.bindings.output.producer.partitionKeyExpression - der Ausdruck zum Partitionieren der Nutzdaten
-
spring.cloud.stream.bindings.output.producer.partitionCount - die Anzahl der Gruppen
Sometimes the expression to partition is too complex to write it in only one line. In diesen Fällen können wir unsere benutzerdefinierte Partitionsstrategie mit der Eigenschaftspring.cloud.stream.bindings.output.producer.partitionKeyExtractorClass schreiben.
6.3. Gesundheitsindikator
In einem Microservices-Kontext müssen wir auchdetect when a service is down or starts failing. Spring Cloud Stream stellt die Eigenschaftmanagement.health.binders.enabled bereit, um die Integritätsindikatoren für Bindemittel zu aktivieren.
Beim Ausführen der Anwendung können wir den Integritätsstatus beihttp://<host>:<port>/health abfragen.
7. Fazit
In diesem Tutorial haben wir die Hauptkonzepte von Spring Cloud Stream vorgestellt und anhand einiger einfacher Beispiele über RabbitMQ gezeigt, wie man es verwendet. Weitere Informationen zu Spring Cloud Stream finden Sie unterhere.
Der Quellcode für diesen Artikel istover on GitHub.