Apache Kafka + Spring のメッセージ リスニング コンテナー

1. メッセージの受信

メッセージの受信: MessageListenerContainer を構成してメッセージ リスナーを提供するか、@KafkaListener アノテーションを使用することで、メッセージを受信できます。この章では主に、MessageListenerContainerの設定とメッセージリスナーの提供によるメッセージの受信方法について説明します。

1.1、メッセージリスナー

メッセージ リスニング コンテナーを使用する場合、データを受信するためにリスナーを提供する必要があります。現在、メッセージ リスナーをサポートするインターフェイスは 8 つあります。

public interface MessageListener<K, V> {
    
     
     // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。
    void onMessage(ConsumerRecord<K, V> data);
}

public interface AcknowledgingMessageListener<K, V> {
    
     
    // 当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。
    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment);
}

public interface ConsumerAwareMessageListener<K, V> extends MessageListener<K, V> {
    
     
    // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(ConsumerRecord<K, V> data, Consumer<?, ?> consumer);

}

public interface AcknowledgingConsumerAwareMessageListener<K, V> extends MessageListener<K, V> {
    
     
    //当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的各个 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(ConsumerRecord<K, V> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

public interface BatchMessageListener<K, V> {
    
     
   //当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。使用此接口时不支持 AckMode.RECORD,因为侦听器会获得完整的批次。
    void onMessage(List<ConsumerRecord<K, V>> data);

}

public interface BatchAcknowledgingMessageListener<K, V> {
    
     
    // 当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。
    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment);

}

public interface BatchConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> {
    
     
    // 当使用自动提交或容器管理的提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。使用此接口时不支持 AckMode.RECORD,因为侦听器会获得完整的批次。提供对 Consumer 对象的访问。
    void onMessage(List<ConsumerRecord<K, V>> data, Consumer<?, ?> consumer);

}

public interface BatchAcknowledgingConsumerAwareMessageListener<K, V> extends BatchMessageListener<K, V> {
    
     
    //当使用手动提交方法之一时,使用此接口处理从 Kafka 消费者 poll() 操作接收到的所有 ConsumerRecord 实例。提供对 Consumer 对象的访问。
    void onMessage(List<ConsumerRecord<K, V>> data, Acknowledgment acknowledgment, Consumer<?, ?> consumer);

}

注: 1. コンシューマ オブジェクトはスレッドセーフではありません; 2. コンシューマの位置やリスナー内のコミットされたオフセットに影響を与える Consumer<?, ?> メソッドを実行しないでください; コンテナはこの情報を管理する必要があります。

2. メッセージ監視コンテナ

2.1. 実装方法

MessageListenerContainer は 2 つの実装を提供します:
1. KafkaMessageListenerContainer、
2. ConcurrentMessageListenerContainer

2.1.1、KafkaMessageListenerコンテナ

2.1.1.1. 基本概念

