1 层次结构

负责进行网络IO请求的是NetworkClient,主要层次结构如下

ClusterConnectionStates报存了每个节点的状态,以node为key,以node的状态为value;inFlightRequets中保存了每个节点已经发送的请求,但是还没有返回的请求,以node为key,以List<ClientRequest>为value。inFlightRequets从名字也可以看出,表示“正在空中飞”的请求。

2 如何保证每次只发送一个请求

sender线程启动后,如果RecordBatch中有消息,会将消息按照所在节点重新排列,每个节点会创建一个ClientRequest,用来发送,每个节点每次只能发送一个ClientRequest,如下

KafkaChannel#setSend(..)

public void setSend(Send send) {
if (this.send != null) // 如果已经有send,会抛出异常
throw new IllegalStateException("Attempt to begin a send operation with prior send operation still in progress.");
this.send = send;
this.transportLayer.addInterestOps(SelectionKey.OP_WRITE);
}

那么kafka是如何保证避免setSend的时候KafkaChannel中已经没有send了呢,这个关键就是在sender线程中会调用NetworkClient#ready(..),会将没有ready的节点去除掉,从而不会在该节点上setSend:

while (iter.hasNext()) {
Node node = iter.next();
if (!this.client.ready(node, now)) { // 关键
iter.remove();
notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now));
}
}

3 NetworkClient#ready(..)

NetworkClient#ready(..)检查节点是否准备好,从而决定是否可以将消息封装成ClientRequest放到KafkaChannel上。

public boolean ready(Node node, long now) {
if (node.isEmpty())
throw new IllegalArgumentException("Cannot connect to empty node " + node); if (isReady(node, now)) // 关键
return true; if (connectionStates.canConnect(node.idString(), now))
initiateConnect(node, now); return false;
}

我们来分析下isReady

public boolean isReady(Node node, long now) {
return !metadataUpdater.isUpdateDue(now) && canSendRequest(node.idString());
}

isReady主要两个条件,一个是判断metadata是否到了更新的时候了,如果metadata需要更新,那么就不发送本次请求,也就是metadata更新优先级高。第二个是判断这个节点是否canSendRequest。

private boolean canSendRequest(String node) {
return connectionStates.isConnected(node) && selector.isChannelReady(node)
&& inFlightRequests.canSendMore(node); // 重点
}

inFlightRequests保存的是“正在空中飞”的请求

public boolean canSendMore(String node) {
Deque<ClientRequest> queue = requests.get(node);
return queue == null || queue.isEmpty() ||
(queue.peekFirst().request().completed() && queue.size() < this.maxInFlightRequestsPerConnection);
}

满足以下几个条件,表示可以继续send

  1. queue是空,即该节点没有“正在空中飞”的request
  2. queue不为空。queue中排在最开头的request已经completed 并且queue的大小小于允许的最大值。如何理解呢?queue是一个双端队列,每次add的时候都会在queue的头部插入,所以queue中第一个就是正在发送的,同样也是KafkaChannel中的send。只有当send发送到网络中的时候才可以继续发送。这就保证了前面说的“如何保证每次只发送一个请求”。

4 inFlightRequests

inFlightRequests保存了"正在空中飞"的请求,所谓“正在空中飞”的意思就是request已经发送到了网络上,但是服务端还没有回ack。NetworkClient#doSend会往inFlightRequests头部放置一个request,同时会在KafkaChannel中放置一个request.send。

public void add(ClientRequest request) {
Deque<ClientRequest> reqs = this.requests.get(request.request().destination());
if (reqs == null) {
reqs = new ArrayDeque<>();
this.requests.put(request.request().destination(), reqs);
}
reqs.addFirst(request); // 重点,插入到头部
}

5 Selector#pollSelectionKeys

Selector#pollSelectionKeys用来处理读写事件。先看写事件

if (channel.ready() && key.isWritable()) {
Send send = channel.write();
if (send != null) {
this.completedSends.add(send); // 请求写完了会放到completedSends中
this.sensors.recordBytesSent(channel.id(), send.size());
}
}

往网络中写的时候,会调用KafkaChannel#write来写。

public Send write() throws IOException {
Send result = null;
if (send != null && send(send)) {
result = send;
send = null; // kafkaChannel中的send被置为null,这时候新的request可以发送了
}
return result;
}
private boolean send(Send send) throws IOException {
send.writeTo(transportLayer);
if (send.completed())
transportLayer.removeInterestOps(SelectionKey.OP_WRITE); return send.completed();
}

发送可能一次不能完全发送完毕,需要发送好几次才能将request全部发送到网络上,只有这个request发送完毕了才能将KafkaChannel中的send置为null,新的request才可以setSend。但此时inFlightRequests并没有移除该request,也就是说该request还在"飞",但是新的request可以添加。发送完毕后会将channel的SelectionKey.OP_WRITE移除,没有发送完毕不会移除,下次轮询的时候该节点没有ready,不会添加新的request,会继续发送没有发完的request。

对于ack=0,不要求服务端ack就表示发送成功。该处理是在NetworkClient#handleCompletedSends(..)中进行的

private void handleCompletedSends(List<ClientResponse> responses, long now) {
// if no response is expected then when the send is completed, return it
for (Send send : this.selector.completedSends()) {
ClientRequest request = this.inFlightRequests.lastSent(send.destination());
if (!request.expectResponse()) { // ack = 0不需要服务端response
this.inFlightRequests.completeLastSent(send.destination()); // request从inFlightRequests中移除,表示此次请求完毕
responses.add(new ClientResponse(request, now, false, null));
}
}
}

