kafka BufferPool_Time

一.Request:Kafka Broker 支持的各类请求


Broker 与 Clients 进行交互主要是基于Request/Response 机制的。

class Request(val processor: Int,              val context: RequestContext,              val startTimeNanos: Long,              memoryPool: MemoryPool,              @volatile private var buffer: ByteBuffer,              metrics: RequestChannel.Metrics) extends BaseRequest {                                }

1.processor


processor 是 Processor 线程的序号,即这个请求是由哪个 Processor 线程接收处理的。Broker端参数 num.network.threads 控制了 Broker 每个监听器上创建的 Processor 线程数。 假设你的 listeners 配置为 PLAINTEXT://localhost:9092,SSL://localhost:9093,那么,在默认情况下,Broker 启动时会创建6个 Processor 线程,每3个为一组,分别给 listeners 参数中设置的两个监听器使用,每组的序号分别是 0、1、2。 当 Request 被后面的 I/O 线程处理完成后,还要依靠 Processor 线程发送 Response 给请求发送方,因此,Request中必须记录它之前是被哪个 Processor 线程接收的。另外,这里我们要先明确一点:Processor 线程仅仅是网络接收线程,不会执行真正的 Request 请求处理逻辑,那是 I/O 线程负责的事情。


2.context


context 是用来标识请求上下文信息的。Kafka 源码中定义了 RequestContext 类,顾名思义,它保存了有关 Request 的所有上下文信息。

