Kafka Broker采用Reactor模式来处理IO。Kafka的Broker端有个SocketServer组件,类似于Reactor模式中的Dispatcher,它也有对应的Acceptor线程和一个工作线程池,在Kafka中这个工作线程池叫网络线程池。Broker 会在它所监听的每一个端口上运行一个 Acceptor 线程,这个线程用来创建与客户端的连接,然后把实际的请求转发到网络线程池中,Kafka提供了Broker端参数num.network.threads,用于调整该网络线程池的线程数。其默认值是3,表示每台Broker启动时会创建3个网络线程,专门处理客户端发送的请求。简要概括如下:
Acceptor线程采用轮询的方式将入站请求公平地发到所有网络线程中,因此,在实际使用过程中,这些线程通常都有相同的几率被分配到待处理请求。这种轮询策略编写简单,同时也避免了请求处理的倾斜,有利于实现较为公平的请求处理调度,如下图:
当网络线程拿到请求后,它不是自己处理,而是将请求放入到一个共享请求队列中。Broker端还有个IO线程池,负责从该队列中取出请求,执行真正的处理。如果是PRODUCE生产请求,则将消息写入到底层的磁盘日志中;如果是FETCH请求,则从磁盘或页缓存中读取消息。
IO线程池处中的线程才是执行请求逻辑的线程。Broker端参数num.io.threads控制了这个线程池中的线程数。目前该参数默认值是8,表示每台Broker启动后自动创建8个IO线程处理请求(可自定义修改)。
由于Acceptor线程只负责请求的转发,不负责处理响应,因此网络线程池需要维护每个请求的处理响应,因此响应队列则是每个网络线程专属的。
当生产者设置了ack=all后,发送的消息需要等到所有ISR副本都同步消息后,Broker才能发送响应,因此这种场景会暂存生产者的发送请求。在Kafka中称为延时请求。所谓延时请求,就是那些一时未满足条件不能立刻处理的请求(典型的就是ack=all). Kafka内部通过Purgatory炼狱组件来处理延时请求,稍后一旦满足了完成条件,IO线程会继续处理该请求,并将Response放入对应网络线程的响应队列中,然后发送给客户端。