Kafka的Producer、Broker和Consumer之间采用的是一套自行设计的基于TCP层的协议
由于UDP协议是一种不可靠的传输协议,所以Kafka系统采用TCP协议作为服务间的通信协议
通信
通信模型
Reactor线程模型
Java NIO中的selector模型。最简单的Reactor模型中,有多个client向服务端发送请求,首先请求会达到Dispatcher组件,它负责将不同的请求分发到多个线程中处理
为什么使用Reactor
传统阻塞IO模型存在的不足
-
每个连接都需要独立线程处理,当并发数大时,创建线程数多,占用资源
-
采用阻塞IO模型,连接建立后,若当前线程没有数据可读,线程会阻塞在读操作上,造成资源浪费
解决方案
-
基于池化思想,避免为每个连接创建线程,连接完成后将业务处理交给线程池处理
-
基于IO复用模型,多个连接共用同一个阻塞对象,不用等待所有的连接。遍历到有新数据可以处理时,操作系统会通知程序,线程跳出阻塞状态,进行业务逻辑处理
Reactor线程模型的思想就是基于IO复用和线程池的结合
Reacotr模型主要分为三个角色
-
Reactor:把IO事件分配给对应的handler处理
-
Acceptor:处理客户端连接事件
-
Handler:处理非阻塞的任务
Reactor线程模型分类
根据Reactor的数量和处理资源的线程数量的不同,分为三类:
单Reactor单线程模型
在Reactor中处理事件,并分发事件,如果是连接事件交给acceptor处理,如果是读写事件和业务处理就交给handler处理,但始终只有一个线程执行所有的事情
不足
-
仅用一个线程处理请求,对于多核资源机器来说是有点浪费的
-
当处理读写任务的线程负载过高后,处理速度下降,事件会堆积,严重的会超时,可能导致客户端重新发送请求,性能越来越差
-
单线程也会有可靠性的问题
单Reactor多线程模型
把业务处理从之前的单一线程脱离出来,换成线程池处理,也就是Reactor线程只处理连接事件和读写事件,业务处理交给线程池处理,充分利用多核机器的资源、提高性能并且增加可靠性
不足
Reactor线程承担所有的事件,例如监听和响应,高并发场景下单线程存在性能问题
多Reactor多线程模型
把Reactor线程拆分了mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理
mainRactor只处理连接事件,用一个线程来处理就好。处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程,业务逻辑由线程池处理
这种模型使各个模块职责单一,降低耦合度,性能和稳定性都有提高 这种模型在许多项目中广泛应用,比如Netty的主从线程模型等
通信流程
Kafka系统采用的是Reactor多线程模型,即通过一个Acceptor线程处理所有的新连接,通过多个Processor线程对请求进行处理(比如解析协议、封装请求、转发等)
-
Client向Server发送请求时,Acceptor负责接收TCP请求,连接成功后传递给Processor线程;
-
Processor线程接收到新的连接后,将其注册到自身的Selector中,并监听READ事件
-
当Client在当前连接对象上写入数据时,会触发READ事件,根据TCP协议调用Handler进行处理
-
Handler处理完成后,可能会有返回值给Client,并将Handler返回的结果绑定Response端进行发送
Kafka老的版本中,以NIO作为网络通信的基础,通过将多个Socket连接注册到一个Selector上进行监听,只用一个线程就能管理多个连接,这极大的节省了多线程的资源开销。
在Kafka之后的新版本中,依然以NIO作为网络通信的基础,也使用了Reactor多线程模型,不同的是,新版本将具体的业务处理模块(Handler模块)独立出去了,并用单独的线程池进行控制
独立Handler模块的优势:
-
能够单独指定Handler的线程数,便于调优和管理
-
防止一个过大的请求阻塞一个Processor线程
-
Request、Handler、Response之间都是通过队列来进行连接的,这样它们彼此之间不存在耦合现象,对提升Kafka系统的性能很有帮助