KafkaMessageListenerContainer は、単一スレッド上のすべてのトピックまたはパーティションからすべてのメッセージを受信します。ConcurrentMessageListenerContainer を 1 つ以上の KafkaMessageListenerContainer インスタンスに委任して、マルチスレッドの使用を提供します。

  • バージョン 2.2.7 以降では、RecordInterceptor リスナー コンテナを追加できるようになり、リスナーが呼び出される前に呼び出され、レコードの検査または変更が可能になります。インターセプターが null を返した場合、リスナーは呼び出されません。
  • バージョン 2.7 以降、リスナーの終了後に呼び出されるメソッドが追加されました (通常は、または例外をスローすることによって)。
  • バッチ インターセプターは、バッチ リスナーと同様の機能を提供します。
  • さらに、ConsumerAwareRecordInterceptor (および BatchInterceptor) は Consumer<?, ?> へのアクセスを提供します。たとえば、これを使用してインターセプタのコンシューマ メトリクスにアクセスできます。
  • CompositeRecordInterceptor と CompositeBatchInterceptor は複数のインターセプターを呼び出すことができます。
  • デフォルトでは、トランザクションを使用する場合、トランザクションの開始後にインターセプターが呼び出されます。バージョン 2.3.4 以降では、トランザクションの開始前にインターセプターを呼び出すように、リスナー コンテナーの interceptBeforeTx プロパティを設定できます。
  • バージョン 2.3.8、2.4.6 以降、ConcurrentMessageListenerContainer は同時実行数が 1 より大きい場合に静的メンバーシップをサポートします。group.instance.id のサフィックスは -n で、1 から始まります。これを session.timeout.ms の値の増加とともに使用すると、アプリケーション インスタンスの再起動時などのリバランス イベントを減らすことができます。
  • 静的メンバーシップは、ストリーミング アプリケーション、コンシューマー グループ、およびグループ リバランス プロトコル上に構築されたその他のアプリケーションの可用性を向上させることを目的としています。リバランス プロトコルは、グループ コーディネーターに依存して、グループ メンバーにエンティティ ID を割り当てます。これらの生成された ID の有効期間は短く、メンバーが再起動して再参加すると変更されます。コンシューマベースのアプリケーションの場合、この「動的メンバーシップ」により、コードのデプロイメント、構成の更新、定期的な再起動などの管理操作中に、タスクの大部分が別のインスタンスに再割り当てされる可能性があります。大規模なステートフル アプリケーションの場合、シャッフル タスクが処理される前にローカル状態を復元するのに時間がかかり、その結果、アプリケーションが部分的または完全に使用できなくなる可能性があります。この観察に触発されて、Kafka のグループ管理プロトコルにより、グループ メンバーは次のことを行うことができます。永続的なエンティティ IDこれらの ID に基づいて、グループ メンバーシップは同じままであるため、リバランスはトリガーされません。

同様に、インターセプタはコンシューマの位置やコミットされたオフセットに影響を与えるメソッドを実装してはなりません。コンテナはこの情報を管理する必要があります。

インターセプタが (新しいレコードを作成することによって) レコードを変更する場合、レコードの損失などの意図しない副作用を避けるために、トピック、パーティション、およびオフセットは変更されないままでなければなりません。

2.1.1.2、KafkaMessageListenerContainerの使用方法

  • KafkaMessageListenerContainer コンストラクター

    public KafkaMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                      ContainerProperties containerProperties)
    

    コンストラクターは、オブジェクト内のトピックとパーティション、およびその他の構成に関する情報を ConsumerFactory から受け取ります。

  • コンテナプロパティ(ContainerProperties)には3つのコンストラクタが含まれており、以下で1つずつ紹介していきます。
    1. TopicPartitionOffset をパラメータとして取得します

    public ContainerProperties(TopicPartitionOffset... topicPartitions)
    

    コンストラクターは、TopicPartitionOffset パラメーターの配列を受け取り、コンテナーが使用するパーティションを (コンシューマーの assign() メソッドを使用して) オプションの初期オフセットとともに明示的に示します。デフォルトでは、正の値は絶対オフセットであり、負の値はパーティション内の現在の最後のオフセットに対する相対値です。TopicPartitionOffset は、コンストラクターに追加パラメーターを提供します。ブール値 true の場合、コンテナー開始時のコンシューマーの現在位置を基準とした初期オフセット (正または負)。
    2. 文字列をパラメータとして取得します

    public ContainerProperties(String... topics)
    

    コンストラクターはトピックの配列を受け取り、Kafka は属性 group.id に基づいてパーティションを割り当てます。
    パターンをパラメーターとして受け取り、グループ 3にパーティションを割り当てます。

    public ContainerProperties(Pattern topicPattern)
    

    このコンストラクターは、正規表現パターンを使用してトピックを選択します。

  • リスナーをコンテナに割り当てる方法
    リスナーにはコンテナもありますが、コンテナにリスナーを割り当てるにはどうすればよいですか? MessageListener をコンテナに割り当てるには、コンテナの作成時に ContainerProps.setMessageListener メソッドを使用します。

    ContainerProperties containerProps = new ContainerProperties("topic1", "topic2");
    containerProps.setMessageListener(new MessageListener<Integer, String>() {
          
          
      ...
    });
    DefaultKafkaConsumerFactory<Integer, String> cf =
                          new DefaultKafkaConsumerFactory<>(consumerProps());
    KafkaMessageListenerContainer<Integer, String> container =
                          new KafkaMessageListenerContainer<>(cf, containerProps);
    return container;
    

    DefaultKafkaConsumerFactory を作成するときに、上記のプロパティのみを受け入れるコンストラクターを使用すると、構成からキーと値のデシリアライザー クラスを取得することになることに注意してください。あるいは、デシリアライザー インスタンスをキーや値の DefaultKafkaConsumerFactory コンストラクターに渡すこともできます。この場合、すべてのコンシューマーが同じインスタンスを共有します。もう 1 つのオプションは、サプライヤー (バージョン 2.3 以降) を提供することです。これは、コンシューマーごとに個別のデシリアライザー インスタンスを取得するために使用されます。

     DefaultKafkaConsumerFactory<Integer, CustomValue> cf =
                          new DefaultKafkaConsumerFactory<>(consumerProps(), null, () -> new      CustomValueDeserializer());
     KafkaMessageListenerContainer<Integer, String> container =
                          new KafkaMessageListenerContainer<>(cf, containerProps);
    return container;
    

