kafka原理剖析(4)-producer消息发送之缓冲区

1 整体过程
kafka原理剖析(4)-producer消息发送之缓冲区_第1张图片
(1)sender线程整体是个while循环,就是在不断轮询每个partition-batch里面有没有合适发送的batch数据。

(2)有要发送的batch数据,看看元数据有没有(没有的要做个标记,后面元数据到位才能发),有元数据才知道往哪个具体机器发。知道了机器,还要建立连接。

(3)对要发送到统一机器的batch做一下聚合分组,聚合后的一组封装成一个request,方便一起发送节省资源。

(4)对发送的数据按照kafka的协议规范进行二进制转化。

(5)通过for循环,用kafka的networkclient把一个个request放入自己的inFlightRequests列表,注意只是进了发送队列,还没真发。

(6)通过networkclient的网络读写事件,用java的nio进行真正收发数据。

2 收集可发送的batch批次
kafka原理剖析(4)-producer消息发送之缓冲区_第2张图片
对照上面的accumulator组件的ready方法,这个组件就是作为producer发送数据的缓冲区,按照topic-patition-batch队列的数据结构进行存储,遍历后得到队列队列第一个batch,然后判断各种条件:
(1)如果batch是重试,没到重试间隔(100ms默认),先不发。

(2)batch只有一个,而且满了,可以发。

(3)batch有多个,取第一个发。(多个是因为前面的满了,才会申请更多内存)

(4)没满,但到了每个batch的最大等待时间(配置是 linger.ms ),也要发。如果linger.ms 配置了0,那么在这只要有batch就一定发。

(5)内存不足,强制flush发送等场景,都需要立刻发。
满足了发送条件的batch,会被收集起来。

3 连接建立过程
kafka原理剖析(4)-producer消息发送之缓冲区_第3张图片

元数据拉取过程前面有单独的一篇介绍,这里是看连接的建立过程,是networkClient的ready方法,主要通过一些连接状态观察连接的建立情况,通过kafka自己封装的selectable组件发起连接,注意连接是非阻塞的,保持alive的,并且关闭nable算法(目的是加快发送)。连接统一控制在selectable中,组件关系为:
NetworkClient持有Selectable,Selectable持有各个机器的连接。

4 核心:NetworkClient的poll
kafka原理剖析(4)-producer消息发送之缓冲区_第4张图片
(1)networkClient的poll方法,其实本质上就是selectable的poll方法,上面说了,selectable持有了每个连接,那么也就有selectKey,所以用nio的方法,处理各种网络事件,典型的就是建立连接,读事件,写事件。
核心组件有:

inFlightRequests:正在发送或等待回应的消息,上面看到sender线程,把可发送的batch调用了send方法,那里不是真发,而是放入本队列。
socketSendBuffer,socketReveiceBuffer: 发送/接收缓冲区

(2)selectable的nio几个重要成员变量:

  a channels,nioselector:保存channel和处理nio的组件,封装在这个网络组件内,非常合适。
  b List connected :有连接的node实例列表
  c completedReveices : 已经接受回来的响应
  d completedSends : 已经发出去的请求
  e stagedReceive: 暂存的接收到的轻球

(3)selectable会轮询所有selectkey,得到有事件的key,然后处理三种事件

(4) ON_CONNECT 连接事件 ,上文说过,发起的连接是非阻塞的,不管连接成功没成功直接往下走,这里就要调用finishConnect,真正阻塞等待连接建立,节省时间,这个思路值得学习。

(5)OP_READ 连接建立后,默认加上了读事件,通过定制的kafkachannel进行读取数据。通过NetworkReceive抽象封装响应,kafka的消息格式也是常见的 消息长度(固定4字节)+消息内容组成。然后kafkachannel会有固定的4个字节的buffer来读长度,然后根据消息长度申请对应大小的内容buffer来下一步读取内容。

    有了这个模式,那么拆包问题可以解决:消息长度4个字节的buffer没读满,说明拆包了,这次掠过下次继续读,读到buffer满了才认为长度拿到;然后内容buffer没读完也说明拆包了,同理下回继续读,读满为止。
    
    读取出来的数据,会放入stageReceives队列里,里面存了channel和receive的对应关系,后面的方法会紧接着这里的receive加入到completedReceives里面。注意,这里一次只会把一个receive放入completeReceives里面,原因是如果一次读取出来了多个receive响应,希望后面的逻辑能一条一条处理。

(6)OP_WRITE
op_write事件,封装了send对象,这个send对象就是最外层send线程往networkclient里面添加的分组好了的request,然后kafkachannel利用nio进行发送,如果遇到拆包没发完,那就多次发送,发完了以后,移除channel的write事件,然后把发出去的请求,加入到selector的completeSends队列里。

所以综上所述,selectable的poll的流程,其实是使用nio处理对应的事件,然后把事件结果写入到selectable的多个队列里,方便networkclient直接读取队列得到不同场景的数据。这种封装解藕思路值得学习。

5 networkclient后续处理
还是参考上图,后续其实是想把返回的receive,调用成功或失败的回调函数。
(1)handleCompletedSend, 其实是把ack=0的情况立刻返回了,因为不用等网络请求,只要accumulator存进去基本就可以认为发送成功了,然后封装个response放个列表。

(2)handleCompletedReceive,这里把收集到的receive封装个response,也放入列表

(3)handleTimeoutRequests, 对超时的请求标记下node状态,下回重新拉元数据

(4)最终,遍历所有response,调用producer成功的response,发送正式完毕。

你可能感兴趣的:(javakafka)