首先,无论是 Kafka 客户端还是 Broker 端,它们之间的交互都是通过请求-响应的方式完成的。Kafka 自己定义了一套请求协议,用于实现各种各样的交互操作,所有的请求都是通过 TCP 以 Socket进行通讯的。比如 PRODUCE
请求是用于生产消息的,FETCH
请求是用于消费消息的,METADATA
请求是用于请求 Kafka 集群元数据信息的。
因为采用顺序请求的方式吞吐量太低,采用每个请求对应一个线程的方式开销太大,所以Kafka采用Reactor设计模式(1 + M + N) → 经典高并发IO设计模式 来处理请求。
在这个架构中,Kafka Broker 端有个 SocketServer
组件,即 Reactor 模式中的 Dispatcher,它有对应的 Acceptor
线程和一个 Worker 线程池 → 叫网络线程池。Kafka 提供了 Broker 端参数 num.network.threads
(默认为3),用于调整该网络线程池的线程数。
num.io.threads
(默认为8→如果CPU资源充足可以适当调大) 控制了这个线程池中的线程数。如果是 PRODUCE 生产请求,则将消息写入到底层的磁盘日志中;如果是 FETCH 请求,则从磁盘或页缓存中读取消息。Purgatory
炼狱组件中等待,可以看到,Kafka中的Reactor是经典的1(监听) + M(读写) + N(业务逻辑)的设计模式。
需要注意一个细节: 请求队列和响应队列的差别:
请求队列是所有网络线程共享的,而响应队列则是每个网络线程专属的。
这么设计的原因就在于,Dispatcher 只是用于请求分发而不负责响应回传,因此只能让每个网络线程自己发送 Response 给客户端,所以这些 Response 也就没必要放在一个公共的地方。
参考自胡夕老师《Kafka核心技术与实战》极客时间专栏,老师的课很棒,强烈推荐!