原文地址:http://www.aboutyun.com/thread-9938-1-1.html

问题导读
1.Kafka提供了Producer类作为java producer的api,此类有几种发送方式?
2.总结调用producer.send方法包含哪些流程?
3.Producer难以理解的在什么地方?

producer的发送方式剖析
Kafka提供了Producer类作为java producer的api,该类有sync和async两种发送方式。
sync架构图

async架构

调用流程如下:

代码流程如下:
Producer:当new Producer(new ProducerConfig()),其底层实现,实际会产生两个核心类的实例:Producer、DefaultEventHandler。在创建的同时,会默认new一个ProducerPool,即我们每new一个java的Producer类,就会有创建Producer、EventHandler和ProducerPool,ProducerPool为连接不同kafka broker的池,初始连接个数有broker.list参数决定。
调用producer.send方法流程:
当应用程序调用producer.send方法时,其内部其实调的是eventhandler.handle(message)方法,eventHandler会首先序列化该消息,
eventHandler.serialize(events)-->dispatchSerializedData()-->partitionAndCollate()-->send()-->SyncProducer.send()
调用逻辑解释:当客户端应用程序调用producer发送消息messages时(既可以发送单条消息,也可以发送List多条消息),调用eventhandler.serialize首先序列化所有消息,序列化操作用户可以自定义实现Encoder接口,下一步调用partitionAndCollate根据topics的messages进行分组操作,messages分配给dataPerBroker(多个不同的Broker的Map),根据不同Broker调用不同的SyncProducer.send批量发送消息数据,SyncProducer包装了nio网络操作信息。
Producer的sync与async发送消息处理,大家看以上架构图一目了然。
partitionAndCollate方法详细作用:获取所有partitions的leader所在leaderBrokerId(就是在该partiionid的leader分布在哪个broker上),
创建一个HashMap>>>,把messages按照brokerId分组组装数据,然后为SyncProducer分别发送消息作准备工作。

名称解释:partKey:分区关键字,当客户端应用程序实现Partitioner接口时,传入参数key为分区关键字,根据key和numPartitions,返回分区(partitions)索引。记住partitions分区索引是从0开始的。

Producer平滑扩容机制
如果开发过producer客户端代码,会知道metadata.broker.list参数,它的含义是kafak broker的ip和port列表,producer初始化时,就连接这几个broker,这时大家会有疑问,producer支持kafka cluster新增broker节点?它又没有监听zk broker节点或从zk中获取broker信息,答案是肯定的,producer可以支持平滑扩容broker,他是通过定时与现有的metadata.broker.list通信,获取新增broker信息,然后把新建的SyncProducer放入ProducerPool中。等待后续应用程序调用。

DefaultEventHandler类中初始化实例化BrokerPartitionInfo类,然后定期brokerPartitionInfo.updateInfo方法,DefaultEventHandler部分代码如下:
def handle(events: Seq[KeyedMessage[K,V]]) {
......
while (remainingRetries > 0 && outstandingProduceRequests.size > 0) {
topicMetadataToRefresh ++= outstandingProduceRequests.map(_.topic)
if (topicMetadataRefreshInterval >= 0 &&
SystemTime.milliseconds - lastTopicMetadataRefreshTime > topicMetadataRefreshInterval) {
Utils.swallowError(brokerPartitionInfo.updateInfo(topicMetadataToRefresh.toSet, correlationId.getAndIncrement))
sendPartitionPerTopicCache.clear()
topicMetadataToRefresh.clear
lastTopicMetadataRefreshTime = SystemTime.milliseconds
}
outstandingProduceRequests = dispatchSerializedData(outstandingProduceRequests)
if (outstandingProduceRequests.size > 0) {
info("Back off for %d ms before retrying send. Remaining retries = %d".format(config.retryBackoffMs, remainingRetries-1))
//休眠时间,多长时间刷新一次
Thread.sleep(config.retryBackoffMs)
// 生产者定期请求刷新最新topics的broker元数据信息
Utils.swallowError(brokerPartitionInfo.updateInfo(outstandingProduceRequests.map(_.topic).toSet, correlationId.getAndIncrement))
.....
}
}
}

