kafka实战面经,助力金三银四

254 阅读10分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战

hi,我是新生代农民工L_Denny,上一篇文章说到了kafka的不基本概念,下面就说说个人一些kafka有关的面经,助力金三银四,下面正式开始。

线上kafka配置

  1. 根据平均QPS推断出瞬时顶峰流量,大约是在平均QPS的5到10倍,一般一台物理机可以承载几万的QPS。
  2. kafka启动时broker内部会有很多线程同时运作,一般建议cpu核数要多一点
  3. kafka写消息到磁盘中,需要用到大量的内存,所以把内存分配点
  4. topic副本数最好设置大于等于2,数据保存周期就根据自己业务设定了

线上问题优化

kafka消息有遇到丢失过吗?(消息可靠性)

丢失场景

消息发送端:

  • acks=0:
    • 表示producer不需要等待任何broker确认收到消息的回复,就可以继续发送下一条消息。性能最高,但是最容易丢消 息。大数据统计报表场景,对性能要求很高,对数据丢失不敏感的情况可以用这种。
  • acks=1:
    • 至少要等待leader已经成功将数据写入本地log,但是不需要等待所有follower是否成功写入。就可以继续发送下一条消 息。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。
  • acks=-1或all:
    • 这意味着leader需要等待所有备份(min.insync.replicas配置的备份个数)都成功写入日志,这种策略会保证只要有一 个备份存活就不会丢失数据。这是最强的数据保证。一般除非是金融级别,或跟钱打交道的场景才会使用这种配置。
    • 当然如果 min.insync.replicas配置的是1则也可能丢消息,跟acks=1情况类似。

消息消费端:

  • 如果消费这边配置的是自动提交,万一消费到数据还没处理完,就自动提交offset了,但是此时你consumer直接宕机了,未处理完的数据 丢失了,下次也消费不到了。

Kafka幂等性

Kafka 幂等性是 Producer 端的特性,为了实现生产端幂等性,Kafka 引入了 Producer ID(即PID)和 Sequence Number。

  • PID:每个新的 Producer 在初始化的时候会被分配一个唯一的 PID,这个PID 对用户完全是透明的。
  • Sequence Numbler:对于每个 PID,该 Producer 发送到每个 Partition 的数据都有对应的序列号,这些序列号是从0开始单调递增的。

Broker 端在缓存中保存了这 Sequence Numbler,对于接收的每条消息,如果其序号比 Broker 缓存中序号大于1则接受它,否则将其丢弃。这样就可以实现了消息重复提交了。

幂等涉及的参数是 enable.idempotence,默认为 false,开启需要设置为 ture。

但是,这种只能保证单个 Producer 对于单会话单 Partition 的 Exactly Once 语义。不能保证同一个 Producer 一个 topic 不同的 Partition 幂等。

kafka重复消费问题

出现原因:

  • 发送消息配置了重试机制
    • 比如网络抖动时间过长导致发送端发送超时,实际broker可能已经接收到消息,但发送方会重新发送消息
  • 消费时间过长
    • 消费时间过长,导致超过了kafka的session timeout时间,那么此时就会触发重平衡,此时offset可能还没提交,会导致重平衡后重复消费
  • 强行kill消费者线程、消费者系统宕机、重启等
  • 设置offset自动提交
    • 如果关闭kafka 在close之前,调用consumer.unsubscribe()则有可能部分offset没有提交,下次重启会导致重复消息

处理方案:

  • 尽量提高消费者端的消息处理能力,避免长时间处理超时而导致重平衡,如:考虑异步、多线程等方式
  • 消息幂等性,如:借助redis,每次消费时根据唯一ID去redis尝试set(类似分布式锁)

kafka消息乱序,如何保证有序

  • kafka只能保证partition内是有序的,但是partition间的有序是没办法的。
    • acsk不能设置为0
    • 生产者在写消息时,指定一个partitionKey,这样同一个key便会写到同一个partition中,同一个partition的消息时有序的
    • 当消费者是多线程并发消费消息时,可以通过定义不同的Queue,同一个key的数据写到一个Queue,每一个Queue对应一个线程

kafka消息积压

  • 线上有时因为发送方发送消息速度过快,或者消费方处理消息过慢,可能会导致broker积压大量未消费消息。
    • 此种情况如果积压了上百万未消费消息需要紧急处理,可以修改消费端程序,让其将收到的消息快速转发到其他topic(可以设置很多分 区),然后再启动多个消费者同时消费新主题的不同分区。
  • 由于消息数据格式变动或消费者程序有bug,导致消费者一直消费不成功,也可能导致broker积压大量未消费消息。
    • 此种情况可以将这些消费不成功的消息转发到其它队列里去(类似死信队列),后面再慢慢分析死信队列里的消息处理问题。

kafka实现延时队列

延时队列存储的对象是延时消息。所谓的“延时消息”是指消息被发送以后,并不想让消费者立刻获取,而是等待特定的时间后,消费者 才能获取这个消息进行消费,延时队列的使用场景有很多, 比如 :

  1. 在订单系统中, 一个用户下单之后通常有 30 分钟的时间进行支付,如果 30 分钟之内没有支付成功,那么这个订单将进行异常处理, 这时就可以使用延时队列来处理这些订单了。
  2. 订单完成1小时后通知用户进行评价。

