Kafka生产者之JAVA NIO分析

前序

  1. 前面两篇文章,基于元数据获取以及发送消息总体做了分析,本篇博文基于其中Kafka对于JAVA NIO的应用进行分析
  2. Java NIO理论知识是参考美团公众号的文章:https://zhuanlan.zhihu.com/p/23488863
  3. 对理论知识有一定了解后,抽取了Kafka生产者对NIO的应用代码并进行了上传

JAVA NIO

  1. NIO(Non-blocking I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础
  2. 传统BIO(Blocking I/O)模型分析(一个连接一个线程
    2.1 Socket.accept()、Socket.read()、Socket.write()都是同步阻塞的,单线程会挂死在那里,但CPU是被释放出来
    2.2 使用多线程的本质:利用多核、可以利用多线程使用CPU资源

NIO是怎么工作的

  1. 所有的系统I/O都分为两个阶段:等待就绪和操作
  2. 几种常见I/O模型的对比
    在这里插入图片描述
    2.1 对于NIO,如果没有数据,则直接返回0,永远不会阻塞
    2.2 NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)

如何结合事件模型使用NIO同步非阻塞特性(重点)

  1. 利用NIO的读写函数可以立刻返回,如果一个连接不能读写,将这件事记录下来
  2. 记录方式:在Selector上注册标记位,然后切换到其他就绪的连接(channel)继续读写
  3. 具体利用事件模型单线程处理所有I/O请求的步骤:
    3.1 注册事件所对应的处理器
    3.2 在事件选择器上注册事件
    3.3 用一个死循环选择就绪的事件
    (1)select是阻塞的,while(true)调用不会CPU空转

优化线程模型(重点)

  1. 现在服务器一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高
  2. 仔细分析一下我们需要的线程,其实主要包括以下几种:
    2.1 事件分发器,单线程选择就绪的事件
    2.2 I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以
    2.3 业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程
  3. Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用
    在这里插入图片描述

Selector.wakeup()

  1. 主要作用:解除阻塞在Selector.select()/select(long)上的线程,立即返回
  2. 两次成功的select之间多次调用wakeup等价于一次调用
  3. 如果当前没有阻塞在select上,则本次wakeup调用将作用于下一次select——“记忆”作用
  4. 优先级更高的事件触发,希望及时处理

抽取Kafka Producer的NIO应用代码

  1. 演示代码入口:NetworkSendTest
  2. 抽取的代码主要是发送消息到指定分区节点
  3. 代码总结
    3.1 连接指定分区节点:Selector#connect,将节点ID与Channel建立绑定关系channels(Map<String, KafkaChannel>)
    3.2 写入消息到ByteBuffer中,并封装在KafkaChannel的send属性中(NetworkSend初始化)
    3.3 将写事件添加到KafkaChannel的selectionKey中
    3.4 Selector轮询到写就绪事件后触发对应处理器,将ByteBuffer中的数据写入SocketChannel中(KafkaChannel#send)
    3.5 将写事件从KafkaChannel的selectionKey中移除

正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。如下图所示:
在这里插入图片描述
NIO处理方式,一个线程可以管理过个网络连接
在这里插入图片描述

抽取Kafka Producer的Future模式

  1. 在发送消息时,将消息添加累计器重后返回一个FutureRecordMetadata(future的实现)
    1.1 在其get方法获取结果时,用latch.await()阻塞
    截图来自RecordAccumulator#append
  2. 在正式NIO发送请求前,定义一个回调RequestCompletionHandler,并封装请求体中InFlightRequest$ClientRequest
    2.1 在InFlightRequests中属性requests保存节点ID与请求列表的映射
    截图来自Sender#sendProduceRequest
  3. 处理NIO的响应,调用对应节点的回调(inFlightRequests)
    截图来自NetworkClient#completeResponses
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值