Kafka 0.9版本开始推出了Java版本的consumer,优化了coordinator的设计以及摆脱了对zookeeper的依赖。社区最近也在探讨正式用这套consumer API替换Scala版本的consumer的计划。鉴于目前这方面的资料并不是很多,本文将尝试给出一个利用KafkaConsumer编写的多线程消费者实例,希望对大家有所帮助。
    这套API最重要的入口就是KafkaConsumer(o.a.k.clients.consumer.KafkaConsumer),普通的单线程使用方法官网API已有介绍,这里不再赘述了。因此,我们直奔主题——讨论一下如何创建多线程的方式来使用KafkaConsumer。KafkaConsumer和KafkaProducer不同,后者是线程安全的,因此我们鼓励用户在多个线程中共享一个KafkaProducer实例,这样通常都要比每个线程维护一个KafkaProducer实例效率要高。但对于KafkaConsumer而言,它不是线程安全的,所以实现多线程时通常由两种实现方法:
1 每个线程维护一个KafkaConsumer
维护一个或多个KafkaConsumer,同时维护多个事件处理线程(worker thread)

当然,这种方法还可以有多个变种:比如每个worker线程有自己的处理队列。consumer根据某种规则或逻辑将消息放入不同的队列。不过总体思想还是相同的,故这里不做过多展开讨论了。
  下表总结了两种方法的优缺点: 

  优点 缺点
方法1(每个线程维护一个KafkaConsumer) 方便实现
速度较快,因为不需要任何线程间交互
易于维护分区内的消息顺序
更多的TCP连接开销(每个线程都要维护若干个TCP连接)
consumer数受限于topic分区数,扩展性差
频繁请求导致吞吐量下降
线程自己处理消费到的消息可能会导致超时,从而造成rebalance
方法2 (单个(或多个)consumer,多个worker线程) 可独立扩展consumer数和worker数,伸缩性好
实现麻烦
通常难于维护分区内的消息顺序
处理链路变长,导致难以保证提交位移的语义正确性 
 
下面我们分别实现这两种方法。需要指出的是,下面的代码都是最基本的实现,并没有考虑很多编程细节,比如如何处理错误等。
方法1
ConsumerRunnable类
 import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays;
import java.util.Properties; public class ConsumerRunnable implements Runnable { // 每个线程维护私有的KafkaConsumer实例
private final KafkaConsumer<String, String> consumer; public ConsumerRunnable(String brokerList, String groupId, String topic) {
Properties props = new Properties();
props.put("bootstrap.servers", brokerList);
props.put("group.id", groupId);
props.put("enable.auto.commit", "true"); //本例使用自动提交位移
props.put("auto.commit.interval.ms", "1000");
props.put("session.timeout.ms", "30000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
this.consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList(topic)); // 本例使用分区副本自动分配策略
} @Override
public void run() {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(200); // 本例使用200ms作为获取超时时间
for (ConsumerRecord<String, String> record : records) {
// 这里面写处理消息的逻辑,本例中只是简单地打印消息
System.out.println(Thread.currentThread().getName() + " consumed " + record.partition() +
"th message with offset: " + record.offset());
}
}
}
}

ConsumerGroup类

 package com.my.kafka.test;

 import java.util.ArrayList;
import java.util.List; public class ConsumerGroup { private List<ConsumerRunnable> consumers; public ConsumerGroup(int consumerNum, String groupId, String topic, String brokerList) {
consumers = new ArrayList<>(consumerNum);
for (int i = 0; i < consumerNum; ++i) {
ConsumerRunnable consumerThread = new ConsumerRunnable(brokerList, groupId, topic);
consumers.add(consumerThread);
}
} public void execute() {
for (ConsumerRunnable task : consumers) {
new Thread(task).start();
}
}
}

ConsumerMain类

 public class ConsumerMain {

     public static void main(String[] args) {
String brokerList = "localhost:9092";
String groupId = "testGroup1";
String topic = "test-topic";
int consumerNum = 3; ConsumerGroup consumerGroup = new ConsumerGroup(consumerNum, groupId, topic, brokerList);
consumerGroup.execute();
}
}

方法2

