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. org.springframework.util.Base64Utils线程安全问题

    Spring提供的org.springframework.util.Base64Utils类,先会检测JDK里是否自带java.util.Base64,如果不带,则使用的是apache提供的org.a ...

  2. centos 配置sentry+钉钉+邮件通知

    1.sentry官方推荐docker方式安装.使用docker-compose,最好是centos7 2.卸载旧版本 yum remove docker docker-common docker-se ...

  3. Javase之集合泛型

    集合泛型知识 泛型 是一种把类型明确工作推迟到创建对象或者调用方法的时候才明确的特殊类型. 也称参数化类型,把类型当成参数传递. 在jdk1.5中出现.一般来说经常在集合中使用. 格式 <数据类 ...

  4. react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面

    一.前言 9月,又到开学的季节.为每个一直默默努力的自己点赞!最近都沉浸在react native原生app开发中,之前也有使用vue/react/angular等技术开发过聊天室项目,另外还使用RN ...

  5. 做一个vue轮播图组件

    根据huangyi老师的慕课网vue项目跟着做的,下面大概记录了下思路 1.轮播图的图 先不做轮播图逻辑部分,先把数据导进来,看看什么效果.在recommend组件新建一个recommends的数组, ...

  6. arcgis api for javascript 学习(四) 地图的基本操作

    1.文章讲解的为地图的平移.放大.缩小.前视图.后视图以及全景视图的基本功能操作 2.主要用到的是arcgis api for javascript中Navigation的用法,代码如下: <! ...

  7. QT QNetworkAccessManager 如何支持RESTFul的HTTP Patch方法

    HTTP Patch方法是除了post,get,put,delete之外的一个新方式, 网上查不到的,也算是独家吧: 主要用下面这个方法: QNetworkReply *sendCustomReque ...

  8. [转]Eclipse插件开发之基础篇(2) 第一个Eclipse插件

    原文地址:http://www.cnblogs.com/liuzhuo/archive/2010/08/15/eclipse_plugin_1_1_1.html 在Eclipse中使用PDE(Plug ...

  9. python中动态创建类

    class Foo(Bar): pass Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧 ...

  10. AtCoder - 2286 (数论——唯一分解定理)

    题意 求n!的因子数%1e9+7. 思路 由唯一分解定理,一个数可以拆成素数幂之积,即2^a * 3^b *……,n!=2*3*……*n,所以计算每个素因子在这些数中出现的总次数(直接对2~n素因子分 ...