上一篇咱们分析了Metadata的更新机制,其中涉及到一个问题,就是Sender如何跟服务器通讯,也就是网络层。同不少Java项目同样,Kafka client的网络层也是用的Java NIO,而后在上面作了一层封装。html
下面首先看一下,在Sender和服务器之间的部分:react
能够看到,Kafka client基于Java NIO封装了一个网络层,这个网络层最上层的接口是KakfaClient。其层次关系以下:
linux
在本篇中,先详细对最底层的Java NIO进行讲述。< 喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMSBpZD0="nio的4大组件">NIO的4大组件编程
Channel: 在一般的Java网络编程中,咱们知道有一对Socket/ServerSocket对象,每1个socket对象表示一个connection,ServerSocket用于服务器监听新的链接。
在NIO中,与之相对应的一对是SocketChannel/ServerSocketChannel。windows
下图展现了SocketChannel/ServerSocketChannel的类继承层次设计模式
?安全
1服务器 2网络 3框架 4 5 6 7 8 9 10 11 12 |
|
从代码能够看出,一个Channel最基本的操做就是read/write,而且其传进去的必须是ByteBuffer类型,而不是普通的内存buffer。
Buffer:在NIO中,也有1套围绕Buffer的类继承层次,在此就不详述了。只需知道Buffer就是用来封装channel发送/接收的数据。
Selector的主要目的是网络事件的 loop 循环,经过调用selector.poll,不断轮询每一个Channel上读写事件
SelectionKey用来记录一个Channel上的事件集合,每一个Channel对应一个SelectionKey。
SelectionKey也是Selector和Channel之间的关联,经过SelectionKey能够取到对应的Selector和Channel。
关于这4大组件的协做、配合,下面来详细讲述。
在《Unix环境高级编程》中介绍了如下4种IO模型(实际不止4种,但经常使用的就这4种):
阻塞IO: read/write的时候,阻塞调用
非阻塞IO: read/write,没有数据,立马返回,轮询
IO复用:read/write一次都只能监听一个socket,但对于服务器来说,有成千上完个socket链接,如何用一个函数,能够监听全部的socket上面的读写事件呢?这就是IO复用模型,对应linux上面,就是select/poll/epoll3种技术。
异步IO:linux上没有,windows上对应的是IOCP。
相信不少人都据说过网络IO的2种设计模式,关于这2种模式的具体阐述,能够自行google之。
在此处,只想对这2种模式作一个“最通俗的解释“:
Reactor模式:主动模式,所谓主动,是指应用程序不断去轮询,问操做系统,IO是否就绪。Linux下的select/poll/epooll就属于主动模式,须要应用程序中有个循环,一直去poll。
在这种模式下,实际的IO操做仍是应用程序作的。
Proactor模式:被动模式,你把read/write所有交给操做系统,实际的IO操做由操做系统完成,完成以后,再callback你的应用程序。Windows下的IOCP就属于这种模式,再好比C++ Boost中的Asio库,就是典型的Proactor模式。
在Linux平台上,Java NIO就是基于epoll来实现的。全部基于epoll的框架,都有3个阶段:
注册事件(connect,accept,read,write), 轮询IO是否就绪,执行实际IO操做。
下面的代码展现了在linux下,用c语言epoll编程的基本框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
一样, NIO中的Selector一样有如下3个阶段,下面把Selector和epoll的使用作个对比:
能够看到,2者只是写法不一样,一样的, 都有这3个阶段。
下面的表格展现了connect, accept, read, write 这4种事件,分别在这3个阶段对应的函数:
下面看一下Kafka client中Selector的核心实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
从代码能够看出, Selector和epoll在代码结构上基本同样,但在事件的注册上面有区别:
epoll: 每次read/write以后,都要调用epoll_ctl从新注册
Selector: 注册一次,一直有效,一直会有事件产生,所以须要取消注册。下面来详细分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
从上面也能够看出,read事件的注册和connect事件的取消,是同时进行的
由于read是要一直监听远程,是否有新数据到来,因此不会取消,一直监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
总结一下:
(1)“事件就绪“这个概念,对于不一样事件类型,仍是有点歧义的
read事件就绪:这个最好理解,就是远程有新数据到来,须要去read
write事件就绪:这个指什么呢? 其实指本地的socket缓冲区有没有满。没有满的话,应该就是一直就绪的,可写
connect事件就绪: 指connect链接完成
accept事件就绪:有新的链接进来,调用accept处理
(2)不一样类型事件,处理方式是不同的:
connect事件:注册1次,成功以后,就取消了。有且仅有1次
read事件:注册以后不取消,一直监听
write事件: 每调用一次send,注册1次,send成功,取消注册