バージョン 2.3.5 以降、authorizationExceptionRetryInterval と呼ばれる新しいコンテナー プロパティが導入されました。これにより、コンテナーは KafkaConsumer から AuthorizationException を取得した後、メッセージの取得を再試行します。これは、たとえば、設定されたユーザーが特定のトピックの読み取りを拒否された場合に発生する可能性があります。authorizationExceptionRetryInterval を定義すると、適切な権限を付与した直後にアプリケーションを再開できるようになります。

2.1.2、ConcurrentMessageListenerContainer

ConcurrentMessageListenerContainer には、KafkaListenerContainer と同様のコンストラクターが 1 つだけあります。

public ConcurrentMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
                            ContainerProperties containerProperties)

これには同時実行プロパティがあり、このプロパティの役割は複数の KafkaMessageListenerContainer インスタンスを作成することです。たとえば、container.setConcurrency(3) は 3 つの KafkaMessageListenerContainer インスタンスを作成します。

複数のトピックをリッスンする場合、デフォルトのパーティション分散が予期したものと異なる場合があります。たとえば、それぞれ 5 つのパーティションを持つ 3 つのトピックがあり、concurrency=15 を使用したい場合、アクティブなコンシューマは 5 つだけ表示され、それぞれが各トピックからパーティションを割り当てられ、残りの 10 個のコンシューマはアイドル状態になります。これは、デフォルトの Kafka PartitionAssignor が RangeAssignor であるためです。この場合、すべてのコンシューマにパーティションを割り当てる RoundRobinAssignor の使用を検討する必要があります。次に、各コンシューマーにトピックまたはパーティションが割り当てられます。DefaultKafkaConsumerFactory に提供されるプロパティで、partition.assignment.strategy コンシューマ プロパティを設定して、PartitionAssignor を変更できます。(ConsumerConfigs.PARTITION_ASSIGNMENT_STRATEGY_CONFIG)。
これは Springboot で実行できます:
spring.kafka.consumer.properties.partition.assignment.strategy=
org.apache.kafka.clients.consumer.RoundRobinAssignor

コンテナー プロパティが TopicPartitionOffset で構成されている場合、ConcurrentMessageListenerContainer は、委任する KafkaMessageListenerContainer インスタンス間で TopicPartitionOffset インスタンスを分散します。

6 つの TopicPartitionOffset インスタンスが同時実行性 3 で提供され、各コンテナには 2 つのパーティションがあると仮定します。5 つの TopicPartitionOffset インスタンスでは
、2 つのコンテナーが 2 つのパーティションを取得し、3 番目のコンテナーが 1 つのパーティションを取得します。同時実行数が TopicPartitions の数より大きい場合は、各コンテナーがパーティションを取得できるように同時実行数を減らします。

3. オフセット

Spring にはいくつかのオフセット オプションが用意されており、enable.auto.commit コンシューマ プロパティが true の場合、Kafka はその設定に従ってオフセットを自動的に送信します。false の場合、コンテナはさまざまな AckMode 設定をサポートします。デフォルトの AckMode は BATCH です。

