1、单独KafkaConsumer实例and多worker线程。
将获取的消息和消息的处理解耦,将消息的处理放入单独的工作者线程中,即工作线程中,同时维护一个或者若各干consumer实例执行消息获取任务。
本例使用全局的KafkaConsumer实例执行消息获取,然后把获取到的消息集合交给线程池中的worker线程执行工作,之后worker线程完成处理后上报位移状态,由全局consumer提交位移。

 package com.bie.kafka.kafkaWorker;

 import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.WakeupException; /**
*
* @Description TODO
* @author biehl
* @Date 2019年6月1日 下午3:28:53
*
* @param <K>
* @param <V>
*
* 1、consumer多线程管理类,用于创建线程池以及为每个线程分配消息集合。 另外consumer位移提交也在该类中完成。
*
*/
public class ConsumerThreadHandler<K, V> { // KafkaConsumer实例
private final KafkaConsumer<K, V> consumer;
// ExecutorService实例
private ExecutorService executors;
// 位移信息offsets
private final Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>(); /**
*
* @param brokerList
* kafka列表
* @param groupId
* 消费组groupId
* @param topic
* 主题topic
*/
public ConsumerThreadHandler(String brokerList, String groupId, String topic) {
Properties props = new Properties();
// broker列表
props.put("bootstrap.servers", brokerList);
// 消费者组编号Id
props.put("group.id", groupId);
// 非自动提交位移信息
props.put("enable.auto.commit", "false");
// 从最早的位移处开始消费消息
props.put("auto.offset.reset", "earliest");
// key反序列化
props.put("key.deserializer", "org.apache.kafka.common.serialization.ByteArrayDeserializer");
// value反序列化
props.put("value.deserializer", "org.apache.kafka.common.serialization.ByteArrayDeserializer");
// 将配置信息装配到消费者实例里面
consumer = new KafkaConsumer<>(props);
// 消费者订阅消息,并实现重平衡rebalance
// rebalance监听器,创建一个匿名内部类。使用rebalance监听器前提是使用消费者组(consumer group)。
// 监听器最常见用法就是手动提交位移到第三方存储以及在rebalance前后执行一些必要的审计操作。
consumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener() { /**
* 在coordinator开启新一轮rebalance前onPartitionsRevoked方法会被调用。
*/
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// 提交位移
consumer.commitSync(offsets);
} /**
* rebalance完成后会调用onPartitionsAssigned方法。
*/
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// 清除位移信息
offsets.clear();
}
});
} /**
* 消费主方法
*
* @param threadNumber
* 线程池中的线程数
*/
public void consume(int threadNumber) {
executors = new ThreadPoolExecutor(
threadNumber,
threadNumber,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(),
new ThreadPoolExecutor.CallerRunsPolicy());
try {
// 消费者一直处于等待状态,等待消息消费
while (true) {
// 从主题中获取消息
ConsumerRecords<K, V> records = consumer.poll(Duration.ofSeconds(1000L));
// 如果获取到的消息不为空
if (!records.isEmpty()) {
// 将消息信息、位移信息封装到ConsumerWorker中进行提交
executors.submit(new ConsumerWorker<>(records, offsets));
}
// 调用提交位移信息、尽量降低synchronized块对offsets锁定的时间
this.commitOffsets();
}
} catch (WakeupException e) {
// 此处忽略此异常的处理.WakeupException异常是从poll方法中抛出来的异常
//如果不忽略异常信息,此处会打印错误哦,亲
//e.printStackTrace();
} finally {
// 调用提交位移信息、尽量降低synchronized块对offsets锁定的时间
this.commitOffsets();
// 关闭consumer
consumer.close();
}
} /**
* 尽量降低synchronized块对offsets锁定的时间
*/
private void commitOffsets() {
// 尽量降低synchronized块对offsets锁定的时间
Map<TopicPartition, OffsetAndMetadata> unmodfiedMap;
// 保证线程安全、同步锁,锁住offsets
synchronized (offsets) {
// 判断如果offsets位移信息为空,直接返回,节省同步锁对offsets的锁定的时间
if (offsets.isEmpty()) {
return;
}
// 如果offsets位移信息不为空,将位移信息offsets放到集合中,方便同步
unmodfiedMap = Collections.unmodifiableMap(new HashMap<>(offsets));
// 清除位移信息offsets
offsets.clear();
}
// 将封装好的位移信息unmodfiedMap集合进行同步提交
// 手动提交位移信息
consumer.commitSync(unmodfiedMap);
} /**
* 关闭消费者
*/
public void close() {
// 在另一个线程中调用consumer.wakeup();方法来触发consume的关闭。
// KafkaConsumer不是线程安全的,但是另外一个例外,用户可以安全的在另一个线程中调用consume.wakeup()。
// wakeup()方法是特例,其他KafkaConsumer方法都不能同时在多线程中使用
consumer.wakeup();
// 关闭ExecutorService实例
executors.shutdown();
} }
 package com.bie.kafka.kafkaWorker;

 import java.util.List;