实现思路:

  • 发送延时消息时先把消息按照不同的延迟时间段发送到指定的队列中(topic_1s,topic_5s,topic_10s,...topic_2h,这个一般不能支持任意时间段的延时),然后通过定时器进行轮训消费这些topic,查看消息是否到期,
    • 如果到期就把这个消息发送到具体业务处理的topic中,队列中消息越靠前的到期时间越早,具体来说就是定时器在一次消费过程中,对消息的发送时间做判断,看下是否延迟到对应时间了,如果到了就转发,如果还没到这一次定时任务就可以提前结束了。

不可以在消息中携带延时的时间,这样有可能有阻塞到后面的数据,如:第一条数据要延迟30分钟,第二条延迟10分钟,因为第一条数据没有被消费,便导致第二条消息到时间了也不被消费

kafka消息回溯

如果某段时间对已消费消息计算的结果觉得有问题,可能是由于程序bug导致的计算错误,当程序bug修复后,这时可能需要对之前已消 费的消息重新消费,可以指定从多久之前的消息回溯消费,这种可以用consumer的offsetsForTimes、seek等方法指定从某个offset偏移 的消息开始消费

kafka事务

Kafka的事务不同于Rocketmq,Rocketmq是保障本地事务(比如数据库)与mq消息发送的事务一致性,Kafka的事务主要是保障一次发送 多条消息的事务一致性(要么同时成功要么同时失败),一般在kafka的流式计算场景用得多一点

  • kafka需要对一个topic里的消息做不同的流式计算处理,处理完分别发到不同的topic里,这些topic分别被不同的下游系统消费(比如hbase,redis,es等),这种我们肯定 希望系统发送到多个topic的数据保持事务一致性。

Kafka 事务 API:

  • producer提供了initTransactions,beginTransaction,sendOffsetsToTransaction,commitTransaction,abortTransaction 五个事务方法。

使用事务API注意事项:

  • 需要消费者的自动模式设置为 false,并且不能子再手动的进行执行consumer#commitSync或者consumer#commitAsyc。
  • 生产者配置 transactional.id 属性。
  • 生产者不需要再配置 enable.idempotence,因为如果配置了 transaction.id,则此时 enable.idempotence 会被设置为 true。
  • 消费者需要配置 isolation.level 属性,有两个可选值:“read_committed”,“read_uncommitted”,默认 “read_uncommitted”。

Kafka要实现类似Rocketmq的分布式事务需要额外开发功能。

Kafka 幂等与事务的关系

事务属性实现前提是幂等性,即在配置事务属性 transaction id 时,必须还得配置幂等性;但是幂等性是可以独立使用的,不需要依赖事务属性

参数组合情况:

  • enable.idempotence = true,transactional.id不设置:只支持幂等性。
  • enable.idempotence = true,transactional.id设置:支持事务属性和幂等性
  • enable.idempotence = false,transactional.id不设置:没有事务属性和幂等性的kafka
  • enable.idempotence = false,transactional.id设置:无法获取到PID,此时会报错

kafka高性能的原因

  • 磁盘顺序读写:kafka消息不能修改以及不会从文件中间删除保证了磁盘顺序读,kafka的消息写入文件都是追加在文件末尾,不会写入文件中的某个位置(随机写)保证了磁盘顺序写。
  • 数据传输的零拷贝
  • 读写数据的批量batch处理以及压缩传输
  • Partition机制

数据传输零拷贝原理

零拷贝减少在用户空间(JVM)的性能损耗

kafka数据分区和消费者的关系,kafka的数据offset读取流程kafka内部如何保证顺序,结合外部组件如何保证消费者的顺序?

  • 一个主题(topic)下的一个分区(papartition)只能被同一个消费者组中的某一个消费者消费消息
  • kafka只能保证partition内是有序的,但是partition间的有序是没办法的。
    • 生产者在写消息时,指定一个partitionKey,这样同一个key便会写到同一个partition中,同一个partition的消息时有序的
    • 当消费者是多线程并发消费消息时,可以通过定义不同的Queue,同一个key的数据写到一个Queue,每一个Queue对应一个线程

kafka 的数据存在内存还是磁盘

磁盘 Kafka 最核心的思想是使用磁盘,而不是使用内存,其实盘的顺序读写速度和内存持平。而且Linux 对于磁盘的读写优化也比较多,包括 read-ahead 和 write-behind,磁盘缓存等。

如果在内存做这些操作的时候,一个是JAVA 对象的内存开销很大,另一个是随着堆内存数据的增多,JAVA 的 GC 时间会变得很长。 使用磁盘操作有以下几个好处:

  • 磁盘缓存由Linux 系统维护,减少了程序员的不少工作。
  • 磁盘顺序读写速度超过内存随机读写。
  • JVM 的 GC 效率低,内存占用大。使用磁盘可以避免这一问题。
  • 系统冷启动后,磁盘缓存依然可用。

以上,就是关于kafka面试的全部内容,如果对你有一定帮助的话,点赞留言。 我是新生代农民工L_Denny,我们下篇文章见。