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