Kafka学习 -- Producer源码分析

本文详细解析了Kafka Producer客户端的数据发送流程,包括初始化、消息队列处理、消息发送等关键步骤。
  Producer客户端代码,它将数据写入到ByteBuffer中,然后再通过JAVA NIO发送到kafka的Server端。 这两天详细跟着Producer的代码,将它的发送过程整理了一下,加深对kafka的理解。

1. Producer的初始化
     producer = new Producer<String, String>(new ProducerConfig(props));
初始化的大量的工作都是在此构造函数中完成。 查看源码
1
2
3
  // 客户端代码会创建kafka.javaapi.producer.Producer, 而此porducer在初始化的时候,
 // 创建kafka.producer.Produce
  def this(config: ProducerConfig) = this(new kafka.producer.Producer[K,V](config))
调用kafka.producer.Producer()此构造函数。 可以查看一下它的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Producer[K,V](val config: ProducerConfig,
                    private val eventHandler: EventHandler[K,V])  // only for unit testing
  extends Logging {
 
  private val queue = new LinkedBlockingQueue[KeyedMessage[K,V]](config.queueBufferingMaxMessages)
 
  private var producerSendThread: ProducerSendThread[K,V] = null
 
  config.producerType match {
    case "sync" =>
    case "async" =>
      sync = false
      //创建一个send线程,并启动它
      producerSendThread = new ProducerSendThread[K,V]("ProducerSendThread-" + config.clientId,
                                                       queue,
                                                       eventHandler,
                                                       config.queueBufferingMaxMs,
                                                       config.batchNumMessages,
                                                       config.clientId)
      producerSendThread.start()
  }
   
 
  //创建实际的handler 对象,后面调用send,实际上就是调用DefaultEventHandler的handle方法
  def this(config: ProducerConfig) =
    this(config,
         new DefaultEventHandler[K,V](config,
                                      Utils.createObject[Partitioner](config.partitionerClass, config.props),
                                      Utils.createObject[Encoder[V]](config.serializerClass, config.props),
                                      Utils.createObject[Encoder[K]](config.keySerializerClass, config.props),
                                      new ProducerPool(config)))
 
 
}
从上面可以看到,主要的两个对象为 ProducerSendThread与DefaultEventHandler,下面分别对于这两个对象进行解析。
但是在我们常见的默认的例子中,是不会走thread线程的
2. 初始化之后,客户端会调用代码:
producer.send(new KeyedMessage<Integer, String>(topic, messageStr));
将消息发送出去,至此,需要客户端编写的代码就完成了。 看一下send的代码
1
2
3
4
5
6
7
8
9
10
11
12
  def send(messages: KeyedMessage[K,V]*) {
    lock synchronized {
      if (hasShutdown.get)
        throw new ProducerClosedException
      recordStats(messages)
      sync match //此处会进行选择,默认的情况下,为true,也就是走handle流程
        case true => eventHandler.handle(messages)  
        case false => asyncSend(messages)  
      }
    }
  }
