Java ApacheKafka-主题/分区上的KafkaStream

Java ApacheKafka-主题/分区上的KafkaStream,java,multithreading,concurrency,apache-kafka,Java,Multithreading,Concurrency,Apache Kafka,我正在为大容量高速分布式应用程序编写Kafka Consumer。我只有一个主题,但传入消息的比率非常高。对于这个用例,使用多个分区来服务更多的使用者是合适的。最好的消费方式是使用多个流读取器。根据文档或可用示例,ConsumerConnector发出的KafkaStreams的数量取决于主题的数量。想知道如何[基于分区]获得多个KafkaStream读取器,以便我可以在每个流中跨一个线程,或者在多个线程中读取同一KafkaStream,这将从多个分区进行并发读取 非常感谢您的任何见解。希望分享

我正在为大容量高速分布式应用程序编写Kafka Consumer。我只有一个主题,但传入消息的比率非常高。对于这个用例,使用多个分区来服务更多的使用者是合适的。最好的消费方式是使用多个流读取器。根据文档或可用示例,ConsumerConnector发出的KafkaStreams的数量取决于主题的数量。想知道如何[基于分区]获得多个KafkaStream读取器,以便我可以在每个流中跨一个线程,或者在多个线程中读取同一KafkaStream,这将从多个分区进行并发读取


非常感谢您的任何见解。

希望分享我从邮件列表中发现的内容:

您在主题映射中传递的数字控制一个主题划分为多少个流。在您的例子中,如果传入1,那么所有10个分区的数据都将馈送到一个流中。如果传入2,则2个流中的每个流都将从5个分区获取数据。如果您传入11,其中10个将分别从1个分区获取数据,而1个流将一无所获

通常,您需要在其自己的线程中迭代每个流。这是因为如果没有新事件,每个流都可能永远阻塞

示例代码段:

topicCount.put(msgTopic, new Integer(partitionCount));
Map<String, List<KafkaStream<byte[], byte[]>>> consumerStreams = connector.createMessageStreams(topicCount);
List<KafkaStream<byte[], byte[]>> streams = consumerStreams.get(msgTopic);

for (final KafkaStream stream : streams) {
    ReadTask task = new ReadTask(stream, msgTopic);
    task.addObserver(this.msgObserver);
    tasks.add(task); executor.submit(task);
}
topicCount.put(msgTopic,新整数(partitionCount));
Map consumerStreams=connector.createMessageStreams(topicCount);
List streams=consumerStreams.get(msgTopic);
对于(最终卡夫卡斯特雷姆流:流){
ReadTask任务=新的ReadTask(流,msgTopic);
task.addObserver(this.msgObserver);
tasks.add(任务);executor.submit(任务);
}

参考资料:

推荐的方法是使用线程池,这样Java就可以为您和createMessageStreamsByFilter方法提供的每个流处理组织,让您以可运行的方式使用它。例如:

int NUMBER_OF_PARTITIONS = 6;
Properties consumerConfig = new Properties();
consumerConfig.put("zk.connect", "zookeeper.mydomain.com:2181" );
consumerConfig.put("backoff.increment.ms", "100");
consumerConfig.put("autooffset.reset", "largest");
consumerConfig.put("groupid", "java-consumer-example");
consumer = Consumer.createJavaConsumerConnector(new ConsumerConfig(consumerConfig));

TopicFilter sourceTopicFilter = new Whitelist("mytopic|myothertopic");
List<KafkaStream<Message>> streams = consumer.createMessageStreamsByFilter(sourceTopicFilter, NUMBER_OF_PARTITIONS);

ExecutorService executor = Executors.newFixedThreadPool(streams.size());
for(final KafkaStream<Message> stream: streams){
    executor.submit(new Runnable() {
        public void run() {
            for (MessageAndMetadata<Message> msgAndMetadata: stream) {
                ByteBuffer buffer = msgAndMetadata.message().payload();
                byte [] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                //Do something with the bytes you just got off Kafka.
            }
        }
    });
}
int-NUMBER\u分区的\u=6;
Properties consumerConfig=新属性();
consumerConfig.put(“zk.connect”、“zookeeper.mydomain.com:2181”);
消费者配置延迟(“回退增量毫秒”、“100”);
consumerConfig.put(“自动偏移量重置”、“最大”);
put(“groupid”,“java消费者示例”);
consumer=consumer.createJavaConsumerConnector(新的ConsumerConfig(ConsumerConfig));
TopicFilter sourceTopicFilter=新的白名单(“mytopic | myothertopic”);
List streams=consumer.createMessageStreamsByFilter(sourceTopicFilter,分区数);
ExecutorService executor=Executors.newFixedThreadPool(streams.size());
对于(最终卡夫卡斯特雷姆流:流){
执行者提交(新的可运行(){
公开募捐{
for(MessageAndMetadata-msgandmatadata:stream){
ByteBuffer buffer=msgAndMetadata.message().payload();
byte[]bytes=新字节[buffer.remaining()];
buffer.get(字节);
//用你刚从卡夫卡出来的字节做点什么。
}
}
});
}
在这个例子中,我要求6个线程,因为我知道每个主题有3个分区,我在白名单中列出了两个主题。一旦我们有了传入流的句柄,我们就可以迭代它们的内容,即MessageAndMetadata对象。元数据实际上只是主题名和偏移量。正如您所发现的,如果您请求1个流而不是我的示例6中的流,那么您可以在单个线程中执行,但是如果您需要并行处理,最好的方法是为每个返回的流启动一个执行器,其中一个线程。

/**
/**
 * @param source : source kStream to sink output-topic
 */
private static void pipe(KStream<String, String> source) {
    source.to(Serdes.String(), Serdes.String(), new StreamPartitioner<String, String>() {

        @Override
        public Integer partition(String arg0, String arg1, int arg2) {
            return 0;
        }
    }, "output-topic");
}
*@param source:source kStream到sink输出主题 */ 专用静态空隙管道(KStream源){ to(Serdes.String(),Serdes.String(),new StreamPartitioner()){ @凌驾 公共整数分区(字符串arg0、字符串arg1、整数arg2){ 返回0; } }“输出主题”); }

上面的代码将在主题名为“output topic”的分区1写入记录。

使用SimpleConsumer不是一个选项?示例代码片段topicCount.put(msgTopic,new Integer(partitionCount));Map consumerStreams=connector.createMessageStreams(topicCount);List streams=consumerStreams.get(msgTopic);对于(最终KafkaStream stream:streams){ReadTask task=newreadtask(stream,msgTopic);task.addObserver(this.msgObserver);tasks.add(task);executor.submit(task);}如果这样做会发生什么?Kafka消费者配置=新消费者配置(…);consumerConnector=Consumer.createJavaConsumerConnector(kafkaConsumerConfig);topicCountMap.put(“mytopic”,1);consumerMap.get(“mytopic”).get(0);检查卡夫卡流列表中是否有get(0),这样我只得到1个流。如果我调用Consumer.createJavaConsumerConnector 10次会发生什么?它们都共享相同的配置,每个都将读取所有分区,因此我猜您将得到10个消费者,所有这些消费者都将试图将其状态保存在同一ZK节点中,这样您将得到消费者1,例如,读取前1K条消息,然后,使用者2读取这些相同的1K消息,但潜在的使用者1将完成其批更新ZK的读取,读取第二个,然后将其位置写入ZK,然后由于某种原因,第二个较慢的线程进入并将其位置写入ZK,导致第一个使用者重新处理第二批。基本上冲突很多。