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去处理