美文网首页
关于消息

关于消息

作者: kar_joe | 来源:发表于2020-01-11 19:40 被阅读0次

    生产者根据消息key以及“路由策略”将消息发往对应topic分区,消息数据在broker服务端按照指定备份策略“持久化”保存起来,消费者不断从服务端拉取消息消费,并提交“消费位移”,整个过程需要关注消息的“重复消费”以及“丢失”问题。

    路由策略

    hash、轮询or自己实现
    Java客户端默认的生产者路由策略的实现类org.apache.kafka.clients.producer.internals.DefaultPartitioner。默认策略为:如果指定了partition就直接发送到该分区;如果没有指定分区但是指定了key,就按照key的hash值选择分区;如果partition和key都没有指定就使用轮询策略。而且如果key不为null,那么计算得到的分区号会是所有分区中的任意一个;如果key为null并且有可用分区时,那么计算得到的分区号仅为可用分区中的任意一个。
    注意kafka只保证分区层面的消息有序性,topic层面并不保证。

    消息持久化

    Kafka 只对“已提交”的消息(committed message)做“有限度”的持久化保证。
    Topic分区Partion,每个分区又有多个副本Repication,消息在broker中持久化到日志文件,多个follower副本异步从leader副本中同步消息。
    如何持久化:每一个topic的分区对应一个目录,该分区的消息追加写到该目录下日志文件,用户可以分配日志保留策略和切割策略。由于采用了顺序写,也就是WAL技术(类似mysql里redolog的使用),性能较高。

    消费位移提交

    每个消息在服务端对应一个位移Offset,服务端利用位移查找消息。另外消费者需要将自己消费位移在合适时机提交,下次消费时从上次消费位移之后继续消费,即使有宕机。
    老版本位移提交依赖于zk,由于zk其实不适合频繁写入,所以新版本kafka,把位移管理重新实现;消费者将消费位移信息写入特殊topic :__consumer_offsets。位移主题的 Key 中保存 3 部分内容::<Group ID,主题名,分区号 >

    • 自动提交
      消费者poll时提交上一次的位移,另外也有后台线程每隔xx时间(auto.commit.interval.ms)提交一次位移,保证最小的提交间隔。
    • 手动提交(同步提交、异步提交)
      注意关注CommitFailedException异常
    • 细粒度提交
      commitSync(Map<TopicPartition, OffsetAndMetadata>)

    消息丢失

    1. 生产者异步发送,消息还没发出时就挂掉
      需要关注异常回调
    2. 消息poll后就先提交位移,消息的处理过程中消费者挂掉
      消息处理完成才提交位移(也会引入重复消费)
    3. unclean配置为true,让落后的broker成为leader,丢失消息
      在可用性与数据一致性之间做出抉择,若不允许数据丢失,则unclean配置置为false。
      另外在生产端以及broker服务端合理配置,设置ack=all,并保证replication.factor > min.insync.replicas>1,保证数据一直冗余备份
    4. 老版本broker分区之间数据同步机制导致
      社区在 0.11 版本正式引入了 Leader Epoch 概念,来规避因高水位更新错配导致的各种不一致问题。

    消息重复消费

    1. 网络问题导致生产端未收到broker回复的ack,引起重发
    2. 消费者消费过程中挂掉,未提交消费位移,导致重复消费
    3. 消费reblance,一个正在消费信息时reblance,消息被另一个消费者又消费
      可以采用幂等性producer,并且在消费端业务处理逻辑上也支持幂等性

    消息交付可靠性保障

    消息交付有三种语义:

    1. 最多一次
    2. 至少一次
    3. 精准一次
      kafka默认是至少一次语义,想要实现精准一次,需要保证幂等性。幂等性就是任意多次执行所产生的影响均与一次执行的影响相同。
      kafka提供了幂等性producer以及事务性producer。幂等性producer底层保证了消息去重,但是其只保证了单分区,单会话上的幂等性。kafka用事务性producer解决上述局限,Kafka 自 0.11 版本开始提供了对事务的支持,目前主要是在 read committed 隔离级别上做事情。它能保证多条消息原子性地写入到目标分区,同时也能保证 Consumer 只能看到事务成功提交的消息。
    producer.initTransactions();
    try {
                producer.beginTransaction();
                producer.send(record1);
                producer.send(record2);
                producer.commitTransaction();
    } catch (KafkaException e) {
                producer.abortTransaction();
    }
    

    另外即使kafka保证了消息唯一性,在极端情况下消费端也还会有重复消费问题,消费代码逻辑也需要保证幂等性。
    另外补充一点,RocketMQ也实现了事务,但是其事务更倾向于保证本地其他操作与发送消息操作之间原子性。kafka的事务更倾向于保证多条消息原子性地写入到目标分区。

    需要注意的细节

    • 生产者批量发送、消费者批量拉取
      提升吞吐量,降低网络损耗,但是提高了延时

    • 压缩
      时间换空间,cpu换io;同样提升吞吐量,降低网络损耗,但是提高了延时
      尽量避免生产端与消费端采用不同压缩算法,这样会造成broker端解压再重新压缩
      正常情况下,broker只解压对消息做简单校验。

    • broker端对采用零拷贝技术(从PageCache到socket缓冲区)
      提升broker端处理效率,提升吞吐量、降低延时。
      尽量保证生产者、消费者、broker服务端版本一致,防止零拷贝技术失效。

    相关文章

      网友评论

          本文标题:关于消息

          本文链接:https://www.haomeiwen.com/subject/fzdbactx.html