BrokerPartitionInfo的updateInfo方法代码如下:

 def updateInfo(topics: Set[String], correlationId: Int) {
var topicsMetadata: Seq[TopicMetadata] = Nil
//根据topics列表,meta.broker.list,其他配置参数,correlationId表示请求次数,一个计数器参数而已
//创建一个topicMetadataRequest,并随机的选取传入的broker信息中任何一个去取metadata,直到取到为止
val topicMetadataResponse = ClientUtils.fetchTopicMetadata(topics, brokers, producerConfig, correlationId)
topicsMetadata = topicMetadataResponse.topicsMetadata
// throw partition specific exception
topicsMetadata.foreach(tmd =>{
trace("Metadata for topic %s is %s".format(tmd.topic, tmd))
if(tmd.errorCode == ErrorMapping.NoError) {
topicPartitionInfo.put(tmd.topic, tmd)
} else
warn("Error while fetching metadata [%s] for topic [%s]: %s ".format(tmd, tmd.topic, ErrorMapping.exceptionFor(tmd.errorCode).getClass))
tmd.partitionsMetadata.foreach(pmd =>{
if (pmd.errorCode != ErrorMapping.NoError && pmd.errorCode == ErrorMapping.LeaderNotAvailableCode) {
warn("Error while fetching metadata %s for topic partition [%s,%d]: [%s]".format(pmd, tmd.topic, pmd.partitionId,
ErrorMapping.exceptionFor(pmd.errorCode).getClass))
} // any other error code (e.g. ReplicaNotAvailable) can be ignored since the producer does not need to access the replica and isr metadata
})
})
producerPool.updateProducer(topicsMetadata)
}

ClientUtils.fetchTopicMetadata方法代码:

