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去处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值