目录
[1. Kafka怎么避免重复消费](#1. Kafka怎么避免重复消费)
[1.1 什么时候出现重复消费](#1.1 什么时候出现重复消费)
[1.2 如何处理重复消费问题](#1.2 如何处理重复消费问题)
[2. Kafka怎么保证消息不丢失](#2. Kafka怎么保证消息不丢失)
[2.1 Producer](#2.1 Producer)
[2.2 Broker](#2.2 Broker)
[2.3 Consumer](#2.3 Consumer)
[3. Kafka怎么保证消息消费的顺序](#3. Kafka怎么保证消息消费的顺序)
最近面试遇到一些常见kafka问题,所以做一下总结。
1. Kafka怎么避免重复消费
1.1 什么时候出现重复消费
- Kafka 的 broker 上存储的消息都有一个offset作为标记,然后 Kafka 的消费者是通过 offset 这个标记来维护当前已经消费的数据,然后消费者每消费一批数据,kafka broker 就会更新 offset ,避免重复消费问题。
默认情况下,消息消费完成以后会自动提交 offset 避免重复消费。
Kafka Consumer 的自动提交逻辑里面有默认 5 秒的间隔,也就是说在 5 秒之后的下一次向 broker 去获取消息的时候来实现 offset 自动提交。
所以在 Consumer 的消费过程中,应用程序强制被 kill 掉或者宕机的时候,可能会导致 offset 没有提交,从而会产生重复消费问题。
- 除此之外,还有另外一种情况也会出现重复消费。在 Kafka 里面有 partition balance 机制,就是把多个 partition 均衡分配给多个Consumer,那么 Consumer 端会从分配的 partition 里面去消费消息,如果 consumer 在默认的5 分钟以内没办法处理完这一批消息的时候,就会触发 Kafka 的 rebalance 的机制,从而导致 offset 自动提交失败。而在 rebalance 以后, Consumer 端还是会从之前没有提交的 offset 位置开始去消费,从而导致重复消费问题。
1.2 如何处理重复消费问题
- 在这样背景下,我们可以提高Consumer 处理性能去避免触发balance。
比如说我们可以用异步的方式来处理消息,缩短单个消息处理时长。或者可以调整消费一批消息处理的时间限制,还可以减少一次性从 broker 上获取的消息条数。
- 我们还可以针对每一条消息去生成一个 MD5 值,然后保存在数据库或者 Redis 里面。
那么在处理消息之前,我们先去数据库或者 Redis 里面去判断是否已经存在相同的MD5,如果存在,那么我就不需要去再处理了,其原理就是利用幂等性的思想来实现。
2. Kafka怎么保证消息不丢失
Kafka 的整个架构是由 Producer Consumer 和 Broker 来组成。所以对于 Kafka 如何去保证消息不丢失这个问题,可以从三个方面来考虑和实现。
2.1 Producer
首先是 Producer 需要去确保消息能够到达broker,并且实现消息的存储,在这个层面上有可能会出现网络问题,导致消息发送失败。所以针对 Producer 可以通过两种方式来避免消息丢失。
Producer 默认是异步发送消息的,这种情况下需要确保消息是发送成功。
这里有两个方法,第一个是把异步发送改成同步发送,那么这样的话 Producer 就能够知道消息发送的结果。第二种是添加异步回调的函数来监听消息的发送结果,如果发送失败,可以在回调中去进行重试,Producer 本身提供了一个重试参数recharge,如果因为网络问题或者 broker 故障导致发送失败,那么 Producer 会自动重试,
2.2 Broker
Broker 需要确保 producer 发送过来消息是不会丢失的,也就是说只需要去把这个消息持久化到磁盘就可以了。但是 Kafka 为了提升性能,采用了异步批量刷盘机制,比如说按照一定的消息量和时间间隔去刷盘,这个动作是由操作系统来调度的,所以如果在刷盘之前系统崩溃了,就会导致数据丢失。
Kafka 并没有提供同步刷盘的机制,所以针对这个问题需要通过 partition 的副本机制和 Acks 机制来解决。
Partition 的副本机制,是针对每个数据分区的高可用策略,每一个 partition 副本会包含唯一的 leader 和多个 follower 专门去处理事务类型的请求,而 follower 去负责同步 leader 的数据。
那么在这个机制的基础上, Producer 可以去设置 Acks 参数,去结合副本机制来共同保障数据的可靠性。
Acks 参数:
-
Acks = 0,表示 Producer 不需要等待 Broker 的响应,就认为消息就发送成功了,消息可能会丢失。
-
Acks = 1,表示 Broker 中的 leader partition 收到消息之后不等待其他的 follower partition 的同步,就给 producer 返回了确认。这种情况下,假设 leader partition 挂了,就会存在数据丢失。
-
Acks = -1,表示 Broker 中的 leader partition 收到消息之后,并且等待 ISR 列表中的所有 follower 同步完成,再去给 Producer 返回确认,那么这样的配置可以保证数据的可靠性。
2.3 Consumer
Consumer 需要保证必须要能够消费的消息。
实际上只要 Producer 和 Broker 的消息可靠性得到了保障,那么Consumer 是不太可能出现消息无法消费的问题的,除非是 Consumer 没有消费完这个消息就已经提交了offset,但是即便是出现这样的情况,我们也可以通过重新调整 offset 的值来重新消费。
3. Kafka怎么保证消息消费的顺序

Kafka 用到了 partition 的分区机制来去实现消息的物理存储,也就是说在同一个 topic 里面维护多个 partition 来去实现消息分片。那么Producer在发送消息的时候会根据消息的 key 来进行取模,来决定把当前的消息存储到哪一个 partition ,而且消息是按照先后有序的去存储在 partition 里面。
在这种情况下,假设一个 topic 里面有三个partition,而消息正好被路由到三个独立的 partition,然后Consumer有三个消费者去通过 balance 机制去分别指派了对应的消费分区。因为消费者是完全独立的一个网络节点,所以可能会出现消息的消费顺序不是按照发送的顺序,导致消息消费乱序的问题。
所以我们针对这个问题一般的解决方法就是自定义消息分区的路由算法,然后把指定的 key 都发送到同一个 partition 里面,然后我们去指定一个消费者,专门去消费某一个分区的数据,这样的话就保证了局部消息的顺序消费。
另外在有些设计方案里,Consumer 会采用异步线程的方式来消费数据,以提高消息的处理效率。那么这种情况下,因为每一个线程的消息处理效率是不同的,所以即便是采用了单个分区的存储和消费,也可能会出现无序访问的问题,那么针对这个问题的解决办法就是在Consumer 采用阻塞队列,把获取到的消息先保存到组织队列里面,然后采用一个异步线程,从组织队列里面去获取消息来进行消费。