Kafka 0.8 Producer处理逻辑
Kafka Producer产生数据发送给Kafka Server,具体的分发逻辑及负载均衡逻辑,全部由producer维护。
1.Kafka Producer默认调用逻辑
1.1 默认Partition逻辑
1、没有key时的分发逻辑
每隔 topic.metadata.refresh.interval.ms
的时间,随机选择一个partition。这个时间窗口内的所有记录发送到这个partition。发送数据出错后也会重新选择一个partition
2、根据key分发
对key求hash,然后对partition数量求模
Utils.abs(key.hashCode) % numPartitions
1.2 如何获取Partition的leader信息(元数据):
决定好发送到哪个Partition后,需要明确该Partition的leader是哪台broker才能决定发送到哪里。
具体实现位置:kafka.client.ClientUtils#fetchTopicMetadata
实现方案
- 从broker获取Partition的元数据。由于Kafka所有broker存有所有的元数据,所以任何一个broker都可以返回所有的元数据
- broker选取策略:将broker列表随机排序,从首个broker开始访问,如果出错,访问下一个
- 出错处理:出错后向下一个broker请求元数据
注意
- Producer是从broker获取元数据的,并不关心zookeeper。
- broker发生变化后,producer获取元数据的功能不能动态变化。
- 获取元数据时使用的broker列表由producer的配置中的 metadata.broker.list 决定。该列表中的机器只要有一台正常服务,producer就能获取元数据。
获取元数据后,producer可以写数据到非 metadata.broker.list 列表中的broker
错误处理: producer的send函数默认没有返回值。出错处理有EventHandler实现。
DefaultEventHandler的错误处理如下:
- 获取出错的数据
- 等待一个间隔时间,由配置
retry.backoff.ms
决定这段时间长短 - 重新获取元数据
- 重新发送数据
出错重试次数由配置 message.send.max.retries 决定.所有重试全部失败时,DefaultEventHandler会抛出异常。代码如下:
if(outstandingProduceRequests.size > 0) {
producerStats.failedSendRate.mark()
val correlationIdEnd = correlationId.get()
error("Failed to send requests for topics %s with correlation ids in [%d,%d]"
.format(outstandingProduceRequests.map(_.topic).toSet.mkString(","),
correlationIdStart, correlationIdEnd-1))
throw new FailedToSendMessageException("Failed to send messages after " + config.messageSendMaxRetries + " tries.", null)
}
2.Producer的发送方式剖析
Kafka提供了Producer类作为java producer的api,该类有sync和async两种发送方式。
2.1 Producer sync发送消息流程
2.2 Producer async发送消息流程
Kafka中Producer异步发送消息是基于同步发送消息的接口来实现的,异步发送消息的实现很简单,客户端消息发送过来以后,先放入到一个BlockingQueue队列中然后就返回了。Producer再开启一个线程(ProducerSendThread)不断从队列中取出消息,然后调用同步发送消息的接口将消息发送给Broker。下面是具体的调用流程:
代码流程如下:
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网络操作信息。
partitionAndCollate()
方法详细作用:获取所有partitions的leader所在leaderBrokerId(就是在该partiionid的leader分布在哪个broker上),
创建一个HashMap<int, Map<TopicAndPartition, List<KeyedMessage<K,Message>>>>
,把messages
按照brokerId分组组装数据,然后为SyncProducer
分别发送消息作准备工作。
名称解释:partKey:分区关键字,当客户端应用程序实现Partitioner接口时,传入参数key为分区关键字,根据key和numPartitions,返回分区(partitions)索引。记住partitions分区索引是从0开始的。
3.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))
.....
}
}
}
有空可以细看:
ClientUtils.fetchTopicMetadata方法
、ProducerPool的updateProducer方法
4.重难点理解
刷新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连接(这个好理解,SyncProducer是threadsafe的,每个SyncProducer的这个socket连接对应一个Broker。它会被放到ProducerPool
中去)。
4.特别注意
在ClientUtils.fetchTopicMetadata
调用完成后,回到BrokerPartitionInfo.updateInfo
继续执行,在其末尾,pool会根据上面取得的最新的metadata建立所有的SyncProducer,即Socket通道
producerPool.updateProducer(topicsMetadata)
在ProducerPool中,SyncProducer的数目是由该topic的partition数目控制的,即每一个SyncProducer对应一个broker,内部封了一个到该broker的socket连接。每次刷新时,会把已存在SyncProducer给close掉,即关闭socket连接,然后新建SyncProducer,即新建socket连接,去覆盖老的。
如果不存在,则直接创建新的。
5.美团的一个Producer应用问题
问题:一个topic的partition的所有broker宕机(这个可能性太小了。)当Kafka集群中某个Partition所有存活的节点都失效或挂掉。会造成Producer尝试重新发送message.send.max.retries(默认值为3)次后抛出Exception,每次尝试都会休眠一定时间(默认值为100ms)。用户捕捉到异常其结果,停止发送会阻塞,继续发送消息会丢失。
简单提一下解决思路
Kafka中默认发送消息方式不变,给用户提供一种可选择方式,增加一个消息发送失效转移的开关,当Producer发送到目标Partition的(所有副本本来存活的)节点都失效或挂掉,就转发到其他Partition上。
Kafka 0.8 Producer处理逻辑的更多相关文章
- apache kafka系列之Producer处理逻辑
最近研究producer的负载均衡策略,,,,我在librdkafka里边用代码实现了partition 值的轮询方法,,,但是在现场验证时,他的负载均衡不起作用,,,所以来找找原因: 下文是一篇描 ...
- kafka性能测试(转)KAFKA 0.8 PRODUCER PERFORMANCE
来自:http://blog.liveramp.com/2013/04/08/kafka-0-8-producer-performance-2/ At LiveRamp, we constantly ...
- Kafka 0.8 Producer (0.9以前版本适用)
Kafka旧版本producer由scala编写,0.9以后已经废除,但是很多公司还在使用0.9以前的版本,所以总结如下: 要注意包Producer是 kafka.javaapi.producer.P ...
- Kafka 0.10 Producer网络流程简述
1.Producer 网络请求 1.1 Producer Client角度 KafkaProducer主要靠Sender来发送数据给Broker. Sender: 该线程handles the sen ...
- Kafka 0.8 Consumer处理逻辑
0.前言 客户端用法: kafka.javaapi.consumer.ConsumerConnector consumer = kafka.consumer.Consumer.createJavaCo ...
- 【译】Flink + Kafka 0.11端到端精确一次处理语义的实现
本文是翻译作品,作者是Piotr Nowojski和Michael Winters.前者是该方案的实现者. 原文地址是https://data-artisans.com/blog/end-to-end ...
- Kafka设计解析(二十二)Flink + Kafka 0.11端到端精确一次处理语义的实现
转载自 huxihx,原文链接 [译]Flink + Kafka 0.11端到端精确一次处理语义的实现 本文是翻译作品,作者是Piotr Nowojski和Michael Winters.前者是该方案 ...
- Kafka 0.11.0.0 实现 producer的Exactly-once 语义(中文)
很高兴地告诉大家,具备新的里程碑意义的功能的Kafka 0.11.x版本(对应 Confluent Platform 3.3)已经release,该版本引入了exactly-once语义,本文阐述的内 ...
- Kafka 0.11.0.0 实现 producer的Exactly-once 语义(官方DEMO)
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients&l ...
随机推荐
- beat冲刺(5/7)
目录 摘要 团队部分 个人部分 摘要 队名:小白吃 组长博客:hjj 作业博客:beta冲刺(5/7) 团队部分 后敬甲(组长) 过去两天完成了哪些任务 ppt制作中 数据集标注 接下来的计划 制作p ...
- “吃神么,买神么”的第一个Sprint计划(第二天)
“吃神么,买神么”项目Sprint计划 ——5.22(第二天)立会内容与进度 团队组员各自任务: 冯美欣:logo的设计.搜索框的制作,"登陆/注册"的文字链接: 吴舒婷:导航条. ...
- 深入理解JAVA集合系列一:HashMap源码解读
初认HashMap 基于哈希表(即散列表)的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键. HashMap继承于AbstractMap,实现了Map.Cloneab ...
- Appium 服务关键字(转)
来源: https://github.com/appium/appium/blob/master/docs/cn/writing-running-appium/caps.cn.md#appium-服务 ...
- 一次DHCP故障排错
关于DHCP授权,我一直遇到这样一个问题:服务器明明已经在授权列表中了,但是服务就是起不来,看日志会告诉你服务器未授权或者局域网内已有DHCP服务器,但是实际上却并不是这样.现在终于知道原因了,老旧D ...
- laravel中的Contracts, ServiceContainer, ServiceProvider, Facades关系
Contracts, ServiceContainer, ServiceProvider, Facades Contracts 合同,契约,也就是接口,定义一些规则,每个实现此接口的都要实现里面的方 ...
- 解决Linux关闭SSH,终端后运行程序终止问题(包括后台)
问题描述: 每次SSH到服务器上,然后运行了一个自己写的服务端程序,比如 ./myserver.sh ,然后关闭ssh或者终端之后,发现服务不能访问. 简要分析下: 根据 这篇博文 的提示,ss ...
- Java容器深入浅出之PriorityQueue、ArrayDeque和LinkedList
Queue用于模拟一种FIFO(first in first out)的队列结构.一般来说,典型的队列结构不允许随机访问队列中的元素.队列包含的方法为: 1. 入队 void add(Object o ...
- iOS 字符串NSString 的一些常用方法
一.字符串创建 1. NSString *str1 = [NSString new]; 2. NSString *str2 = @"字符串内容"; 二.字符串拼接 1. NSStr ...
- Candies CodeForces - 991C(二分水题)
就是二分暴力就好了 为什么要记下来 呵呵....emm你说为什么... 行吧 好吧 我一直以为我的二分出问题了 原来不是 依旧很帅 统计的时候求的减了多少次 然后用次数乘了mid 这样做会使那个人获 ...