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. \" 转义字符, \a系统警报,逐字字符串(verbatim string)

    string str="The key factors are \"focus\" and \"perseverance\""; strin ...

  2. Python 最强 IDE 详细使用指南!-PyCharm

    PyCharm 是一种 Python IDE,可以帮助程序员节约时间,提高生产效率.那么具体如何使用呢?本文从 PyCharm 安装到插件.外部工具.专业版功能等进行了一一介绍,希望能够帮助到大家. ...

  3. Spring MVC的常用注解(一)

    概述 Spring从2.5版本开始引入注解,虽然版本不断变化,但是注解的特性一直被延续下来并不断进行扩展,这里就来记录一下Spring MVC中常用的注解,本文记录@Controller.@Reque ...

  4. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  5. Meterpreter初探

    Meterpreter Meterpreter号称"黑客瑞士军刀",Meterpreter是Metasploit框架中的一个杀手锏,通常作为漏洞溢出后的攻击载荷使用,攻击载荷在触发 ...

  6. iOS UItableview 镶嵌 collectionView ,cell 自适应高度动态布局

    最近在写这个功能,之前看到很多,可是需求一直没有涉及到,大致思路是有的,发现,网上的大部分都有缺陷和bug,我也是好无语啦啦啦,也不晓得是不是升级 了xcode,一样的代码,允许的效果都不一样,,,苦 ...

  7. Android 布局测试

    wrap_content <Button android:id="@+id/button1" android:layout_width="wrap_content& ...

  8. ARP攻击 winpcap

    ARP攻击就是通过伪造IP地址和MAC地址实现ARP欺骗.解决办法详见百科 #define ETHER_ADDR_LEN 6 typedef struct { u_char DestMAC[ETHER ...

  9. LeetCode——Delete Duplicate Emails(巧用mysql临时表)

    Write a SQL query to delete all duplicate email entries in a table named Person, keeping only unique ...

  10. python 逐行读取txt文件

    逐行读取txt文件 path = r'D:\123456\1.txt'with open(path, 'r', encoding='utf-8') as f:    for line in f:   ...