深入剖析Kafka生产者:揭秘消息从发送到落地的全过程

        本文将带你了解生产者发送消息的详细流程,通过本文将知道消息的发送流程。生产者,发送消息一方,生产者负责创建消息,然后将其投递到Kafka的topic中,接下来由各消费组内的消费者去消费,生产者发送消息时有三种模式。

消息发送模式

  1. 发后既忘:只管往Kafka中发送消息并不关心消息是否正确到达。某些情况下会造成消息丢失,这种发送方式性能最高,可靠性最差。
  2. 同步发送:可靠性高,要么消息被发送成功,要么发生异常。如果发送异常,则可以捕获并进行相应的处理。不过同步方式的性能会差很多,需要阻塞等待一条消息发送完之后才发送下一条。
  3. 异步发送:一般在send()方法里指定一个Callback的回调函数,Kafka在返回响应时调用该函数来实现异步确认。

序列化

        生产者需要用序列化器(Serializer)把对象转换成字节数组才能通过网络发送给Kafka。而在对侧,消费者需要用反序列化器(Deserializer)把从Kafka中收到的字节数组转换成相应的对象。 

        生产者使用的序列化器和消费者使用的反序列化器是需要一一对应的。如果Kafka提供的序列化无法满足应用需求,可以使用其他通用序列化工具或者自定义序列化器。

拦截器

        Kafka有两种拦截器:生产者拦截器和消费者拦截器。生产者拦截器既可以用来在消息发送前做一些准备工作,比如过滤不符合要求的消息、修改消息内容等,也可以用来在发送回调逻辑前做一些定制化的需求,比如统计类的工作。KafkaProducer中不仅可以指定一个拦截器,还可以指定多个拦截器链。

分区器

        消息在通过send()方法发往broker的过程中,有可能会经过拦截器(Interceptor)、序列化器(Serializer)和分区器(Partitioner)的一系列作用之后才能被真正发往 broker。

        拦截器不是必须的,而序列化器是必须的。消息经过序列化之后就需要确定它发往的分区,如果指定了partition字段,就不需要分区器的作用,因为 partition 代表的就是所要发往的分区号。如果没指定 partition 字段,那么就需要依赖分区器,根据 key 字段来计算。

        Kafka 中提供的默认分区器为 DefaultPartitioner,它实现了 Partitioner 接口,这个接口中有两个方法,源代码如下。分区器的作用就是为消息分配分区器。

/**
 * Compute the partition for the given record.
 *
 * @param topic The topic name
 * @param key The key to partition on (or null if no key)
 * @param keyBytes The serialized key to partition on( or null if no key)
 * @param value The value to partition on or null
 * @param valueBytes The serialized value to partition on or null
 * @param cluster The current cluster metadata
 */
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);

/**
 * This is called when partitioner is closed.
 */
public void close();

        在partition()方法中定义了主要的分区分配逻辑。如果key不为空,那么默认的分区器会对key进行哈希,最终根据得到的hash值来计算分区号,拥有相同key的消息会被写入同一个分区,如果key为空,那么消息将以轮训的方式发往主题内的各个分区。

        在不改变主题分区数量的情况下,key与分区之间的映射可以保持不变。

消息发送流程

        整个生产者客户端有两个线程协调运行,这两个线程分别为主线程和Sender线程(发送线程)。具体流程如下:

  1. 主线程中由KafkaProducer创建消息,然后通过可能的拦截器、序列化器和分区器的作用之后缓存到消息累加器(RecordAccumulator,也称为消息收集器)中。
  2. RecordAccumulator主要用来缓存消息以便Sender线程可以批量发送,进而减少网络传输的资源消耗以提升性能。
  3. sender线程负责从RecordAccumulator中获取消息并将其发送到Kafka中。
  4. 发送到kafka broker中后会将消息通过日志的形式写入磁盘中,完成持久化。

        消息收集器(RecordAccumulator)缓存大小可以通过生产者客户端参数buffermemory配置,默认值为32MB,如果生产者发送消息的速度超过发往服务器的速度,则会导致生产者空间不足,这个时候KafkaProducer的send()方法调用要么被阻塞,要么抛出异常,这个取决于max.block.ms的配置,此参数的默认值为6000,即60s。

        主线程中发送过来的消息被追加到RecordAccumulator的某个双端队列(Deque)中,在RecordAccumulator的内部为每个分区都维护了一个双端队列,队列中的内容就是ProducerBath。消息写入缓存时追加到双端队列尾部;Sender线程读取消息时,从双端队列的头部读取。

        Sender从RecordAccumulator中获取缓存消息之后,会进一步将原本<分区, Deque>的保存形式转变成>的形式,其中Node是Kafka集群的broker节点。

        请求在从Sender线程发往Kafka之前还会保存到InFlightRequest中,InFlightRequest的主要作用是缓存已经发出去但还没有收到响应的请求。

生产者参数

        下面介绍一些生产者端的核心参数。

  • ack:指定分区中必须有多少个副本收到这条消息,生产者才认为写入成功。是消息可靠性和吞吐量的权衡。这个参数有3个值,默认值是1。acks=1时代表leader写入成功即为成功;acks=0代表发送消息后不需要等待任何响应。可达到最大吞吐量;acks=-1发送消息后,ISR中所有副本写入成功才能收到来自服务端的成功响应。
  • max.request.size:用来限制客户端发送消息的最大值,默认值104857B即1MB
  • retrys:配置生产者重试次数,默认为0,即发生异常时不进行重试
  • retry.backoff.ms:设定两次重试之间的间隔,默认值100
  • compression.type:消息的压缩方式,消息压缩可以极大的较少网络传输量、降低网络I/O,提高整体性能,默认值为“none"
  • linger.ms:指定生产者发送ProducerBath之前等待更多消息加入producerBath的时间。增大该值可以提高吞吐量,但会有一定的延迟,默认为0
  • request.timeout.ms:用来设置Producer等待响应的最长时间,默认值30000ms
  • bootstrap.servers:指定Kafka集群所需的broker地址清单
  • key.serializer:消息中key对应的序列化类
  • value.serializer:消息中value对应的序列化类

总结

        消息中间件组件中,相对于消费者俩说,生产者的流程要简单很多,下节将介绍消费者的消费过程。

往期经典推荐

Kafka消息流转的挑战与对策:消息丢失与重复消费问题-CSDN博客

揭秘 Kafka 高性能之谜:一文读懂背后的设计精粹与技术实现-CSDN博客

Redis缓存危机大揭秘:雪崩、击穿与穿透——从理论到实战防御策略_redis缓存雪崩、穿透以及技术预防-CSDN博客​​​​​​​

领航分布式消息系统:一起探索Apache Kafka的核心术语及其应用场景-CSDN博客

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超越不平凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值