一、订阅Topic
### --- 订阅Topic
~~~ 下面我们先来看一下subscribe方法都有哪些逻辑
public void subscribe(Collection<String> topics, ConsumerRebalanceListenerlistener) {
// 轻量级锁
acquireAndEnsureOpen();
try {
if (topics == null) {
throw new IllegalArgumentException("Topic collection to subscribe to cannot be null");
} else if (topics.isEmpty()) {
// topics为空,则开始取消订阅的逻辑
this.unsubscribe();
} else {
// topic合法性判断,包含null或者空字符串直接抛异常
for (String topic : topics) {
if (topic == null || topic.trim().isEmpty())
throw new IllegalArgumentException("Topic collection to subscribe to cannot contain null or empty topic");
}
// 如果没有消费协调者直接抛异常
throwIfNoAssignorsConfigured();
log.debug("Subscribed to topic(s): {}", Utils.join(topics, ", "));
// 开始订阅
this.subscriptions.subscribe(new HashSet<>(topics), listener);
// 更新元数据,如果metadata当前不包括所有的topics则标记强制更新
metadata.setTopics(subscriptions.groupSubscription());
}
} finally {
release();
}
}
public void subscribe(Set<String> topics, ConsumerRebalanceListenerlistener) {
if (listener == null)
throw new IllegalArgumentException("RebalanceListener cannot be null");
// 按照指定的Topic名字进行订阅,自动分配分区
setSubscriptionType(SubscriptionType.AUTO_TOPICS);
// 监听
this.listener = listener;
// 修改订阅信息
changeSubscription(topics);
}
private void changeSubscription(Set<String> topicsToSubscribe) {
if (!this.subscription.equals(topicsToSubscribe)) {
// 如果使用AUTO_TOPICS或AUTO_PARTITION模式,则使用此集合记录所有订阅的Topic
this.subscription = topicsToSubscribe;
// Consumer Group中会选一个Leader,Leader会使用这个集合记录Consumer Group中所有消费者订阅的Topic,而其他的Follower的这个集合只会保存自身订阅的Topic
this.groupSubscription.addAll(topicsToSubscribe);
}
}
~~~ KafkaConsumer不是线程安全类,开启轻量级锁,topics为空抛异常,topics是空集合开始取消订阅,
~~~ 再次判断topics集合中是否有非法数据,判断消费者协调者是否为空。开始订阅对应topic。
~~~ listener默认为NoOpConsumerRebalanceListener ,一个空操作
~~~ # 轻量级锁:
~~~ 分别记录了当前使用KafkaConsumer的线程id和重入次数,
~~~ KafkaConsumer的acquire()和release()方法实现了一个”轻量级锁“,它并非真正的锁,
~~~ 仅是检测是否有多线程并发操作KafkaConsumer而已
~~~ 每一个KafkaConsumer实例内部都拥有一个SubscriptionState对象,
~~~ subscribe内部调用了subscribe方法,subscribe方法订阅信息记录到SubscriptionState ,
~~~ 多次订阅会覆盖旧数据。
~~~ 更新metadata,判断如果metadata中不包含当前groupSubscription,
~~~ 开始标记更新(后面会有更新的逻辑),并且消费者侧的topic不会过期
二、消息消费过程
### --- 消息消费过程
~~~ 下面KafkaConsumer的核心方法poll是如何拉取消息的,先来看一下下面的代码:
### --- poll
public ConsumerRecords<K, V> poll(long timeout) {
// 使用轻量级锁检测kafkaConsumer是否被其他线程使用
acquireAndEnsureOpen();
try {
// 超时间小于0抛异常
if (timeout < 0)
throw new IllegalArgumentException("Timeout must not be negative");
// 订阅类型为NONE抛异常,表示当前消费者没有订阅任何topic或者没有分配分区
if (this.subscriptions.hasNoSubscriptionOrUserAssignment())
throw new IllegalStateException("Consumer is not subscribed to any topics or assigned any partitions");
// poll for new data until the timeout expires
long start = time.milliseconds();
long remaining = timeout;
do {
// 核心方法,拉取消息
Map<Topic