バージョン 2.3 以降、フレームワークは、構成で明示的に設定されていない限り、enable.auto.commit を false に設定します。以前は、このプロパティが設定されていない場合、Kafka のデフォルト (true) が使用されていました。

コンシューマのpoll()メソッドは、1つ以上のConsumerRecordを返します。各レコードに対して MessageListener を呼び出します。次のリストは、コンテナが各 AckMode に対して実行するアクション (トランザクションが使用されていない場合) を説明しています。

  • RECORD: レコードの処理後にリスナーが戻ったときにオフセットをコミットします。

  • BATCH:poll() によって返されたすべてのレコードが処理されたときにオフセットをコミットします。

  • TIME:poll() によって返されたすべてのレコードが処理されたら、最後のコミットから ackTime が経過するたびにオフセットをコミットします。

  • COUNT: 最後のコミット以降に ackCount レコードが受信されている限り、poll() によって返されたすべてのレコードが処理されたときにオフセットをコミットします。

  • COUNT_TIME: TIME および COUNT と似ていますが、どちらかの条件が true の場合にコミットを実行します。

  • 手動: メッセージ リスナーは、acknowledge() 確認応答を担当します。その後は、BATCH と同じセマンティクスが適用されます。

  • MANUAL_IMMEDIATE: リスナーが Acknowledgment.acknowledge() メソッドを呼び出すと、オフセットはすぐにコミットされます。

トランザクションを使用する場合、リスナーのタイプ (レコードまたはバッチ) に応じて、意味的には RECORD または BATCH と同等のオフセットがトランザクションに送信されます。MANUAL および MANUAL_IMMEDIATE では、リスナーが AcknowledgingMessageListener または BatchAcknowledgingMessageListener である必要があります。

syncCommits コンテナーのプロパティに応じて、コンシューマーで commitSync() または commitAsync() メソッドを使用します。デフォルトでは、syncCommits は true です。

著者の個人的な提案では、ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG を false に設定することを提案しています。

バージョン 2.3 以降、Acknowledgment インターフェイスに 2 つのメソッド nack(long sleep) と nack(intindex, long sleep) が追加されました。1 つ目はレコード リスナーで使用され、2 つ目はバッチ リスナーで使用されます。リスナーの種類に対して間違ったメソッドを呼び出すと、IllegalStateException がスローされます。その前の彼はこんな感じでした。

public interface Acknowledgment {
    
    

    void acknowledge();

}
  • 部分的なバッチを送信する場合は、nack() を使用します。
  • トランザクションを使用する場合は、AckMode を MANUAL に設定します。
  • nack() を呼び出すと、正常に処理されたレコードのオフセットがトランザクションに送信されます。
  • nack() は、リスナーを呼び出したコンシューマ スレッドでのみ呼び出すことができます。
  • nack() が呼び出されると、すべての保留中のオフセットがコミットされ、前のポーリングからの残りのレコードが破棄され、失敗した未処理のレコードが次のポーリング ( ) で再配信されるように、そのパーティションに対してルックアップが実行されます。
  • sleep パラメータを設定すると、再配信前にコンシューマ スレッドを一時停止できます。これは、コンテナーが SeekToCurrentErrorHandler で構成されている場合に例外がスローされる方法と似ています。

グループ管理によるパーティション割り当てを使用する場合、スリープ パラメータ (および以前にポーリングされたレコードの処理にかかった時間) がコンシューマの max.poll.interval.ms プロパティよりも小さいことを確認することが非常に重要です。

4. リスナーコンテナが自動的に起動します

リスナー コンテナは SmartLifecycle を実装し、autoStartup のデフォルトは true です。コンテナーは遅い段階 (Integer.MAX-VALUE - 100) で開始されます。リスナーからのデータを処理するために SmartLifecycle を実装する他のコンポーネントは、早い段階で開始する必要があります。-100 では、後の段階のための余地が残され、コンテナーの後でコンポーネントが自動的に開始できるようになります。

おすすめ

転載: blog.csdn.net/qq_35241329/article/details/132312378