请求协议

  1. Kafka自定义了一组请求协议,用于实现各种各样的交互操作
    • PRODUCE请求用于生产消息,FETCH请求用于消费消息,METADATA请求用于请求Kafka集群元数据信息
  2. Kafka 2.3总共定义了45种请求格式,所有请求都通过TCP网络以Socket的方式进行通讯

处理请求方案

顺序处理

实现简单,但吞吐量太差,只适用于请求发送非常不频繁的场景

1
2
3
4
while (true) {
Request request = accept(connection);
handle(request);
}

单独线程处理

为每个请求都创建一个新的线程异步处理,完全异步,但开销极大,只适用于请求发送频率很低的场景

1
2
3
4
5
while (true) {
Request request = accept(connection);
Thread thread = new Thread(() -> { handle(request); });
thread.start();
}

Reactor模式

  1. Reactor模式是事件驱动架构的一种实现方式,特别适合应用于处理多个客户端并发向服务器端发起请求的场景
  2. 多个客户端会发送请求给Reactor,Reactor有个请求分发线程Acceptor,将不同的请求下发到多个工作线程中处理
  3. Acceptor线程只用于请求分发,不涉及具体的逻辑处理,非常轻量级,有很高的吞吐量
    • 工作线程可以根据实际业务处理需要任意增减,从而动态调节系统负载能力

Kafka

  1. Broker端有一个SocketServer组件,类似于Reactor模式中的Dispatcher
    • 也有对应的Acceptor线程和一个工作线程池(即网络线程池,参数设置num.network.threads,默认值为3
  2. Acceptor线程采用轮询的方式将入站请求公平地发到所有网络线程中
    • 实现简单,避免了请求处理的倾斜,有利于实现较为公平的请求处理调度
  1. 网络线程拿到请求后,并不是自己处理,而是将请求放入到一个共享请求队列
  2. Broker端还有一个IO线程池,负责从共享请求队列中取出请求,执行真正的处理
    • 如果是PRODUCE请求,将消息写入到底层的磁盘日志
    • 如果是FETCH请求,则从磁盘页缓存中读取消息
  3. IO线程池中的线程才是执行请求逻辑的线程,参数num.io.threads,默认值为8
  4. IO线程处理完请求后,会将生成的响应发送到网络线程池的响应队列
    • 然后由对应的网络线程负责将Response返回给客户端
  5. 请求队列是所有网络线程共享的,而响应队列是每个网络线程专属
    • Purgatory组件用于_缓存延时请求_
    • acks=allPRODUCE请求,必须等待ISR中所有副本都接收消息后才能返回
      • 此时处理该请求的IO线程必须等待其他Broker的写入结果,当请求不能处理时,就会暂存在Purgatory中
      • 等到条件满足后,IO线程会继续处理该请求,并将Response放入对应网络线程的响应队列中
  6. Kafka将PRODUCE、FETCH这类请求称为数据类请求,把LeaderAndIsr、StopReplica这类请求称为控制类请求
    • Kafka 2.3,正式实现了数据类请求控制类请求分离完全拷贝一套组件,实现两类请求的分离)

参考资料

Kafka核心技术与实战