面试题
1.生产者
1.1 消息丢失
- acks=0或者1
- 未启用重试机制
- 对失败消息没做处理
如何防止消息丢失?
-
配置acks=all: 确保消息在分区的所有同步副本(
ISR副本: Leader + Follower
)都成功收到后,才向生产者确认。- 存在的问题: Broker在处理消息时遇到临时故障,例如某个副本不可用,此时会向生产者返回一个错误响应。如果生产者没有重试机制,那么在收到错误响应后,生产者可能会直接丢弃该消息,从而导致消息丢失。
-
启用重试机制: 生产者在遇到临时故障时会自动重试发送消息
- 作用: 主要是为了解决短暂的、可以在短时间内恢复的故障(临时故障、网络波动、副本重启、临时资源不足等)。
- 存在的问题: 当重试次数达到设定的最大重试次数后,如果消息仍然无法成功发送,消息就会被丢弃,从而导致消息丢失。
-
监控失败消息: 生产者可以通过实现
Callback
接口,在消息发送成功或失败时执行特定的逻辑。这种方法允许生产者在消息发送失败后执行自定义的处理逻辑,从而进一步减少消息丢失的风险。
1.2 消息重复发送
- 网络问题: 网络延迟或中断可能导致生产者在发送消息时未收到Broker的确认(ACK),即使Broker已经成功接收并存储了消息。生产者会认为发送失败,并重新发送消息,导致消息重复。
- 生产者重试机制: 当生产者没有收到Broker的确认时,它会根据配置的重试策略自动重试发送。每次重试都可能导致消息被重复发送。
- Broker故障: 如果Broker在消息处理后但在返回确认之前崩溃,生产者未能收到确认,可能会重发消息,导致Broker在恢复后收到重复的消息。
- 重启或故障恢复: 如果生产者在发送消息过程中发生崩溃或重启,可能会在恢复后重发之前的消息,导致重复。
如何防止消息被重复发送?
- 启用幂等性: Kafka生产者的幂等性是通过记录每一条消息的身份信息(
生产者ID
和消息序列号
)来实现的。这个机制确保了即使在网络故障或重试情况下,消息也不会被重复写入到Kafka主题中。 - 使用事务性生产者: 事务性生产者允许多个消息的发送作为一个原子操作,确保这些消息要么都成功,要么都失败。这在处理需要跨多个Topic或分区的事务时尤为重要。在事务中发送消息时,即使在故障情况下,也不会出现部分消息写入的情况,从而避免重复处理。
1.3 消息的序号
- 消息在生产者端,有一个独立序号,即
Sequence Number
,用来实现幂等性,防止消息被重复发送。 - 消息在broker端,有一个独立序号,即
Offset
,帮助消费者跟踪消费进度。
2.消费者
2.1 消息丢失
消息丢失的风险通常与过早提交偏移量有关。
消息丢失的情况:
提前提交偏移量:如果消费者在处理某条消息之前或者处理中途,Kafka自动提交了偏移量(即消费者还未完全处理该消息),但处理过程中发生了异常或失败,那么这条消息的偏移量已经被提交。Kafka会认为这条消息已经被成功消费,导致下次消费者不会重新处理这条消息,从而导致消息丢失。
示例场景:
假设消费者处理一条消息需要10秒钟,但自动提交间隔是5秒。如果消费者在处理消息的第8秒时发生异常,但在第5秒时偏移量已经提交,那么Kafka会认为这条消息已经被处理完毕。在下次消费时,这条消息会被跳过,导致数据丢失。
如何防止消息丢失?
自动提交偏移量可能会导致消息漏消费,而手动提交偏移量
通常能够更精确地控制消费进度,减少漏消费的风险。
2.2 消息重复消费
当启用了自动提交功能(enable.auto.commit=true),Kafka会在后台定期自动提交当前的偏移量。默认情况下,自动提交的间隔时间是5秒(auto.commit.interval.ms=5000)。
重复消费的情况:
如果消费者在处理一批消息时发生异常或崩溃,并且在下次消费者启动时,Kafka会从最后一次成功提交的偏移量开始重新消费。这可能导致那些已经被成功处理但偏移量还未提交的消息被再次消费,从而造成消息的重复消费。
示例场景:
自动提交偏移量的时间到达时,Kafka提交了当前处理的偏移量。然而,此后,消费者处理消息时发生异常并崩溃。重启消费者后,它会从最后提交的偏移量处重新开始,这会导致在崩溃前已经成功处理但偏移量还未更新的消息被重复消费。
自动提交偏移量导致重复消费的情况
- 偏移量提前提交:
- 场景: 在自动提交模式下,Kafka默认会定期提交偏移量(默认每5秒一次)。如果在偏移量自动提交后但消息处理逻辑未完成之前发生异常,消费者在重启后会从已提交的偏移量继续消费,导致未完成处理的消息被重复消费。
- 结果: 未完成处理的消息将被再次消费,导致重复消费。
- 消息处理时间过长:
- 场景: 如果消息处理逻辑非常耗时,可能在处理一条消息的过程中已经自动提交了偏移量。若处理过程中出现异常,后续的消息也会从偏移量处开始重新消费。
- 结果: 消息处理过程中的异常可能导致已处理但未完成的消息被再次消费。
手动提交偏移量导致重复消费的情况
- 处理后但未提交偏移量时异常:
- 场景: 消费者成功处理了消息,但在提交偏移量之前发生异常(如进程崩溃或网络中断)。
- 结果: 由于偏移量未提交,消费者重启后会从上次提交的偏移量重新开始消费,导致重复消费。
- 错误处理逻辑:
- 场景: 手动提交中,如果错误地在消息处理前提交偏移量,或者提交逻辑中存在bug,也可能导致处理的消息未被正确记录,进而导致重复消费。
- 结果: 错误处理可能使已消费的消息未被正确标记为已处理,从而在后续被重复消费。
如何防止消息重复消费?
- 实现幂等性: 无论是自动提交还是手动提交,都应确保消息处理逻辑是幂等的,即重复处理同一条消息不会对系统产生不良影响。幂等性是解决重复消费的根本手段。
- 合理设置自动提交间隔: 如果使用自动提交偏移量,可以根据消息处理的复杂性和时长调整提交间隔(通过配置 auto.commit.interval.ms),以减少重复消费的概率。
- 手动提交偏移量时捕获异常: 手动提交偏移量时,应捕获并处理可能的异常,确保偏移量在消息处理成功后才提交。
- 使用事务性消费: 在支持事务的Kafka版本中,使用事务性消费将消息处理与偏移量提交捆绑在同一个事务中,确保它们的一致性,减少重复消费的风险。
- 批量提交偏移量: 在手动提交的场景中,可以批量处理和提交偏移量,这样可以减少异常发生时的影响范围,降低重复消费的可能性。
- 适当的重试机制: 通过实现消息重试机制,在处理失败时可以在有限的次数内重新尝试处理,这样可以减少处理失败导致的消息重复消费或丢失问题。
2.3 自动提交和手动提交
如果启用自动提交,消费者会定期自动提交当前的offset,提交时间由auto.commit.interval.ms
参数控制。
如果禁用自动提交,消费者需要在处理完消息后显式提交offset。通常,消费者会在处理消息成功后调用commitSync()或commitAsync()来提交 offset。
2.4 kafka的消费者是pull(拉)还是push(推)模式,这种模式有什么好处?
producer将消息推送到 broker,consumer从broker拉取消息。
优点:pull模式消费者自主决定是否批量从broker拉取数据,而push模式在无法知道消费者消费能力情况下,不易控制推送速度,太快可能造成消费者奔溃,太慢又可能造成浪费。
缺点:如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到到达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发送)。
3.消费者组
3.1 什么是消费者组
消费者组由多个消费者实例组成,多个实例共同订阅若干个主题,实现共同消费。
同一个消费组下的每个实例共享同一个消费组ID。当消费某个topic时,每个分区只能被消费者组中的一个消费者消费。消费者与分区的关系是1对1或1对多的。
3.2 消费者位移机制
- Key的结构:
- Group ID:消费者组的唯一标识符。
- Topic:消费者组订阅的主题名称。
- Partition:消费者组订阅的分区编号。
- 这个key的具体格式如下:
GroupID|Topic|Partition
- Value的结构:
- Offset:表示消费者已经提交的位移(offset)。这是消费者在指定的主题分区中已经消费到的最后一条消息的位移。
- Metadata:一些附加信息,可以是空字符串或消费者组用于标识消费状态的自定义信息。
- Commit Timestamp:表示这个位移提交的时间戳。
- Expire Timestamp(可选):用于某些情况下,标记这个位移信息过期的时间戳。
- value的结构通常如下:
{Offset, Metadata, Commit Timestamp, Expire Timestamp (optional)}
为什么只记录GroupID|Topic|Partition
就足够了
- 当Kafka记录
GroupID|Topic|Partition
时,它实际上是在记录这个消费者组对某个主题分区的消费进度。 - Kafka不需要知道具体是哪一个消费者提交了这个位移,因为组内只有一个消费者在消费这个分区。只要知道组和分区,就知道了当前这个分区的消费进度。