3. 调用Handler会调用到  ProducerSendThread中的processEvents()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ProducerSendThread[K,V](... ... ) extends Thread(threadName) with Logging with KafkaMetricsGroup {
 
  ... ....
  //在此send线程中,主体动作是调用processEvents
  private def processEvents() {
      ... ... 
    // drain the queue until you get a shutdown command
    //从queue队列中取出一个消息。用户在使用producer.send()时,消息将会被放入到queue队列中
    Stream.continually(queue.poll(scala.math.max(0, (lastSend + queueTime) - SystemTime.milliseconds), TimeUnit.MILLISECONDS))
                      .takeWhile(item =if(item != null) item ne shutdownCommand else true).foreach {
        ...  ... 
        if(currentQueueItem != null) {
          //将取出来的消息赋给event
          events += currentQueueItem
        }
 
    ... .... 
    // send the last batch of events
    //调用tryToHandle来处理这个消息
    tryToHandle(events)
 
  }
  def tryToHandle(events: Seq[KeyedMessage[K,V]]) {
        ... ... 
        //调用DefaultEventHandler中的handle函数来处理消息
        handler.handle(events)
       ... ... 
  }
}
从上面代码中,可以看到,producer的消息,会首先放入到一个BlockingQueue队列中,sendThread线程会不断轮询这个队列,从队列中取出消息,然后调用DefaultEventHandler::hanle进行处理。 

2. DefaultEventHandler::handle
在updateInfo这个函数中,客户端会发送一个TopicMetadataRequest()消息,kafka的server端收到消息后,会从zookeeper读取信息并将消息返回给客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
  def handle(events: Seq[KeyedMessage[K,V]]) {
    //将该消息序列化
    val serializedData = serialize(events)
     ... ... 
        //然后会根据传入的broker信息、topic信息,去取最新的该topic的metadata信息
        //此处还不是太清楚
        Utils.swallowError(brokerPartitionInfo.updateInfo(topicMetadataToRefresh.toSet, correlationId.getAndIncrement))
      ... ... 
      //开始发送消息了
      outstandingProduceRequests = dispatchSerializedData(outstandingProduceRequests)
      ... ... 
  }
    
3. 开始调用dispatchSerializedData()发送消息。
1
2
3
4
5
6
7
8
  private def dispatchSerializedData(messages: Seq[KeyedMessage[K,Message]]): Seq[KeyedMessage[K, Message]] = {
     ... .... 
     //此处就开始发送消息了。
     //很奇怪的是,使用debug模式调式,总是跳过它。加上日志可以看清楚            
     val failedTopicPartitions = send(brokerid, messageSetPerBroker)
      ... ... 
  }
    
4. 正式调用send将消息发送出去
1
2
3
4
5
6
7
8
9
  private def send(brokerId: Int, messagesPerTopic: collection.mutable.Map[TopicAndPartition, ByteBufferMessageSet]) = {
        ... ...
        //首先根据brokerId,找到对应的producer句柄,这个producerPool是事先初始化好的
        val syncProducer = producerPool.getProducer(brokerId)
         ... ... 
         //找到后,开始调用send命令
          val response = syncProducer.send(producerRequest)
        ... ... 
  }
对于这个procerPool.getProducer就是从一个HashMap中找到key值为brokerId的producer, 并用这个producer将这个消息发送出去

5. TCP连接的发送消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  private def doSend(request: RequestOrResponse, readResponse: Boolean = true): Receive = {
    lock synchronized {
      //首先校验消息是否合法
      verifyRequest(request)
      //获取与broken的连接。如我的broker 0是hadoop221:9092,客户端就会与9092这个端口连接
      //只有在首次的时候会连接,连接后,会持有此连
      getOrMakeConnection()
 
      var response: Receive = null
      try {
        //将消息通过tcp连接发送过去
        blockingChannel.send(request)
        if(readResponse) //收到回复消息
          response = blockingChannel.receive()
 
      
      ... ... 
  }
在整个发送过程中,会创建两条连接一个是writeChannel,一个是readChannel,在connect(),我们可以很清楚的看到

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  def connect() = lock synchronized  {
    if(!connected) {
      try {
        channel = SocketChannel.open()
        //阻塞式连接
        channel.configureBlocking(true)
        channel.socket.setSoTimeout(readTimeoutMs)
        channel.socket.setKeepAlive(true)
        channel.socket.setTcpNoDelay(true)
        channel.socket.connect(new InetSocketAddress(host, port), connectTimeoutMs)
        //writeChannel 用于写数据 
        writeChannel = channel
        //而readChannel是获取inputstream流
        readChannel = Channels.newChannel(channel.socket().getInputStream)
        connected = true
        ... ... 
   
    }
  }

6. 数据通过NIO发送出去
1
2
3
4
5
6
7
def send(request: RequestOrResponse):Int = {
     .. ... 
   //在send的时候,会创建一个ByteBufferSend对易用
   val send = new BoundedByteBufferSend(request)
   send.writeCompletely(writeChannel)
}
    
此处我们需要看一下这个ByteBuffer的写入数据的格式:
1
2
3
4
5
6
7
8
9
def this(request: RequestOrResponse) = {
    //初始化bytebuffer的大小
    this(request.sizeInBytes + (if(request.requestId != None) 2 else 0))
    request.requestId match {
      case Some(requestId) =>
        //写入requestId,其值val ProduceKey: Short = 0,也就是第一个值就是requestId
        buffer.putShort(requestId) 
      case None =>
}
后面系统会根据这个requesId的值调用相应的handler去处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值