public class RequestContext implements AuthorizableRequestContext {    public final RequestHeader header; // Request头部数据,主要是一些对用户不可见的元数据信息,如Request类型、Request API版本、clientId等    public final String connectionId; // Request发送方的TCP连接串标识,由Kafka根据一定规则定义,主要用于表示TCP连接    public final InetAddress clientAddress; // Request发送方IP地址    public final KafkaPrincipal principal;  // Kafka用户认证类,用于认证授权    public final ListenerName listenerName; // 监听器名称,可以是预定义的监听器(如PLAINTEXT),也可自行定义    public final SecurityProtocol securityProtocol; // 安全协议类型,目前支持4种:PLAINTEXT、SSL、SASL_PLAINTEXT、SASL_SSL    public final ClientInformation clientInformation; // 用户自定义的一些连接方信息    // 从给定的ByteBuffer中提取出Request和对应的Size值    public RequestAndSize parseRequest(ByteBuffer buffer) {             ......    }    ......}

3.memoryPool


memoryPool 表示源码定义的一个非阻塞式的内存缓冲区,主要作用是避免 Request 对象无限使用内存。当前,该内存缓冲区的接口类和实现类,分别是 MemoryPool 和 SimpleMemoryPool。


4.buffer


buffer 是真正保存 Request 对象内容的字节缓冲区。Request 发送方必须按照 Kafka RPC 协议规定的格式向该缓冲区写入字节,否则将抛出 InvalidRequestException 异常。这个逻辑主要是由 RequestContext 的 parseRequest 方法实现的。

public RequestAndSize parseRequest(ByteBuffer buffer) {    if (isUnsupportedApiVersionsRequest()) {        // 不支持的ApiVersions请求类型被视为是V0版本的请求,并且不做解析操作,直接返回        ApiVersionsRequest apiVersionsRequest = new ApiVersionsRequest(new ApiVersionsRequestData(), (short) 0, header.apiVersion());        return new RequestAndSize(apiVersionsRequest, 0);    } else {        // 从请求头部数据中获取ApiKeys信息        ApiKeys apiKey = header.apiKey();        try {            // 从请求头部数据中获取版本信息            short apiVersion = header.apiVersion();            // 解析请求            Struct struct = apiKey.parseRequest(apiVersion, buffer);            AbstractRequest body = AbstractRequest.parseRequest(apiKey, apiVersion, struct);            // 封装解析后的请求对象以及请求大小返回            return new RequestAndSize(body, struct.sizeOf());        } catch (Throwable ex) {            // 解析过程中出现任何问题都视为无效请求,抛出异常            throw new InvalidRequestException("Error getting request for apiKey: " + apiKey +                    ", apiVersion: " + header.apiVersion() +                    ", connectionId: " + connectionId +                    ", listenerName: " + listenerName +                    ", principal: " + principal, ex);        }    }}

5.metrics


metrics 是 Request 相关的各种监控指标的一个管理类。它里面构建了一个 Map,封装了所有的请求 JMX 指标。除了上面这些重要的字段属性之外,Request 类中的大部分代码都是与监控指标相关的.

二.响应:定义了与 Request 对应的各类响应


Kafka 为 Response 定义了1个抽象父类和5个具体子类

kafka BufferPool_localhost 当前无法处理此请求。_02

abstract class Response(val request: Request) {  def processor: Int = request.processor  def responseString: Option[String] = Some("")  def onComplete: Option[Send => Unit] = None  override def toString: String}

三.RequestChannel:传输 Request/Response 的通道


1.重要属性

class RequestChannel(val queueSize: Int, val metricNamePrefix : String, time: Time) extends KafkaMetricsGroup {  import RequestChannel._  val metrics = new RequestChannel.Metrics  private val requestQueue = new ArrayBlockingQueue[BaseRequest](queueSize)  private val processors = new ConcurrentHashMap[Int, Processor]()  val requestQueueSizeMetricName = metricNamePrefix.concat(RequestQueueSizeMetric)  val responseQueueSizeMetricName = metricNamePrefix.concat(ResponseQueueSizeMetric)  }
  • requestQueue:请求队列,每个 RequestChannel 对象实例创建时,会定义一个队列来保存Broker接收到的各类请求,Kafka 使用 Java 提供的阻塞队列 ArrayBlockingQueue 实现这个请求队列,并利用它天然提供的线程安全性来保证多个线程能够并发安全高效地访问请求队列。
  • queueSize:请求队列的最大长度。当 Broker 启动时,SocketServer 组件会创建 RequestChannel 对象并把 Broker 端参数 queued.max.requests 赋值给 queueSize。因此在默认情况下,每个 RequestChannel 上的队列长度是 500。
  • processors:封装的是RequestChannel下辖的 Processor 线程池。每个Processor线程负责具体的请求处理逻辑。

2.Processor 管理


当前 Kafka Broker 端所有网络线程都是在 RequestChannel 中维护的。

def addProcessor(processor: Processor): Unit = {  if (processors.putIfAbsent(processor.id, processor) != null)    warn(s"Unexpected processor with processorId ${processor.id}")  newGauge(responseQueueSizeMetricName, () => processor.responseQueueSize,    Map(ProcessorMetricTag -> processor.id.toString))}def removeProcessor(processorId: Int): Unit = {  processors.remove(processorId)  removeMetric(responseQueueSizeMetricName, Map(ProcessorMetricTag -> processorId.toString))}

3.处理 Request 和 Response


所谓的发送 Request,仅仅是将 Request 对象放置在 Request 队列中而已,而接收 Request 则是从队列中取出 Request。整个流程构成了一个迷你版的“生产者 - 消费者”模式,然后依靠 ArrayBlockingQueue 的线程安全性来确保整个过程的线程安全。

/** Send a request to be handled, potentially blocking until there is room in the queue for the request */  def sendRequest(request: RequestChannel.Request): Unit = {    requestQueue.put(request)  }  /** Get the next request or block until specified time has elapsed */  def receiveRequest(timeout: Long): RequestChannel.BaseRequest =    requestQueue.poll(timeout, TimeUnit.MILLISECONDS)  /** Get the next request or block until there is one */  def receiveRequest(): RequestChannel.BaseRequest =    requestQueue.take()


对于 Response 而言,则没有所谓的接收 Response,只有发送 Response,即 sendResponse 方法。sendResponse 是啥意思呢?其实就是把 Response 对象发送出去,也就是将 Response 添加到 Response 队列的过程。

四.监控指标:封装了与 Request 队列相关的

object RequestMetrics {  val consumerFetchMetricName = ApiKeys.FETCH.name + "Consumer"  val followFetchMetricName = ApiKeys.FETCH.name + "Follower"  val RequestsPerSec = "RequestsPerSec"  val RequestQueueTimeMs = "RequestQueueTimeMs"  val LocalTimeMs = "LocalTimeMs"  val RemoteTimeMs = "RemoteTimeMs"  val ThrottleTimeMs = "ThrottleTimeMs"  val ResponseQueueTimeMs = "ResponseQueueTimeMs"  val ResponseSendTimeMs = "ResponseSendTimeMs"  val TotalTimeMs = "TotalTimeMs"  val RequestBytes = "RequestBytes"  val MessageConversionsTimeMs = "MessageConversionsTimeMs"  val TemporaryMemoryBytes = "TemporaryMemoryBytes"  val ErrorsPerSec = "ErrorsPerSec"}
  • RequestsPerSec:每秒处理的 Request 数,用来评估 Broker 的繁忙状态。
  • RequestQueueTimeMs:计算 Request 在 Request 队列中的平均等候时间,单位是毫秒。倘若 Request 在队列的等待时间过长,你通常需要增加后端 I/O 线程的数量,来加快队列中 Request 的拿取速度。
  • LocalTimeMs:计算 Request 实际被处理的时间,单位是毫秒。一旦定位到这个监控项的值很大,你就需要进一步研究 Request 被处理的逻辑了,具体分析到底是哪一步消耗了过多的时间。
  • RemoteTimeMs:Kafka 的读写请求(PRODUCE 请求和 FETCH 请求)逻辑涉及等待其他 Broker 操作的步骤。RemoteTimeMs 计算的,就是等待其他 Broker 完成指定逻辑的时间。因为等待的是其他 Broker,因此被称为 Remote Time。这个监控项非常重要!Kafka 生产环境中设置 acks=all 的 Producer 程序发送消息延时高的主要原因,往往就是 Remote Time 高。因此,如果你也碰到了这样的问题,不妨先定位一下 Remote Time 是不是瓶颈。
  • TotalTimeMs:计算 Request 被处理的完整流程时间。这是最实用的监控指标,没有之一!毕竟,我们通常都是根据 TotalTimeMs 来判断系统是否出现问题的。一旦发现了问题,我们才会利用前面的几个监控项进一步定位问题的原因。

五.文总结一下:

kafka BufferPool_ci_03