Worker类
 import org.apache.kafka.clients.consumer.ConsumerRecord;

 public class Worker implements Runnable {

     private ConsumerRecord<String, String> consumerRecord;

     public Worker(ConsumerRecord record) {
this.consumerRecord = record;
} @Override
public void run() {
// 这里写你的消息处理逻辑,本例中只是简单地打印消息
System.out.println(Thread.currentThread().getName() + " consumed " + consumerRecord.partition()
+ "th message with offset: " + consumerRecord.offset());
}
}
ConsumerHandler类
 import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; public class ConsumerHandler { // 本例中使用一个consumer将消息放入后端队列,你当然可以使用前一种方法中的多实例按照某张规则同时把消息放入后端队列
private final KafkaConsumer<String, String> consumer;
private ExecutorService executors; public ConsumerHandler(String brokerList, String groupId, String topic) {
Properties props = new Properties();
props.put("bootstrap.servers", brokerList);
props.put("group.id", groupId);
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("session.timeout.ms", "30000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList(topic));
} public void execute(int workerNum) {
executors = new ThreadPoolExecutor(workerNum, workerNum, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy()); while (true) {
ConsumerRecords<String, String> records = consumer.poll(200);
for (final ConsumerRecord record : records) {
executors.submit(new Worker(record));
}
}
} public void shutdown() {
if (consumer != null) {
consumer.close();
}
if (executors != null) {
executors.shutdown();
}
try {
if (!executors.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Timeout.... Ignore for this case");
}
} catch (InterruptedException ignored) {
System.out.println("Other thread interrupted this shutdown, ignore for this case.");
Thread.currentThread().interrupt();
}
} }
Main类
 public class Main {

     public static void main(String[] args) {
String brokerList = "localhost:9092,localhost:9093,localhost:9094";
String groupId = "group2";
String topic = "test-topic";
int workerNum = 5; ConsumerHandler consumers = new ConsumerHandler(brokerList, groupId, topic);
consumers.execute(workerNum);
try {
Thread.sleep(1000000);
} catch (InterruptedException ignored) {}
consumers.shutdown();
}
}

  总结一下,这两种方法或是模型都有各自的优缺点,在具体使用时需要根据自己实际的业务特点来选取对应的方法。就我个人而言,我比较推崇第二种方法以及背后的思想,即不要将很重的处理逻辑放入消费者的代码中,很多Kafka consumer使用者碰到的各种rebalance超时、coordinator重新选举、心跳无法维持等问题都来源于此。

【原创】Kafka Consumer多线程实例的更多相关文章

  1. 【原创】Kafka Consumer多线程实例续篇

    在上一篇<Kafka Consumer多线程实例>中我们讨论了KafkaConsumer多线程的两种写法:多KafkaConsumer多线程以及单KafkaConsumer多线程.在第二种 ...

  2. kafka系列 -- 多线程消费者实现

    看了一下kafka,然后写了消费Kafka数据的代码.感觉自己功力还是不够. 不能随心所欲地操作数据,数据结构没学好,spark的RDD操作没学好. 不能很好地组织代码结构,设计模式没学好,面向对象思 ...

  3. 【原创】kafka consumer源代码分析

    顾名思义,就是kafka的consumer api包. 一.ConsumerConfig.scala Kafka consumer的配置类,除了一些默认值常量及验证参数的方法之外,就是consumer ...

  4. 【原创】美团二面:聊聊你对 Kafka Consumer 的架构设计

    在上一篇中我们详细聊了关于 Kafka Producer 内部的底层原理设计思想和细节, 本篇我们主要来聊聊 Kafka Consumer 即消费者的内部底层原理设计思想. 1.Consumer之总体 ...

  5. kafka consumer assign 和 subscribe模式差异分析

    转载请注明原创地址:http://www.cnblogs.com/dongxiao-yang/p/7200971.html 最近需要研究flink-connector-kafka的消费行为,发现fli ...

  6. Kafka设计解析(四)- Kafka Consumer设计解析

    本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/08/09/KafkaColumn4 摘要 本文主要介绍了Kafka High Level Con ...

  7. 读Kafka Consumer源码

    最近一直在关注阿里的一个开源项目:OpenMessaging OpenMessaging, which includes the establishment of industry guideline ...

  8. kafka consumer 配置详解

    1.Consumer Group 与 topic 订阅 每个Consumer 进程都会划归到一个逻辑的Consumer Group中,逻辑的订阅者是Consumer Group.所以一条message ...

  9. [Big Data - Kafka] Kafka设计解析(四):Kafka Consumer解析

    High Level Consumer 很多时候,客户程序只是希望从Kafka读取数据,不太关心消息offset的处理.同时也希望提供一些语义,例如同一条消息只被某一个Consumer消费(单播)或被 ...

随机推荐

  1. centos7+mono4+jexus5.6.2安装过程中的遇到的问题

    过程参考: http://www.linuxdot.net/ http://www.jexus.org/ http://www.mono-project.com/docs/getting-starte ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. 在Ubuntu 16.10 安装 git 并上传代码至 git.oschina.net

    1. 注册一个账号和创建项目 先在git.oschina.net上注册一个账号和新建一个project ,如project name 是"myTest". 2.安装git sudo ...

  4. 修改eclipse皮肤

    习惯了vim黑色背景的程序猿们想必用eclipse时会倍感的不适应吧,不过没关系,因为eclipse的皮肤是可以自己定制的! 下面是我电脑上的eclipse界面,看到这个是不是找回了vim的感觉呢? ...

  5. 免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)

    在生活中有一种东西几乎已经快要成为我们的另一个电子”身份证“,那就是二维码.无论是在软件开发的过程中,还是在普通用户的日常中,几乎都离不开二维码.二维码 (dimensional barcode) , ...

  6. 微信网页开发之获取用户unionID的两种方法--基于微信的多点登录用户识别

    假设网站A有以下功能需求:1,pc端微信扫码登录:2,微信浏览器中的静默登录功能需求,这两种需求就需要用到用户的unionID,这样才能在多个登录点(终端)识别用户.那么这两种需求下用户的unionI ...

  7. 易用BPM时代,企业如何轻松驾驭H3?

    众所周知,BPM作为企业发展的推动力,能敏捷高效的融合业务流程和信息资源.通过综合考虑流程的成本.效率.质量等方面因素,用IT系统将调整后的流程固化下来,从而降低企业管理成本,提高内部运营效率,提升企 ...

  8. iOS之开发中常用的颜色及其对应的RGB值

      R G B 值   R G B 值   R G B 值 黑色 0 0 0 #000000 黄色 255 255 0 #FFFF00 浅灰蓝色 176 224 230 #B0E0E6 象牙黑 41 ...

  9. 练习JavaScript判断上传文件后缀名

    <script type = text/javascript> function jiance(filename) { var pic = ["jpg","p ...

  10. 【腾讯Bugly干货分享】JSPatch 成长之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/579efa7083355a9a57a1ac5b Dev Club 是一个交流移动 ...