上一篇博文Kafka源码阅读(一):Kafka Producer整体架构概述和源码分析(一)介绍了Kafka 生产者发送消息的主要流程和计算分区等机制,接下来这篇博文将对Kafka更新Producer Metadata的机制进行讲解说明。
Metadata
什么是metadata
metadata指Kafka集群的元数据,包含了Kafka集群的各种信息,例如如:
- 集群中有哪些节点;
- 集群中有哪些topic,这些topic有哪些partition;
- 每个partition的leader副本分配在哪个节点上,follower副本分配在哪些节点上;
- 每个partition的AR有哪些副本,ISR有哪些副本;
metadata应用场景
metadata在Kafka中无疑是非常重要的,很多场景中都需要从metadata中获取数据或更新数据,例如:
- KafkaProducer发送一条消息到指定的topic中,需要知道分区的数量,要发送的目标分区,目标分区的leader,leader所在的节点地址等,这些信息都要从metadata中获取。
- 当Kafka集群中发生了leader选举,节点中partition或副本发生了变化等,这些场景都需要更新metadata中的数据。
LeastLoadedNode
LeastLoadedNode
指Kafka集群中所有node中负载最小的那一个node,它是由每个node再InFlightRequests
中还未确定的请求数决定的,未确定的请求越少则负载越小。如上图所示,node1即为LeastLoadedNode
。
更新metadata
当客户端中没有需要使用的元数据信息时,比如没有指定的主题信息或者超过了rnetadata .rnax.age.rns
配置的时间还没有更新元数据就会进行元数据的强制更新。
元数据的更新操作是在客户端内部进行的,对客户端的外部使用者不可见。当需要更新元数据时,会先挑选出LeastLoadedNode
,然后向这个node发送MetadataRequest
来获取具体的元数据信息。
创建完成MetadataRequest
后,该请求也会放入InFlightRequests
中,因此更新元数据与发送消息一样都是由Sender线程负责的,但是主线程也会读取元数据信息,因此这些操作都会通过synchronized
和final
来保证数据一致性。
源码分析
上一篇博文中KafkaProducer发送消息的doSend()
方法中调用了waitOnMetadata()
方法来等待更新元数据,那么Kafka是如何等待更新元数据的呢?接下来就让我们通过阅读源码来分析一下这其中的一些细节。在开始分析源码之前我们先看下Cluster
对象和Metadata
对象中的主要属性,以便更好的理解代码。
Metadata.java
// 该Metadata对象会被主线程和Sender线程共享, 当metadata不包含我们所需要的数据时会发送``MetadataRequest``来同步数据。
// ProducerMetadata继承了Metadata类
public class Metadata implements Closeable {
private final Logger log;
private final Map<String, Long> topics = new HashMap<>(); // topic和过期时间的对应关系
private final long refreshBackoffMs;// retry.backoff.ms: 默认值为100ms,它用来设定两次重试之间的时间间隔,避免无效的频繁重试.
private final long metadataExpireMs;// metadata.max.age.ms: 默认值为300000,如果在这个时间内元数据没有更新的话会被 强制更新.
private int updateVersion; // 更新版本号,每更新成功1次,version自增1,主要是用于判断metadata是否更新
private int requestVersion; // 请求版本号,没发送一次请求,version自增1
private long lastRefreshMs; // 上一次更新的时间(包含更新失败)
private long lastSuccessfulRefreshMs; // 上一次更新成功的时间
private KafkaException fatalException;
private Set<String> invalidTopics; // 非法的topics
private Set<String> unauthorizedTopics; // 未认证的topics
private MetadataCache cache = MetadataCache.empty();
private boolean needUpdate;
private final ClusterResourceListeners clusterResourceListeners; // 会收到metadata updates的Listener列表
private boolean isClosed;
private final Map<TopicPartition, Integer> lastSeenLeaderEpochs; // 存储Partition最近一次的leaderEpoch
}
Cluster.java
// 保存了Kafka集群中部分nodes、topics和partitions的信息
public final class Cluster {
private final boolean isBootstrapConfigured;
private final List<Node> nodes;
private final Set<String> unauthorizedTopics; // 未认证的topics
private final Set<String> invalidTopics; // 非法的topics
private f