本文只跟踪消费者拉取消息的流程。对于 java 客户端, kafka 的生产者和消费者复用同一个网络 io 类 NetworkClient。

入口在 KafkaConsumer#pollOnce 中,抽出主要步骤:

// 构造 FetchRequest 请求,将请求对象放入 unsent 集合,等待发送
fetcher.sendFetches(); // 取出 unsent 中的请求,调用 NetworkClient#send,NetworkClinet#poll
client.poll(pollTimeout, nowMs, new PollCondition() {
@Override
public boolean shouldBlock() {
// since a fetch might be completed by the background thread, we need this poll condition
// to ensure that we do not block unnecessarily in poll()
return !fetcher.hasCompletedFetches();
}
}); // 返回数据给用户
return fetcher.fetchedRecords();

Fetcher#sendFetches

public synchronized int sendFetches() {
// 构造拉取消息请求。从哪个节点,哪个分区,什么位置拉取消息
Map<Node, FetchSessionHandler.FetchRequestData> fetchRequestMap = prepareFetchRequests();
for (Map.Entry<Node, FetchSessionHandler.FetchRequestData> entry : fetchRequestMap.entrySet()) {
final Node fetchTarget = entry.getKey();
final FetchSessionHandler.FetchRequestData data = entry.getValue();
//1. 借助 Builder 构造 FetchRequest 对象
final FetchRequest.Builder request = FetchRequest.Builder
.forConsumer(this.maxWaitMs, this.minBytes, data.toSend())
.isolationLevel(isolationLevel)
.setMaxBytes(this.maxBytes)
.metadata(data.metadata())
.toForget(data.toForget());
if (log.isDebugEnabled()) {
log.debug("Sending {} {} to broker {}", isolationLevel, data.toString(), fetchTarget);
} client.send(fetchTarget, request)
//4. 给 RequestFutureCompletionHandler.future 添加 RequestFutureListener
.addListener(new RequestFutureListener<ClientResponse>() {
@Override
public void onSuccess(ClientResponse resp) {
synchronized (Fetcher.this) {
FetchResponse response = (FetchResponse) resp.responseBody();
FetchSessionHandler handler = sessionHandler(fetchTarget.id());
if (handler == null) {
log.error("Unable to find FetchSessionHandler for node {}. Ignoring fetch response.",
fetchTarget.id());
return;
}
if (!handler.handleResponse(response)) {
return;
} Set<TopicPartition> partitions = new HashSet<>(response.responseData().keySet());
FetchResponseMetricAggregator metricAggregator = new FetchResponseMetricAggregator(sensors, partitions); for (Map.Entry<TopicPartition, FetchResponse.PartitionData> entry : response.responseData().entrySet()) {
TopicPartition partition = entry.getKey();
long fetchOffset = data.sessionPartitions().get(partition).fetchOffset;
FetchResponse.PartitionData fetchData = entry.getValue(); log.debug("Fetch {} at offset {} for partition {} returned fetch data {}",
isolationLevel, fetchOffset, partition, fetchData);
// 10. 把数据放入 completedFetches,最终返回给用户
completedFetches.add(new CompletedFetch(partition, fetchOffset, fetchData, metricAggregator,
resp.requestHeader().apiVersion()));
} sensors.fetchLatency.record(resp.requestLatencyMs());
}
} @Override
public void onFailure(RuntimeException e) {
synchronized (Fetcher.this) {
FetchSessionHandler handler = sessionHandler(fetchTarget.id());
if (handler != null) {
handler.handleError(e);
}
}
}
});
}
return fetchRequestMap.size();
}

ConsumerNetworkClient#send

public RequestFuture<ClientResponse> send(Node node, AbstractRequest.Builder<?> requestBuilder) {
long now = time.milliseconds();
//2. 使用 RequestFutureCompletionHandler 作为回调函数
RequestFutureCompletionHandler completionHandler = new RequestFutureCompletionHandler();
ClientRequest clientRequest = client.newClientRequest(node.idString(), requestBuilder, now, true,
completionHandler);
//3. 请求放入 unsent 集合
unsent.put(node, clientRequest); // wakeup the client in case it is blocking in poll so that we can send the queued request
client.wakeup();
return completionHandler.future;
}

ConsumerNetworkClient#poll

