Kafka源码分析-Server-网络层(2)

RequestChannel

Processor线程与Handler线程之间传递数据是通过RequestChannel完成的。在RequestChannel中包含了一个requestQueue队列和多个responseQueue队列,每个Processor线程对应一个responseQueue队列。Processor线程将读取到的请求存入requestQueue队列中,Handler线程从requestQueue队列中取出请求进行处理;Handler线程处理请求产生的响应会放到Processor对应的responseQueue中,Processor线程从其对应的responseQueue中取出响应发送给客户端。RequestChannel的结构如下:


RequestChannel结构.png

RequestChannel核心字段:

  • requestQueue:ArrayBlockingQueue[RequestChannel.Request]类型。Processor线程向Handler线程传递请求的队列。因为多个Processor线程和多个Handler线程并发操作,这个队列是线程安全的队列。
  • responseQueues:LinkedBlockingQueue[RequestChannel.Response]队列的数组,Handler线程向Processor线程传递响应的队列,每个Processor对应数组的一个队列。
  • numProcessors: Processor线程的个数,也是responseQueues数组的长度。
  • queueSize:缓存请求的最大个数,即requestQueue的长度。
  • responseListeners:List[(Int)=>Unit]类型,该字段是监听器列表,监听器的主要功能是Handler线程向responseQueue存放响应时唤醒对应的Processor线程。
    RequestChannel提供了增删requestQueue队列,responseQueues集合以及responseListeners列表中元素的方法。在SocketServer的初始化过程中,有向RequestChannel.responseListeners集合中添加一个唤醒对应Processor线程的监听,如下:
requestChannel.addResponseListener(id => processors(id).wakeup())

每次向responseQueues添加请求时都要触发responseListeners列表中的监听器:

//向对应responseQueue队列中添加SendAction类型的Response
  /** Send a response back to the socket server to be sent over the network */
  def sendResponse(response: RequestChannel.Response) {
    responseQueues(response.processor).put(response)
    for(onResponse <- responseListeners)//调用responseListeners集合中所有的监听器
      onResponse(response.processor)
  }

  //向对应responseQueue队列中添加NoOpAction类型的Response
  /** No operation to take for the request, need to read more over the network */
  def noOperation(processor: Int, request: RequestChannel.Request) {
    responseQueues(processor).put(new RequestChannel.Response(processor, request, null, RequestChannel.NoOpAction))
    for(onResponse <- responseListeners)//调用responseListeners集合中所有的监听器
      onResponse(processor)
  }

  //向对应responseQueue队列中添加CloseConnectionAction类型的Response
  /** Close the connection for the request */
  def closeConnection(processor: Int, request: RequestChannel.Request) {
    responseQueues(processor).put(new RequestChannel.Response(processor, request, null, RequestChannel.CloseConnectionAction))
    for(onResponse <- responseListeners)//调用responseListeners集合中所有的监听器
      onResponse(processor)
  }

在RequestChannel中保存的是RequestChannel.Request和RequestChannel.Response两个类的对象。RequestChannel.Request会对请求进行解析,组成requestId(请求类型ID),header(请求头),body(请求体)等字段,供Handler线程使用,并提供了一些记录操作时间的字段供监控用。RequestChannel.Request的解析过程:

