了解Kafka生产者
之前对kafka的整体架构有浅显的了解,这次正好有时间,准备深入了解一下kafka,首先先从数据的生产者开始吧。
生产者的整体架构
可以看到整个生产者进程主要由两个线程进行协调工作,其中一个是主线程,首先由KafkaProducer创建消息,然后通过拦截器、消息序列化器、分区器的处理后,缓存到消息累加器中。另一个是Sender线程,负责从消息累加器中获取消息,并发送至Kafka集群中。
下面来具体分析各个组件的作用,以便加深了解。
-
拦截器: 从名字就可看出是按照一定规则对消息进行过滤。这个具体的规则可以自己去重写kafka中的ProducerInterceptorPrefix类中的onSend方法来实现。之后在KafkaProducer的配置参数 interceptor.classes中指定该拦截器来进行使用。还可以指定多个拦截器,组成拦截链。
-
序列化器:生产者需要使用它将消息对象转化为字节数组发送给kafka集群。消费者端进行反序列化还原消息对象。kafka中自带序列化器StringSerializer可对String、VyteArray、ByteBuffer等等类型进行序列化。kafka支持自定义序列化器,实现Serializer,重写serialize方法,即可实现自定义序列化器。修改配置文件中的value.serializer参数为自定义的类名即可。
-
分区器:顾名思义,分区器就是控制消息最终发往那个分区。若ProducerRecord中指定了partition字段,将发往指定的分区。反之,将根据消息的key来计算partition的值,拥有相同key的消息会被写入同一分区,key为null的消息,将会以轮询的方式发往topic内的各个可用分区。topic中的分区数量不变时,key与分区之间的映射关系保持不变。当topic内的分区数量变化时,该关系将难以保持。
注意: 如果key为null,计算得到的分区号仅为可用分区中的任意一个。当key不为null时,计算得到的分区号是所有分区中的任意一个。
当然,分区器也可以自定义,实现Partitioner中的partition方法即可。同样也是通过配置参数partitioner.class来显式指定这个分区器。
-
消息累加器:RecordAccumulator主要用来缓存消息,使Sender线程可以批量发送消息,进而减少网络传输的前期准备和收尾的资源消耗,提升性能和效率。缓存大小可以通过buffer.memory配置,默认值32MB。主程序生产的消息(ProducerRecord)都会追加到ProducerBatch尾部。消息累加器里面维护了一个Deque
,他们之间的关系是多个ProducerRecord组成了一个ProducerBatch,多个ProducerBatch组成了一个Deque ,这样的结构同样也是为了使消息排列更紧凑,提升效率和性能。 消息在生产端的生命历程
在RecordAccumulator内部还有一个BufferPool,用来实现ByteBuffer的复用,以实现缓存的高效利用,不过BufferPool只针对特定大小的ByteBuffer,其他大小的ByteBuffer不会缓存进BufferPool,这个特定大小由batch.size来指定,默认值为16KB。
当消息进入RecordAccumulator时,会先寻找指定分区所对应的Deque(若无将创建),再从Deque获取尾部的PoducerBatch(若无将创建),查看是否还能写入该消息(ProducerRecord),若可以则写入,否则将创建一个新的ProducerRecord,在创建时评估消息大小是否超过batch.size的大小,若不超过,PoducerBatch大小为该值,超过的话,将以消息大小创建PoducerBatch。换句话说,该PoducerBatch中仅有一条消息。
以batch.size创建的PoducerBatch可以通过BufferPool的管理来进行复用。
Sender从RecordAccumulator中获取缓存的消息之后,会将消息格式<分区,Deque
>转换为<Node,List >,Node为kafka集群中的broker节点。之后Sender还会进一步封装成<Node,Request>的形式,这样就可以将Request请求发往各个Node了。请求在从Sender线程发往Kafka之前还会保存到InFlightRequests中,消息格式为Map<NodeId,Deque >,主要作用是缓存了已经发出去但还没有收到响应的请求。InFlightRequests可以通过配置参数来限制每个连接最多缓存的请求数。配置参数为max.in.flight.requests.per.connection,默认值为5。类似于golang中的channel,通道中的未响应请求数量达到5个,将阻塞,当被缓存的未响应请求收到响应,可以继续添加。 生产端的元数据更新
kafka中的元数据:集群中的topic信息,每个topic有哪些分区,每个分区的leader副本分配在哪个节点上,follower副本分配在哪些节点上,具体哪些副本在AR、ISR等集合中,集群中有哪些节点,控制器节点是哪个等信息。
当生产者客户端发送的消息中缺失需要的元数据时(例如未指定topic),或超过metadata.max.age.ms 时间没有更新元数据都会引起元数据的更新操作。默认值为5分钟。元数据的更新是在客户端内部进行的,对外部使用者不可见,也就是开发者不可感知到元数据的更新。当需要更新元数据时,会先挑选出lwastLoaderNode,然后向这个Node发送MetadataRequest请求来获取具体的元数据信息,该操作由Sender发起,在创建完MetadayaRequest之后,会存入InflightRequests,之后的步骤和发送消息类似,元数据的更新虽然由Sender线程负责更新,但是主线程也需要读取这些信息,这里的数据同步通过synchronized和final关键字来保障。
今天也是了解了大概的原理,每个细节都值得去深挖,故写文自勉。