简介
上次我们通过分析KafkaProducer的源码了解了生产端的主要流程,今天学习下服务端的网络层主要做了什么,先看下 KafkaServer的整体架构图
![a321068fb725b29ffc2a3a14e948c341.png](https://i-blog.csdnimg.cn/blog_migrate/f8360b344263afc48ec401fcc888c2a7.jpeg)
由图可见Kafka的服务端主要包括网络层、API层、日志子系统、副本子系统这几个大模块。当client端发起请求时,网络层会收到请求,并把请求放到共享请求队列中,然后由API层的Handler线程从队列中取出请求,并执行请求。比如是 KafkaProducer发过来的生产消息的请求,会把消息写到磁盘日志中,最后把响应返回给client
网络层
从上面的图中,可以看到Kafka服务端做的事情还是很多的,也有很多优秀的设计,我们后面再慢慢介绍,今天主要学习网络层
网络层主要完成和客户端、其他Broker的网络连接,采用了Reactor模式,一种基于事件驱动的模式,之前写过相关文章,可以参考下(链接)这里不再赘述
网络层的核心类是SocketServer,包含一个Acceptor用来接收新的连接,Acceptor对应多个Processor线程,每个 Processor线程都有自己的Selector,用来从连接中读取请求并写回响应
同时一个Acceptor线程对应多个Handler线程,这才是真正处理请求的线程,Handler线程处理完请求后把响应返回给 Processor线程,其中Processor线程和Handler线程通过RequestChannel传递数据,ReqeustChannel包括共享的RequestQueue和Processor私有的ResponseQueue,下面附上一张图
![36aea3817bba1aefb82ad8fcdd5dbd62.png](https://i-blog.csdnimg.cn/blog_migrate/c399c90aef6b2a32735d583add859677.jpeg)
上面说的有些抽象,我们深入到源码中看看Kafka服务端是如何接收请求并把响应返回给客户端的
源码分析
KafkaServer
KafkaServer是Kafka服务端的主类,KafkaServer中和网络层相关的组件包括SockerServer、KafkaApis和KafkaRequestHandlerPool。其中KafkaApis和KafkaRequestHandlerPool都是通过SocketServer提供的RequestChannel来处理网络请求,和本次介绍的网络层相关的代码如下所示
class KafkaServer(...) {
...
var dataPlaneRequestProcessor: KafkaApis = null
...
def startup(): Unit = {
...
// SocketServer主要关注网络层相关事宜
socketServer = new SocketServer(...)
// Processor涉及到权限相关,所以调用SocketServer.startup时,只先启动Acceptor,初始化完权限相关的凭证后,再启动Processor,所以startupProcessors为false
socketServer.startup(startupProcessors = false)
...
// KafkaApis进行真正的业务逻辑处理
dataPlaneRequestProcessor = new KafkaApis(socketServer.dataPlaneRequestChannel ,...)
// 创建KafkaRequestHandler线程池,其中KafkaRequestHandler主要用来从请求通道中取请求
dataPlaneRequestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.dataPlaneRequestChannel, dataPlaneRequestProcessor, config.numIoThreadsx,...)
...
// 初始化结束
...
// Processor涉及到权限相关的操作,所以要等初始化完成,再启动Processor
socketServer.startDataPlaneProcessors()
...
}
}
我们把KafkaServer网络层再用一张带有具体的类的图展示下
![a353fc93cc83b3a0a93ded09e3cdf5c1.png](https://i-blog.csdnimg.cn/blog_migrate/deebf017286a4f53b6ae7dd29ba54ca0.jpeg)
具体步骤
1.客户端(NetworkClient)发送请求被接收器(Acceptor)转发给处理器(Processor)处理
2.处理器把请求放到请求通道(RequestChannel)的共享请求队列中
3.请求处理器线程(KafkaRequestHandler)从请求通道的共享请求队列中取出请求
4.业务逻辑处理器(KafkaApis)进行业务逻辑处理
5.业务逻辑处理器把响应发到请求通道中与各个处理器对应的响应队列中
6.处理器从对应的响应队列中取出响应
7.处理器将响应的结果返回给客户端
SocketServer.startup方法
通过KafkaServer相关源码我们知道了整体的大概处理流程,既然今天主要学习网络连接相关源码,下面我们看下SocketServer.startup都做了什么
// SocketServer.startup
def startup(startupProcessors: Boolean = true): Unit = {
this.synchronized {
// 初始化控制每个IP上的最大连接数的对象,底层是Map
connectionQuotas = new ConnectionQuotas(config, time)
...
// 创建接收器和处理器
createDataPlaneAcceptorsAndProcessors(config.numNetworkThreads, config.dataPlaneListeners)
// 判断是否需要启动处理器,在KafkaServer初始化代用SocketServer.startup方法时,startipProcessors传
// 的是false,原因请看上面介绍KafkaServer的源码
if (startupProcessors) {
// 启动处理器
startControlPlaneProcessor()
...
}
}
...
}
// 创建接收器和处理器,同时并启动接收器
private def createDataPlaneAcceptorsAndProcessors(dataProcessorsPerListener: Int,endpoints: Seq[EndPoint]): Unit = synchronized {
// 遍历broker节点,为每个broker节点都创建接收器
endpoints.foreach { endpoint =>
connectionQuotas.addListener(config, endpoint.listenerName)
// 创建接收器
val dataPlaneAcceptor = createAcceptor(endpoint, DataPlaneMetricPrefix)
// 创建处理器,并把处理器添加到RequestChannel和Acceptor的处理器列表中
addDataPlaneProcessors(dataPlaneAcceptor, endpoint, dataProcessorsPerListener)
// 启动接收器线程
KafkaThread.nonDaemon(s"data-plane-kafka-socket-acceptor-${endpoint.listenerName}-${endpoint.securityProtocol}-${endpoint.port}