import java.util.Map; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; /**
*
* @Description TODO
* @author biehl
* @Date 2019年6月1日 下午3:45:38
*
* @param <K>
* @param <V>
*
* 1、本质上是一个Runnable,执行真正的消费逻辑并且上报位移信息给ConsumerThreadHandler。
*
*/
public class ConsumerWorker<K, V> implements Runnable { // 获取到的消息
private final ConsumerRecords<K, V> records;
// 位移信息
private final Map<TopicPartition, OffsetAndMetadata> offsets; /**
* ConsumerWorker有参构造方法
*
* @param records
* 获取到的消息
* @param offsets
* 位移信息
*/
public ConsumerWorker(ConsumerRecords<K, V> records, Map<TopicPartition, OffsetAndMetadata> offsets) {
this.records = records;
this.offsets = offsets;
} /**
*
*/
@Override
public void run() {
// 获取到分区的信息
for (TopicPartition partition : records.partitions()) {
// 获取到分区的消息记录
List<ConsumerRecord<K, V>> partConsumerRecords = records.records(partition);
// 遍历获取到的消息记录
for (ConsumerRecord<K, V> record : partConsumerRecords) {
// 打印消息
System.out.println("topic: " + record.topic() + ",partition: " + record.partition() + ",offset: "
+ record.offset()
+ ",消息记录: " + record.value());
}
// 上报位移信息。获取到最后的位移消息,由于位移消息从0开始,所以最后位移减一获取到位移位置
long lastOffset = partConsumerRecords.get(partConsumerRecords.size() - ).offset();
// 同步锁,锁住offsets位移
synchronized (offsets) {
// 如果offsets位移不包含partition这个key信息
if (!offsets.containsKey(partition)) {
// 就将位移信息设置到map集合里面
offsets.put(partition, new OffsetAndMetadata(lastOffset + ));
} else {
// 否则,offsets位移包含partition这个key信息
// 获取到offsets的位置信息
long curr = offsets.get(partition).offset();
// 如果获取到的位置信息小于等于上一次位移信息大小
if (curr <= lastOffset + ) {
// 将这个partition的位置信息设置到map集合中。并保存到broker中。
offsets.put(partition, new OffsetAndMetadata(lastOffset + ));
}
}
}
}
} }
 package com.bie.kafka.kafkaWorker;

 /**
*
* @Description TODO
* @author biehl
* @Date 2019年6月1日 下午4:13:25
*
* 1、单独KafkaConsumer实例和多worker线程。
* 2、将获取的消息和消息的处理解耦,将消息的处理放入单独的工作者线程中,即工作线程中,
* 同时维护一个或者若各干consumer实例执行消息获取任务。
* 3、本例使用全局的KafkaConsumer实例执行消息获取,然后把获取到的消息集合交给线程池中的worker线程执行工作,
* 之后worker线程完成处理后上报位移状态,由全局consumer提交位移。
*
*
*/ public class ConsumerMain { public static void main(String[] args) {
// broker列表
String brokerList = "slaver1:9092,slaver2:9092,slaver3:9092";
// 主题信息topic
String topic = "topic1";
// 消费者组信息group
String groupId = "group2";
// 根据ConsumerThreadHandler构造方法构造出消费者
final ConsumerThreadHandler<byte[], byte[]> handler = new ConsumerThreadHandler<>(brokerList, groupId, topic);
final int cpuCount = Runtime.getRuntime().availableProcessors();
System.out.println("cpuCount : " + cpuCount);
// 创建线程的匿名内部类
Runnable runnable = new Runnable() { @Override
public void run() {
// 执行consume,在此线程中执行消费者消费消息。
handler.consume(cpuCount);
}
};
// 直接调用runnable此线程,并运行
new Thread(runnable).start(); try {
// 此线程休眠20000
Thread.sleep(20000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Starting to close the consumer...");
// 关闭消费者
handler.close();
} }

待续......

单独KafkaConsumer实例and多worker线程。的更多相关文章

  1. Netty服务端接收的新连接是如何绑定到worker线程池的?

    更多技术分享可关注我 前言 原文:Netty服务端接收的新连接是如何绑定到worker线程池的? 前面分析Netty服务端检测新连接的过程提到了NioServerSocketChannel读完新连接后 ...

  2. HTML5_06之拖放API、Worker线程、Storage存储

    1.拖放API中源对象与目标对象事件间的数据传递: ①创建全局变量--污染全局对象:  var 全局变量=null;  src.ondragstart=function(){   全局变量=数据值;  ...

  3. 模板应用--UI线程与worker线程同步 模仿c# invoke

    由之前的一篇博文 <UI线程与worker线程><UI线程与worker线程>引出,UI线程与worker线程“串行化”在win32上实现是多么没有节操的事情,代码编写麻烦不说 ...

  4. UI线程与worker线程

    也谈谈我对UI线程和worker线程的理解 UI线程又叫界面线程,能够响应操作系统的特定消息,包括界面消息.鼠标键盘消息.自定义消息等,是在普通的worker线程基础上加上消息循环来实现的,在这个消息 ...

  5. 每一个Servlet只有一个实例,多个线程

    每一个Servlet只有一个实例,多个线程: Servlet: package com.stono.servlet.synchronize; import javax.servlet.http.Htt ...

  6. 在netty3.x中存在两种线程:boss线程和worker线程。

    在netty 3.x 中存在两种线程:boss线程和worker线程.

  7. js Worker 线程

    在平时的运行的javascript脚本都在主线程中执行,如果当前脚本包含复杂的.耗时的代码.那么JavaScript脚本的执行将会被阻塞,甚至整个刘看齐都是提示失去响应. 例子: 假设程序需要计算.收 ...

  8. 【转】 Pro Android学习笔记(九十):了解Handler(4):Worker线程

    目录(?)[-] worker线程小例子 小例子代码worker线程通过handler实现与主线程的通信 小例子代码继承Handler代码 小例子代码子线程的Runnable 文章转载只能用于非商业性 ...

  9. 前端框架小实验-在umi框架中以worker线程方式使用SQL.js的wasm

    总述:在Win7环境下配置umijs框架,在框架中用worker线程方式使用SQL.js的wasm,在浏览器端实现数据的增删改查以及数据库导出导入. 一.安装node.js 1.Win7系统只支持no ...

随机推荐

  1. list方法补充

    在上一个随便我们写了list 常用的方法,该随便为一些需要补充的内容 注:本次例子为: student = ["张天赐","小明","小红" ...

  2. Flask 教程 第一章:Hello, World!

    本文翻译自The Flask Mega-Tutorial Part I: Hello, World! 一趟愉快的学习之旅即将开始,跟随它你将学会用Python和Flask来创建Web应用.上面的视频包 ...

  3. MySQL学习——操作存储过程

    MySQL学习——操作存储过程 摘要:本文主要学习了使用DDL语句操作存储过程的方法. 了解存储过程 是什么 存储过程是一组为了完成特定功能的SQL语句集合. 使用存储过程的目的是将常用或复杂的工作预 ...

  4. DevExpress的下拉框控件ComboxBoxEdit怎样绑定键值对选项

    场景 DevExpress的下拉框控件ComboBoxEdit控件的使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1028 ...

  5. 升鲜宝V2.0_生鲜配送行业,对生鲜配送行业的思考及对系统流程开发的反思_升鲜宝生鲜配送系统_15382353715_余东升

    升鲜宝V2.0_生鲜配送行业,对生鲜配送行业的思考及对系统流程开发的反思_升鲜宝生鲜配送系统_15382353715_余东升 -----生鲜配送行业现状及存在问题----- 1.  从业者整体素质偏低 ...

  6. android ANR 分析定位问题

    ANR ? android 规定,Activity如果5秒钟之内无法响应屏幕触摸事件或者键盘输入事件,BroadcastReceiver 如果10s中之内还未执行完操作就会出现ANR 定位ANR问题 ...

  7. mysql操作篇续

    # ### part1. 数据类型 - 时间date YYYY-MM-DD 年月日 (纪念日)time HH:MM:SS 时分秒 (体育竞赛)year YYYY 年份值 (酒的年份,82年拉菲)dat ...

  8. JDBC API浅析

    使用java开发数据库应用程序一般都需要用到四个接口:Driver.Connection.Statement.ResultSet 1.Driver接口用于加载驱动程序 2.Connection接口用于 ...

  9. Python xlwt模块写Excel问题集合

    1.数字转换成汉字 数据库查询返回结果为多元组,在写入Excel需要判断,数据库查询结果是否为数字,为数字的话需要将其转换成对应的汉字,此时元组不可修改,所以需要将返回结果修改成列表.实现可以在数据库 ...

  10. vue操作select获取option值

    如何实时的获取你选中的值 只用@change件事 @change="changeProduct($event)" 动态传递参数 vue操作select获取option的ID值 如果 ...