对于ack !=0 ,则要求服务端ack才表示发送成功,该处理是在

NetworkClient#handleCompletedReceives(..)中进行

private void handleCompletedReceives(List<ClientResponse> responses, long now) {
for (NetworkReceive receive : this.selector.completedReceives()) {
String source = receive.source();
ClientRequest req = inFlightRequests.completeNext(source);
Struct body = parseResponse(receive.payload(), req.request().header());
if (!metadataUpdater.maybeHandleCompletedReceive(req, now, body))
responses.add(new ClientResponse(req, now, false, body));
}
}

5 参考

https://blog.csdn.net/chunlongyu/article/details/52651960

kafka生产者网络层总结的更多相关文章

  1. 【转】 详解Kafka生产者Producer配置

    粘贴一下这个配置,与我自己的程序做对比,看看能不能完善我的异步带代码:   -----------------------------------------    详解Kafka生产者Produce ...

  2. Kafka生产者-向Kafka中写入数据

    (1)生产者概览 (1)不同的应用场景对消息有不同的需求,即是否允许消息丢失.重复.延迟以及吞吐量的要求.不同场景对Kafka生产者的API使用和配置会有直接的影响. 例子1:信用卡事务处理系统,不允 ...

  3. Python 使用python-kafka类库开发kafka生产者&消费者&客户端

    使用python-kafka类库开发kafka生产者&消费者&客户端   By: 授客 QQ:1033553122       1.测试环境 python 3.4 zookeeper- ...

  4. Kafka集群安装部署、Kafka生产者、Kafka消费者

    Storm上游数据源之Kakfa 目标: 理解Storm消费的数据来源.理解JMS规范.理解Kafka核心组件.掌握Kakfa生产者API.掌握Kafka消费者API.对流式计算的生态环境有深入的了解 ...

  5. [Spark][kafka]kafka 生产者,消费者 互动例子

    [Spark][kafka]kafka 生产者,消费者 互动例子 # pwd/usr/local/kafka_2.11-0.10.0.1/bin 创建topic:# ./kafka-topics.sh ...

  6. Kafka权威指南 读书笔记之(三)Kafka 生产者一一向 Kafka 写入数据

    不管是把 Kafka 作为消息队列.消息总线还是数据存储平台来使用 ,总是需要有一个可以往 Kafka 写入数据的生产者和一个从 Kafka 读取数据的消费者,或者一个兼具两种角色的应用程序. 开发者 ...

  7. kafka生产者

    1.kafka生产者是线程安全的,她允许多个线程共享一个kafka实例 2.kafka管理一个简单的后台线程,所有的IO操作以及与每个broker的tcp连接通信,如果没有正确的关闭生产者可能会造成资 ...

  8. java实现Kafka生产者示例

    使用java实现Kafka的生产者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...

  9. kafka生产者和消费者流程

    前言 根据源码分析kafka java客户端的生产者和消费者的流程. 基于zookeeper的旧消费者 kafka消费者从消费数据到关闭经历的流程. 由于3个核心线程 基于zookeeper的连接器监 ...

随机推荐

  1. ctf平台

    CTF靶场 蓝鲸安全:http://whalectf.xin bugku:https://ctf.bugku.com XCTF攻防世界:https://adworld.xctf.org.cn/ i春秋 ...

  2. 分享几个你可能不知道的交互式Git 命令

    摘要:本文中讲述的几个交互式 Git 命令可以帮助你将文件的特定部分组合成提交. 本文分享自华为云社区<Git你有可能不知道交互式暂存>,作者:龙哥手记. 本节中的几个交互式 Git 命令 ...

  3. 移动BI,移动报表平台

    ​随着大数据时代的到来,随着商业智能衍生出来的移动BI也将处于一片大好的形式中,由于智能手机.移动应用的普及,越来越多的办公软件均已支持了移动办公,这也给移动BI带来了更多的想象,通过一部手机就可以随 ...

  4. 2021年BI软件系统推荐,知名商业智能厂商品牌

    国内外一直有一些厉害的商业智能厂商,在国外,例如国外微软的PowerBI.在国外是商业智能的行业领导者,在国外的市场占有率上远远领先其它产品,然而在中国市场却落后于国内商业智能厂商思迈特软件的Smar ...

  5. C++ 派生类函数重载与虚函数继承详解

    目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...

  6. Window常用快捷键

    Window常用快捷键 Ctrl+C:复制 Ctrl+V:粘贴 Ctrl+A:全选 Ctrl+X:剪切 Ctrl+D:删除 Ctrl+Z:撤销 Ctrl+Y:反撤销 Ctrl+Shift+Esc:调出 ...

  7. Linux查看进程的4种方法

    转至:https://www.cnblogs.com/hml-blog-com/p/11558369.html 进程是在 CPU 及内存中运行的程序代码,而每个进程可以创建一个或多个进程(父子进程). ...

  8. c语言刷 DFS题记录

    144. 二叉树的前序遍历 /** * Definition for a binary tree node. * struct TreeNode { * int val; * struct TreeN ...

  9. STC8单片机波特率115200时乱码解决

    最近开发一个STC8单片机(STC8H3K32S2)项目,需要通过传口与蓝牙模块通讯,波特率高于57600后STC接收时出现乱码,但发送时正常.当将stc8串口和蓝牙串口分别接USB转串口模块通讯正常 ...

  10. laravel7 搜索分页

    html: <form action="{{url('http://www.yanbing.com/fang/index')}}" method="get" ...