web-dev-qa-db-ja.com

Kafkaリバランス。重複した処理の問題

私はコンシューマーワーカーアプリケーションを持っています。これは内部でX個のスレッドを起動し、各スレッドはKafkaCosnumerを生成しています。 Cosnumerは同じgroupIdを持ち、同じトピックにサブスクライブしています。したがって、各コンシューマーがパーティションの公平なシェアを取得できるようにします。

処理の性質上、メッセージを失うことも、重複を許可することもできません。私が実行しているkafkaのバージョンは0.10.2.1です。

私が直面している問題は次のとおりです。コンシューマスレッド1がメッセージの消費を開始し、poll()でメッセージのバッチを取得します。また、ConsumerRebalanceListenerを実装して、メッセージが正常に処理されるたびにoffsetsマップに追加されるようにします。 (以下のコードを参照してください。)したがって、リバランスが発生すると、パーティションが他のコンシューマーに再割り当てされる前に、オフセットをコミットできます。そのバッチを処理するために、_max.poll.interval.ms_よりも時間がかかる場合があります。これにより、リバランスが発生し、パーティションがコンシューマー1からプルされ、コンシューマー2に割り当てられます。メッセージを処理し、その間にコンシューマー2は最後のオフセット(RebalanceListenerによってコミットされた)から取得し、同じメッセージを処理します。

すでに他のコンシューマーに割り当てられているループ内のメッセージの処理を停止できるように、パーティションが取り消されたことをコンシューマーに通知する方法はありますか?

_public class RebalanceListener<K, V> implements ConsumerRebalanceListener {

    private final KafkaConsumer<K, V> consumer;

    private static final ConcurrentMap<TopicPartition, OffsetAndMetadata> CURRENT_OFFSETS =
            Maps.newConcurrentMap();

    private static final Logger LOGGER = LoggerFactory.getLogger(RebalanceListener.class);

    public RebalanceListener(KafkaConsumer<K, V> consumer) {
        this.consumer = consumer;
    }

    public void addOffset(String topic, int partition, long offset) {
        LOGGER.debug("message=Adding offset to offsets map, topic={}, partition={}, offset={}",
                topic, partition, offset);
        CURRENT_OFFSETS.put(new TopicPartition(topic, partition),
                new OffsetAndMetadata(offset, "commit"));
    }

    public Map<TopicPartition, OffsetAndMetadata> getCurrentOffsets() {
        return CURRENT_OFFSETS;
    }

    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        LOGGER.debug("message=following partitions have been revoked from consumer: [{}]",
                partitions.stream().map(
                        topicPartition -> topicPartition.topic() + ":" + topicPartition.partition())
                        .collect(joining(",")));
        LOGGER.debug("message=Comitting offsets for partititions [{}]",
                CURRENT_OFFSETS.keySet().stream().map(
                        topicPartition -> topicPartition.topic() + ":" + topicPartition.partition())
                        .collect(joining(",")));
        consumer.commitSync(CURRENT_OFFSETS);
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        LOGGER.debug("message=following partitions have been assigned to consumer: [{}]",
                partitions.stream().map(
                        topicPartition -> topicPartition.topic() + ":" + topicPartition.partition())
                        .collect(joining(",")));
    }

}
_

RebalanceListener内に_consumerId -- TopicPartition_の同時マップを作成し、すべてのメッセージを処理する前に、現在のコンシューマーがまだレコードに関連付けられているかどうかを確認できると思います(各ConsumerRecordtopicおよびpartitionフィールドがあります)。そうでない場合-サイクルを中断し、次のpoll()を作成します。

複数のKafkaConsumerスレッドが回転していても、ワーカーアプリが単一のインスタンスで実行されている場合、これは実行可能なソリューションになります。ただし、スケールアップすると、静的マップにオフセットとコンシューマートピックパーティションマッピングを格納できなくなります。それは、ある種の集中型ストレージ、データベース、またはたとえばRedisである必要があります。

ただし、アイテムを処理する前に、現在のコンシューマスレッドでレコードを合法的に処理できるかどうかを確認する必要があります。スケーリングされたワーカーアプリの場合、外部ストレージへのネットワーク呼び出しになり、処理が遅くなるため、kafkaを使用する目的が無効になります。実行することを選択する場合があります。オフセットは、単一のアイテムが処理された後にコミットします。

10
Ihor M.

OnPartitionsRevoked()を実装する必要があります

https://kafka.Apache.org/0110/javadoc/org/Apache/kafka/clients/consumer/ConsumerRebalanceListener.html#onPartitionsRevoked(Java.util.Collection)

OnPartitionsAssignedを呼び出すプロセスの前に、すべてのコンシューマプロセスがonPartitionsRevokedを呼び出すことが保証されています。したがって、オフセットまたはその他の状態がonPartitionsRevoked呼び出しで保存される場合、そのパーティションを引き継ぐプロセスが状態をロードするためにonPartitionsAssignedコールバックを呼び出すまでに保存されることが保証されます。

1
Hans Jespersen

ConsumerRebalanceListenerのjavadocによると

このコールバックは、パーティションの割り当てが変更されるたびに、poll(long)呼び出しの一部としてユーザースレッドでのみ実行されます。

したがって、poll()によって返されたメッセージの最後のバッチを処理している最中にパーティションの再割り当てが発生することを心配する必要はありません。これらのメッセージをすべて処理し、poll()を再度呼び出すまで、この問題は発生しません。

Javadocも言います:

OnPartitionsAssignedを呼び出すプロセスの前に、すべてのコンシューマプロセスがonPartitionsRevokedを呼び出すことが保証されています。したがって、オフセットまたはその他の状態がonPartitionsRevoked呼び出しで保存される場合、そのパーティションを引き継ぐプロセスが状態をロードするためにonPartitionsAssignedコールバックを呼び出すまでに保存されることが保証されます。

0
Patrick T