从KafkaProducer源码学习异步发送,缓冲区管理,NIO编程。

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
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值