def fetchTopicMetadata(topics: Set[String], brokers: Seq[Broker], producerConfig: ProducerConfig, correlationId: Int): TopicMetadataResponse = {
var fetchMetaDataSucceeded: Boolean = false
var i: Int = 0
val topicMetadataRequest = new TopicMetadataRequest(TopicMetadataRequest.CurrentVersion, correlationId, producerConfig.clientId, topics.toSeq)
var topicMetadataResponse: TopicMetadataResponse = null
var t: Throwable = null
val shuffledBrokers = Random.shuffle(brokers) //生成随机数
while(i
ProducerPool的updateProducer
def updateProducer(topicMetadata: Seq[TopicMetadata]) {
val newBrokers = new collection.mutable.HashSet[Broker]
topicMetadata.foreach(tmd => {
tmd.partitionsMetadata.foreach(pmd => {
if(pmd.leader.isDefined)
newBrokers+=(pmd.leader.get)
})
})
lock synchronized {
newBrokers.foreach(b => {
if(syncProducers.contains(b.id)){
syncProducers(b.id).close()
syncProducers.put(b.id, ProducerPool.createSyncProducer(config, b))
} else
syncProducers.put(b.id, ProducerPool.createSyncProducer(config, b))
})
}
}

当我们启动kafka broker后,并且大量producer和consumer时,经常会报如下异常信息。

  1. root@lizhitao:/opt/soft$ Closing socket connection to 192.168.11.166

复制代码

笔者也是经常很长时间看源码分析,才明白了为什么ProducerConfig配置信息里面并不要求使用者提供完整的kafka集群的broker信息,而是任选一个或几个即可。因为他会通过您选择的broker和topics信息而获取最新的所有的broker信息。
值得了解的是用于发送TopicMetadataRequest的SyncProducer虽然是用ProducerPool.createSyncProducer方法建出来的,但用完并不还回ProducerPool,而是直接Close.

重难点理解:
刷新metadata并不仅在第一次初始化时做。为了能适应kafka broker运行中因为各种原因挂掉、paritition改变等变化,
eventHandler会定期的再去刷新一次该metadata,刷新的间隔用参数topic.metadata.refresh.interval.ms定义,默认值是10分钟。
这里有三点需要强调:

客户端调用send, 才会新建SyncProducer,只有调用send才会去定期刷新metadata在每次取metadata时,kafka会新建一个SyncProducer去取metadata,逻辑处理完后再close。根据当前SyncProducer(一个Broker的连接)取得的最新的完整的metadata,刷新ProducerPool中到broker的连接.每10分钟的刷新会直接重新把到每个broker的socket连接重建,意味着在这之后的第一个请求会有几百毫秒的延迟。如果不想要该延迟,把topic.metadata.refresh.interval.ms值改为-1,这样只有在发送失败时,才会重新刷新。Kafka的集群中如果某个partition所在的broker挂了,可以检查错误后重启重新加入集群,手动做rebalance,producer的连接会再次断掉,直到rebalance完成,那么刷新后取到的连接着中就会有这个新加入的broker。

说明:每个SyncProducer实例化对象会建立一个socket连接

特别注意:
在ClientUtils.fetchTopicMetadata调用完成后,回到BrokerPartitionInfo.updateInfo继续执行,在其末尾,pool会根据上面取得的最新的metadata建立所有的SyncProducer,即Socket通道producerPool.updateProducer(topicsMetadata)

在ProducerPool中,SyncProducer的数目是由该topic的partition数目控制的,即每一个SyncProducer对应一个broker,内部封了一个到该broker的socket连接。每次刷新时,会把已存在SyncProducer给close掉,即关闭socket连接,然后新建SyncProducer,即新建socket连接,去覆盖老的。
如果不存在,则直接创建新的。

apache kafka源码分析-Producer分析---转载的更多相关文章

  1. Apache Kafka源码分析 – Broker Server

    1. Kafka.scala 在Kafka的main入口中startup KafkaServerStartable, 而KafkaServerStartable这是对KafkaServer的封装 1: ...

  2. Apache Kafka源码分析 - kafka controller

    前面已经分析过kafka server的启动过程,以及server所能处理的所有的request,即KafkaApis 剩下的,其实关键就是controller,以及partition和replica ...

  3. Apache Kafka源码分析 – Log Management

    LogManager LogManager会管理broker上所有的logs(在一个log目录下),一个topic的一个partition对应于一个log(一个log子目录)首先loadLogs会加载 ...

  4. Apache Kafka源码分析 - autoLeaderRebalanceEnable

    在broker的配置中,auto.leader.rebalance.enable (false) 那么这个leader是如何进行rebalance的? 首先在controller启动的时候会打开一个s ...

  5. Apache Kafka源码分析 - KafkaApis

    kafka apis反映出kafka broker server可以提供哪些服务,broker server主要和producer,consumer,controller有交互,搞清这些api就清楚了 ...

  6. Apache Kafka源码分析 – Controller

    https://cwiki.apache.org/confluence/display/KAFKA/Kafka+Controller+Internalshttps://cwiki.apache.org ...

  7. Apache Kafka源码分析 – Replica and Partition

    Replica 对于local replica, 需要记录highWatermarkValue,表示当前已经committed的数据对于remote replica,需要记录logEndOffsetV ...

  8. Apache Kafka源码分析 – ReplicaManager

    如果说controller作为master,负责全局的事情,比如选取leader,reassignment等那么ReplicaManager就是worker,负责完成replica的管理工作 主要工作 ...

  9. Apache Kafka源码分析 - ReplicaStateMachine

    startup 在onControllerFailover中被调用, /** * Invoked on successful controller election. First registers ...

随机推荐

  1. NOI冲刺计划

    省选过了,剩下大概是NOI冲刺了吧.中间还有一大堆诸如会考,CTSC,APIO等东西. 最近先不急着天天刷八中了吧,多在不同网站见一些题,然后再着重提高一下代码准确性.重点把DP这个板块多练习一下,八 ...

  2. WinterCamp 2015 总结

    这次WC2015确实有很多遗憾,特别是考试的时候犯的低级错误,由于我没有看到第三题每个点输出不全可以得小分,对于又没跑出来的点,我都根本没有上交.这确实是一个很悲伤的事情,但是也给我了足够时间去反思. ...

  3. 两种方法,获取磁盘剩余空间--PYTHON

    import ctypes import os import platform import sys def get_free_space_mb(folder): """ ...

  4. UVA 1513 Movie collection

    #include<stdio.h> #include<string.h> #include<stdlib.h> #define N 200010 #define l ...

  5. Multi-bit per cell storage

    Memories Scaling      其他的的半导体存储器的制程一般2年为一个升级周期,但是nand flash 存储器的制程升级周期和他们比起来只有1年.这种更快的制程升级导致SLC NAND ...

  6. zabbix通过jmx监控tomcat

    Zabbix版本: Zabbix 3.0.2 一.服务端配置 1.安装jdk(版本1.7.0_79) 安装与配置比较简单,过程省略.执行java -version命令,出现类似界面表示成功.   2. ...

  7. Ubuntu下安装Apache2, php5 mysql

    不错的博文:http://blog.csdn.net/guaikai/article/details/6905781 1:首先安装apache:打开终端(ctrl+Alt+t), 输入命令:sudo ...

  8. winform代码反编译后图片等资源文件恢复解决方案

    用Reflector工具反编译的winform代码,图片等资源文件不能很好的反编译成功. 这里有一个笨的解决方案.首先我们要了解图片资源当初加入到工程的几种方式,及他们所在的位置. 一般winform ...

  9. jQuery dataTables 网格

    对于服务器来说,可以通过请求参数来获得当前的操作信息. 类型 名称 说明 int iDisplayStart 显示的起始索引 int iDisplayLength 显示的行数 int iColumns ...

  10. Axure RP 8.0 中继器初体验

    为了解决增删等复杂交互的问题,中继器是个不错的选择. 拖拽出一个默认的中继器 中继器的数据集感觉就像是数据库一样,在右边检视窗口中可以看到中继器的默认数据集,可以理解成一张二维表.默认有1列,现成的3 ...