KafkaProducer初始化参数
-
clientId没主动设置clientId时,后台都会生成一个client.id,producer-自增长的数字,producer-1。
-
partitioner决定消息路由到Topic的哪个分区里去的
-
metadata组件,生产端拉取Topic的元数据,包括Topic有哪些分区,分区的Leader位于哪个broker,有一个metadata.max.age参数默认是五分钟,强制重新刷新数据。
-
request.max.size默认是1mb,一次请求最大为1mb。buffer.memory默认是32mb,异步用的缓冲区。max.block.ms用于控制send方法阻塞多久,默认60s。
-
核心组件:RecordAccumulator,缓冲区,负责消息的复杂的缓冲机制,发送到每个分区的消息会被打包成batch,一个broker上的多个分区对应的多个batch会被打包成一个request,batch size(16kb),设置一个linger.ms,如果在指定时间范围内,都没凑出来一个batch把这条消息发送出去,那么到了这个linger.ms指定的时间,比如说5ms,如果5ms还没凑出来一个batch,那么就必须立即把这个消息发送出去。
-
核心行为:始化的时候,直接调用Metadata组件的方法,去broker上拉取了一次集群的元数据过来,后面每隔5分钟会默认刷新一次集群元数据,但是在发送消息的时候,如果没找到某个Topic的元数据,一定也会主动去拉取一次的
-
核心组件:网络通信的组件,NetworkClient,一个网络连接最多空闲多长时间(9分钟),每个连接最多有几个request没收到响应(5个),重试连接的时间间隔(50ms),Socket发送缓冲区大小(128kb),Socket接收缓冲区大小(32kb)
-
核心组件:Sender线程,负责从缓冲区里获取消息发送到broker上去,request最大大小(1mb),acks(1,只要leader写入成功就认为成功),重试次数(0,无重试),请求超时的时间(30s),线程类叫做“KafkaThread”,线程名字叫做“kafka-producer-network-thread”,此处线程直接被启动。
-
核心组件:序列化组件,拦截器组件
集群元数据存储
KafkaProducer在初始化的时候是不会去拉取集群的元数据的,做了一个最最基本的初始化,也就是仅仅把我们配置的那个broker的地址放了进去,在客户端缓存集群元数据的时候,采用了哪些数据结构。
- List<Node>,Kafka Broker节点,一台机器
- unautorhizedTopics,没有被授权访问的Topic的列表,就是kafka是可以支持权限控制的,如果你的客户端没有被授权访问某个Topic,那么就会放在这个列表里。
- Map<TopicParittion, PartitionInfo>,TopicPartition就代表了一个分区,里面就是他的topic的名字,以及他在topic里的分区号;PartitioinInfo,就代表了分区的详细信息,属于哪个topic,分区号,每个分区都有多个副本,Leader在哪个broker上,followers在哪些broker上,ISR列表,都在里面。
- partitionsByTopic,每个topic有哪些分区
- availablePartitionsByTopic,每个topic有哪些当前可用的分区,如果某个分区没有leader是存活的,此时那个分区就不可用了。
- partitionsByNode,每个broker上放了哪些分区。
- nodesById,broker.id -> Node
Producer.Send()
- 回调自定义的拦截器
- 同步阻塞等待获取topic元数据
如果你要往一个topic里发送消息,必须是得有这个topic的元数据的,你必须要知道这个topic有哪些分区,然后根据Partitioner组件去选择一个分区,然后知道这个分区对应的leader所在的broker,才能跟那个broker建立连接,发送消息。调用同步阻塞的方法,去等待先得获取到那个topic对应的元数据,如果此时客户端还没缓存那个topic的元数据,那么一定会发送网络请求到broker去拉取那个topic的元数据过来,但是下一次就可以直接根据缓存好的元数据来发送了
- 序列化key和value
你的key和value可以是各种各样的类型,比如说String、Double、Boolean,或者是自定义的对象,但是如果要发送消息到broker,必须对这个key和value进行序列化,把那些类型的数据转换成byte[]字节数组的形式
- 基于获取到的topic元数据,使用Partitioner组件获取消息对应的分区
- 检查要发送的这条消息是否超出了请求最大大小,以及内存缓冲最大大小
- 设置好自定义的callback回调函数以及对应的interceptor拦截器的回调函数
- 将消息添加到内存缓冲里去,RecordAccumulator组件负责的
- 如果某个分区对应的batch填满了,或者是新创建了一个batch,此时就会唤醒Sender线程,让他来进行工作,负责发送batch
Topic元数据细粒度按需加载及阻塞等待
如果元数据拉取成功,那么version会加一,所以在唤醒后只需要判断当前version是不是大于之前的version就可以判定元数据是否拉取成功。如果超时还没判定成功,则认为是元数据拉取失败。
Sender线程初始化
public KafkaThread(final String name, Runnable runnable, boolean daemon) {
super(runnable, name);
configureThread(name, daemon);
}
private void configureThread(final String name, boolean daemon) {
setDaemon(daemon);
setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
log.error("Uncaught exception in thread '{}':", name, e);
}
});
}
如果没指定分区key是如何对消息负载均衡分发到分区的
counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
初始值是一个随机的integer类型的数字,接下来默认是递增的,一定会保证是一个正整数,就是比如说topic有5个分区,就会对这个递增的数字(23),对topic的分区数量进行取模。
Kafka的内存缓冲区
Kafka实现了一个BufferPool,缓冲池,可以利用它申请内存。
/**
有人的消息是52kb,超出了16kb,分配的那个ByteBuffer就会是52kb,如果对52kb的ByteBuffer进行处理,当deallocate的时候他会直接释放掉这块内存,不去加入到free,让gc掉,avaialbeMemory给加回去
*/
public BufferPool(long