// 5. 发送 unsent 中的请求,并没有产生网络 io
trySend(now); // 真实的网络数据写和读
// 6. 发送请求
// 7. 接收响应
// 8. 触发 RequestFutureCompletionHandler 回调
client.poll(0, now); // 9. 触发 RequestFutureListener 中的回调
firePendingCompletedRequests();

NetworkClient#handleCompletedReceives

private void handleCompletedReceives(List<ClientResponse> responses, long now) {
for (NetworkReceive receive : this.selector.completedReceives()) {
String source = receive.source();
InFlightRequest req = inFlightRequests.completeNext(source);
Struct responseStruct = parseStructMaybeUpdateThrottleTimeMetrics(receive.payload(), req.header,
throttleTimeSensor, now);
if (log.isTraceEnabled()) {
log.trace("Completed receive from node {} for {} with correlation id {}, received {}", req.destination,
req.header.apiKey(), req.header.correlationId(), responseStruct);
}
AbstractResponse body = AbstractResponse.parseResponse(req.header.apiKey(), responseStruct);
if (req.isInternalRequest && body instanceof MetadataResponse)
metadataUpdater.handleCompletedMetadataResponse(req.header, now, (MetadataResponse) body);
else if (req.isInternalRequest && body instanceof ApiVersionsResponse)
handleApiVersionsResponse(responses, req, now, (ApiVersionsResponse) body);
else
// 此处给 responses 添加元素
// return new ClientResponse(header, callback, destination, createdTimeMs, timeMs, false, null, response);
// 直接把请求的 callback 赋值给响应
// 生产者发送消息的 callback,是用户通过参数传入的
// 消费者拉取消息的 callback,是在 ConsumerNetworkClient#send 指定的,是 RequestFutureCompletionHandler
responses.add(req.completed(body, now));
}
}

NetworkClient#completeResponses

private void completeResponses(List<ClientResponse> responses) {
for (ClientResponse response : responses) {
try {
// callback.onComplete(this);
response.onComplete();
} catch (Exception e) {
log.error("Uncaught error in request completion:", e);
}
}
}

RequestFutureCompletionHandler#onComplete

public void onComplete(ClientResponse response) {
this.response = response;
pendingCompletion.add(this);
}

ConsumerNetworkClient#firePendingCompletedRequests

private void firePendingCompletedRequests() {
boolean completedRequestsFired = false;
for (;;) {
RequestFutureCompletionHandler completionHandler = pendingCompletion.poll();
if (completionHandler == null)
break; completionHandler.fireCompletion();
completedRequestsFired = true;
} // wakeup the client in case it is blocking in poll for this future's completion
if (completedRequestsFired)
client.wakeup();
}

ConsumerNetworkClient.RequestFutureCompletionHandler#fireCompletion

public void fireCompletion() {
if (e != null) {
future.raise(e);
} else if (response.wasDisconnected()) {
RequestHeader requestHeader = response.requestHeader();
int correlation = requestHeader.correlationId();
log.debug("Cancelled {} request {} with correlation id {} due to node {} being disconnected",
requestHeader.apiKey(), requestHeader, correlation, response.destination());
future.raise(DisconnectException.INSTANCE);
} else if (response.versionMismatch() != null) {
future.raise(response.versionMismatch());
} else {
future.complete(response);
}
}

RequestFuture#complete

public void complete(T value) {
try {
if (value instanceof RuntimeException)
throw new IllegalArgumentException("The argument to complete can not be an instance of RuntimeException"); if (!result.compareAndSet(INCOMPLETE_SENTINEL, value))
throw new IllegalStateException("Invalid attempt to complete a request future which is already complete");
fireSuccess();
} finally {
completedLatch.countDown();
}
} private void fireSuccess() {
T value = value();
while (true) {
RequestFutureListener<T> listener = listeners.poll();
if (listener == null)
break;
// 终于调到 RequestFutureListener
listener.onSuccess(value);
}
}

如果不考虑心跳线程,consumer 第一次 poll 是不会有数据的,此时请求才发出去,响应还没回来,必须在第二次 poll 时,才能同时处理网络读写事件。

跟完之后,个人觉得调用链还是挺长的。一点感觉,全程只有一个线程,但是每次走的分支都不一样,给人的启发就是,单线程只要不等待,速度也很快。

