Java ApacheKafka-主题/分区上的KafkaStream
我正在为大容量高速分布式应用程序编写Kafka Consumer。我只有一个主题,但传入消息的比率非常高。对于这个用例,使用多个分区来服务更多的使用者是合适的。最好的消费方式是使用多个流读取器。根据文档或可用示例,ConsumerConnector发出的KafkaStreams的数量取决于主题的数量。想知道如何[基于分区]获得多个KafkaStream读取器,以便我可以在每个流中跨一个线程,或者在多个线程中读取同一KafkaStream,这将从多个分区进行并发读取Java ApacheKafka-主题/分区上的KafkaStream,java,multithreading,concurrency,apache-kafka,Java,Multithreading,Concurrency,Apache Kafka,我正在为大容量高速分布式应用程序编写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,导致第一个使用者重新处理第二批。基本上冲突很多。