case class Request(processor: Int, connectionId: String, session: Session, private var buffer: ByteBuffer, startTimeMs: Long, securityProtocol: SecurityProtocol) {
    // These need to be volatile because the readers are in the network thread and the writers are in the request
    // handler threads or the purgatory threads
    //下面是一些记录操作时间的字段,由于这些字段会被多个线程修改和读取,使用@volatile保证可见性
    @volatile var requestDequeueTimeMs = -1L
    @volatile var apiLocalCompleteTimeMs = -1L
    @volatile var responseCompleteTimeMs = -1L
    @volatile var responseDequeueTimeMs = -1L
    @volatile var apiRemoteCompleteTimeMs = -1L

    val requestId = buffer.getShort()//请求类型id

    // TODO: this will be removed once we migrated to client-side format
    // for server-side request / response format
    // NOTE: this map only includes the server-side request/response handlers. Newer
    // request types should only use the client-side versions which are parsed with
    // o.a.k.common.requests.AbstractRequest.getRequest()
    private val keyToNameAndDeserializerMap: Map[Short, (ByteBuffer) => RequestOrResponse]=
      Map(ApiKeys.FETCH.id -> FetchRequest.readFrom,
        ApiKeys.CONTROLLED_SHUTDOWN_KEY.id -> ControlledShutdownRequest.readFrom
      )

    // TODO: this will be removed once we migrated to client-side format
    val requestObj =
      keyToNameAndDeserializerMap.get(requestId).map(readFrom => readFrom(buffer)).orNull

    // if we failed to find a server-side mapping, then try using the
    // client-side request / response format
    //请求头
    val header: RequestHeader =
      if (requestObj == null) {
        buffer.rewind
        try RequestHeader.parse(buffer)
        catch {
          case ex: Throwable =>
            throw new InvalidRequestException(s"Error parsing request header. Our best guess of the apiKey is: $requestId", ex)
        }
      } else
        null
    //请求体
    val body: AbstractRequest =
      if (requestObj == null)
        try {
          // For unsupported version of ApiVersionsRequest, create a dummy request to enable an error response to be returned later
          if (header.apiKey == ApiKeys.API_VERSIONS.id && !Protocol.apiVersionSupported(header.apiKey, header.apiVersion))
            new ApiVersionsRequest
          else
          //解析请求
            AbstractRequest.getRequest(header.apiKey, header.apiVersion, buffer)
        } catch {
          case ex: Throwable =>
            throw new InvalidRequestException(s"Error getting request for apiKey: ${header.apiKey} and apiVersion: ${header.apiVersion}", ex)
        }
      else
        null

    buffer = null//解析完成

RequestChannel.Response需要注意,字段ResponseAction有三种类型:SendAction,NoOpAction,CloseConnectionAction。
当请求进入RequestChannel.requestQueue之后,会有多个Handler线程并发处理从其中取出请求处理,如何保证客户端请求的顺序性呢?
可以看下Processor.run()方法,有多处注册/取消OP_READ事件以及注册/取消OP_WRITE事件的操作,这样可以保证每个连接上只有一个请求和对应的响应,从而实现请求的顺序性。

回顾一个请求数据从生产者发送到服务端的流程过程:

消息从生产者到服务端的流转.png

KafkaProducer线程创建ProducerRecord后,会将ProducerRecord缓存进RecordAccumulator。Sender线程从RecordAccumulator中获取缓存的消息,放入KafkaChannel.send字段中等待发送,同时放入InflightRequests队列中等待响应。之后,客户端会通过KSelector将请求发送出去。在服务端,Processor线程使用KSelector读取请求并暂存到stagedReceives队列中,KSelector.poll()方法结束后,请求被转移到completedReceives队列中。之后,Processor将请求做一系列的解析后放入RequestChannel.requestQueue队列。Handler线程会从RequestChannel.requestQueue队列中取出请求进行处理,将处理之后的响应放入RequestChannel.responseQueue队列。Processor线程从其对应的RequestChannel.responseQueue队列中取出响应并放入inflightResponse队列中缓存,当响应发送出去后会将其从inflightResponse中删除。生产者读取响应的过程与服务端读取请求的过程类似,去吧是inFlightRequest中的请求进行确认。消费者与服务端直接的请求和响应的流转过程与上述过程类似。
在高性能的分布式框架中经常采用这种Reactor模式的设计,如:HDFS RPC框架的服务端,ZooKeeper等。也有实现了Reactor模式的框架,如:Netty。

你可能感兴趣的:(Kafka源码分析-Server-网络层(2))