kafka 消费者拉取消息的更多相关文章

  1. Kafka消费者拉取数据异常Unexpected error code 2 while fetching data

    Kafka消费程序间歇性报同一个错: 上网没查到相关资料,只好自己分析.通过进一步分析日志发现,只有在拉取某一个特定的topic的数据时报错,如果拉取其他topic的数据则不会报错.而从这个异常信息来 ...

  2. Kafka消费者手动提交消息偏移

    生产者每次调用poll()方法时,它总是返回由生产者写入Kafka但还没有消费的消息,如果消费者一致处于运行状态,那么分区消息偏移量就没什么用处,但是如果消费者发生崩溃或者有新的消费者加入群组,就会触 ...

  3. 【mq】从零开始实现 mq-09-消费者拉取消息 pull message

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  4. 【mq】从零开始实现 mq-10-消费者拉取消息回执 pull message ack

    前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...

  5. RocketMQ入门(3)拉取消息

    转自:http://www.changeself.net/archives/rocketmq入门(3)拉取消息.html RocketMQ入门(3)拉取消息 RocketMQ不止可以直接推送消息,在消 ...

  6. RocketMQ 拉取消息-文件获取

    看完了上一篇的<RocketMQ 拉取消息-通信模块>,请求进入PullMessageProcessor中,接着 PullMessageProcessor.processRequest(f ...

  7. RocketMQ 拉取消息-通信模块

    首先看server端:class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer 下面这个实现了 ...

  8. .net MVC 微信公众号 点击菜单拉取消息时的事件推送

    官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141016&token=&lang=zh_CN ...

  9. JAVA封装消息中间件调用二(kafka消费者篇)

    上一遍我简单介绍了kafka的生成者使用,调用方式比较简单,今天我给大家分享下封装kafka消费者,作为中间件,我们做的就是最大程度的解耦,使业务方接入我们依赖程度降到最低. 第一步,我们先配置一个消 ...

随机推荐

  1. mysql查看库、表占用存储空间大小

    http://blog.csdn.net/bzfys/article/details/55252962 1. 查看该数据库实例下所有库大小,得到的结果是以MB为单位 <span class=&q ...

  2. Codeforces 989 P循环节01构造 ABCD连通块构造 思维对云遮月参考系坐标轴转换

    A 直接判存不存在连续的三个包含A,B,C就行 /*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a ...

  3. 洛谷P1412 经营与开发题解

    题目链接QWQ这里就不阐述了: 题解部分: 从题面上来看,这是个dp(递推)的题目. 但是dp要满足无后效性,但这个题为了取最值,得考虑从当前开始一直持续到结束的p的影响. 这让我们怎么满足无后效性? ...

  4. 五种I/O模型

    文档地址:https://www.cse.huji.ac.il/course/2004/com1/Exercises/Ex4/I.O.models.pdf 五种I/O模型: 1.  blocking ...

  5. element 表单校验失败自动聚焦到失败的input框

    1.在对应的input框上添加ref属性,直接根据ref就可精确地获取到元素 <el-form-item label="课程名称" :label-width="fo ...

  6. JS 验证码的实现

    转自:https://github.com/ace0109/verifyCode 正要做一个验证码,网上找到这个还不错: gVerify.js: !(function(window, document ...

  7. 洛谷P5055 可持久化文艺平衡树 (可持久化treap)

    题目链接 文艺平衡树的可持久化版,可以使用treap实现. 作为序列使用的treap相对splay的优点如下: 1.代码短 2.容易实现可持久化 3.边界处理方便(splay常常需要在左右两端加上保护 ...

  8. SpringMVC 中的注解@RequestParam与@PathVariable的区别

    @PathVariable绑定URI模板变量值 @PathVariable是用来获得请求url中的动态参数的 @PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上.//配置 ...

  9. Docker(2)--Centos7 上安装部署

    Centos7 上安装docker Docker从1.13版本之后采用时间线的方式作为版本号,分为社区版CE和企业版EE. 社区版是免费提供给个人开发者和小型团体使用的,企业版会提供额外的收费服务,比 ...

  10. 【NOIP2016提高A组集训第13场11.11】最大匹配

    题目 mhy12345学习了二分图匹配,二分图是一种特殊的图,其中的点可以分到两个集合中,使得相同的集合中的点两两没有连边. 图的"匹配"是指这个图的一个边集,里面的边两两不存在公 ...