Spring Cloud Streamの紹介

Spring Cloud Streamの紹介

1. 概要

Spring Cloud Streamは、helps in creating event-driven or message-driven microservicesであるSpringBootとSpringIntegrationの上に構築されたフレームワークです。

この記事では、Spring CloudStreamの概念と構成をいくつかの簡単な例とともに紹介します。

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 [スマートエンドポイントとダムパイプ]」の原則に従います。 エンドポイント間の通信は、RabbitMQやApache Kafkaなどのメッセージングミドルウェアパーティによって駆動されます。 Services communicate by publishing domain events via these endpoints or channels

Spring Cloud Streamフレームワークを構成する概念と、メッセージ駆動型サービスを構築するために知っておく必要のある重要なパラダイムについて見ていきましょう。

3.1. 構築物

inputバインディングをリッスンし、outputバインディングに応答を送信するSpring CloudStreamの単純なサービスを見てみましょう。

@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は、インターフェースProcessor内で定義されたチャネルINPUTOUTPUTをバインドするようにアプリケーションを構成します。 Both channels are bindings that can be configured to use a concrete messaging-middleware or binder.

これらすべての概念の定義を見てみましょう。

  • Bindings —入力チャネルと出力チャネルを宣言的に識別するインターフェイスのコレクション

  • Binder —KafkaやRabbitMQなどのメッセージングミドルウェアの実装

  • Channel —メッセージングミドルウェアとアプリケーション間の通信パイプを表します

  • StreamListenersMessageConverterがミドルウェア固有のイベントとドメインオブジェクトタイプ/ POJOの間でシリアル化/逆シリアル化を行った後、チャネルからのメッセージで自動的に呼び出されるBeanのメッセージ処理メソッド

  • MessageSchemas —メッセージのシリアル化と逆シリアル化に使用されます。これらのスキーマは、場所から静的に読み取るか、動的にロードして、ドメインオブジェクトタイプの進化をサポートします。

3.2. コミュニケーションパターン

Messages designated to destinations are delivered by the Publish-Subscribe messaging pattern.パブリッシャーはメッセージをトピックに分類し、それぞれが名前で識別される。 サブスクライバーは、1つ以上のトピックに関心を示します。 ミドルウェアはメッセージをフィルタリングし、興味深いトピックのトピックをサブスクライバーに配信します。

これで、サブスクライバーをグループ化できました。 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. カスタムチャネル

上記の例では、Spring Cloudが提供するProcessorインターフェースを使用しました。このインターフェースには、1つの入力チャネルと1つの出力チャネルしかありません。

1つの入力チャネルと2つの出力チャネルなど、別の何かが必要な場合は、カスタムプロセッサを作成できます。

public interface MyProcessor {
    String INPUT = "myInput";

    @Input
    SubscribableChannel myInput();

    @Output("myOutput")
    MessageChannel anOutput();

    @Output
    MessageChannel anotherOutput();
}

Springは、このインターフェイスの適切な実装を提供します。 チャネル名は、@Output(“myOutput”)のような注釈を使用して設定できます。

それ以外の場合、Springはメソッド名をチャネル名として使用します。 したがって、myInputmyOutput、およびanotherOutputという3つのチャネルがあります。

ここで、値が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アノテーションを使用すると、SpEL expressionsで定義した任意の条件を使用してfilter the messages we expect in the consumerを実行することもできます。

例として、メッセージを異なる出力にルーティングする別のアプローチとして条件付きディスパッチを使用できます。

@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

または、this dependencyを含めることで、RabbitMQのバインダーライブラリをクラスパスに追加できます。


    org.springframework.cloud
    spring-cloud-stream-binder-rabbit
    1.3.0.RELEASE

バインダーの実装が提供されていない場合、Springはチャネル間の直接メッセージ通信を使用します。

5.2. RabbitMQ構成

セクション3.1の例をRabbitMQバインダーを使用するように構成するには、src/main/resourcesにあるapplication.ymlを更新する必要があります。

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管理サイトを使用してメッセージを公開できます。 交換queue.log.messagesPublish Messageパネルで、リクエストを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

ほとんどの場合、メッセージを1回だけ処理する必要があります。 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は2つのプロパティを提供します:

  • spring.cloud.stream.instanceCount —実行中のアプリケーションの数

  • spring.cloud.stream.instanceIndex —現在のアプリケーションのインデックス

たとえば、上記のMyLoggerServiceApplicationアプリケーションの2つのインスタンスをデプロイした場合、プロパティspring.cloud.stream.instanceCountは両方のアプリケーションで2になり、プロパティspring.cloud.stream.instanceIndexはそれぞれ0と1になります。

これらのプロパティは、this articleで説明されているようにSpring DataFlowを使用してSpringCloudStreamアプリケーションをデプロイすると自動的に設定されます。

6.2. パーティショニング

ドメインイベントはPartitionedメッセージである可能性があります。 これは、scaling up the storage and improving application performanceの場合に役立ちます。

通常、ドメインイベントにはパーティションキーがあり、関連するメッセージを含む同じパーティションになります。

ログメッセージをメッセージの最初の文字(パーティションキー)でパーティション化し、2つのパーティションにグループ化するとします。

A-Mで始まるログメッセージ用に1つのパーティションがあり、N-Z.用に別のパーティションがあります。これは、次の2つのプロパティを使用して構成できます。

  • 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にあります。