kafka客户端版本:kafka-clients-2.6.0.jar
问题背景
kafka主节点宕机,排查后有一台客户端存在数据丢失问题,引发的一系列排查与疑问
- 为什么mq会丢失?
- 为什么只有一台服务的kafka客户端出现了问题?
- 客户端metadata更新机制是什么?
问题排查
为什么mq会丢失
检查相关报错日志如下
org.apache.kafka.common.errors.TimeoutException: Expiring 78 record(s) for events-0:120000 ms has passed since batch creation
通常表示,Kafka生产者客户端在将消息发送到指定分区时,因超过了配置的超时时间而导致消息过期。
错误的含义:
- Batch超时:Kafka生产者客户端会将多条消息(
record(s)
)放入一个批次(batch)中进行发送。为了提高性能,Kafka会等待批次达到一定的大小或超时时间后再发送。如果等待时间超过了配置的时间限制,这些消息会被标记为超时并丢弃。这条错误信息说明有78条消息等待了120秒(120,000 ms),但未能发送成功,因此被过期处理。 - 原因可能包括: - 网络延迟或网络问题:客户端和Kafka broker之间的网络传输问题导致消息无法及时送达。- Kafka broker负载过高:broker可能由于高负载无法及时响应客户端的请求,导致消息超时。- 分区不可用:目标分区
events-0
可能暂时不可用或处于恢复过程中。- 生产者配置问题:生产者配置的delivery.timeout.ms
时间过短,或linger.ms
配置较高,导致客户端等待时间过长。- ACK机制:如果生产者配置了高等级的acks
(例如all
),等待所有副本确认可能会导致超时。
解决思路:
- 调整配置: - 增加
delivery.timeout.ms
参数(默认为120秒),适当延长消息的发送超时时间。- 降低linger.ms
和batch.size
,确保消息能更快发送,而不必等待批次达到一定大小。 - 监控Kafka集群状态: - 检查broker的负载、分区健康状态、网络延迟等,确保Kafka集群能够及时处理生产者请求。
- 网络优化: - 检查客户端与Kafka broker之间的网络,减少可能的网络延迟或丢包问题。
发现生产mq发送超时丢失后排查了发送的代码如下
org.apache.kafka.clients.producer.KafkaProducer#send(org.apache.kafka.clients.producer.ProducerRecord<K,V>,org.apache.kafka.clients.producer.Callback)
Producer端配置
request.required.acks
acks
配置的值及含义:
- acks = 0: - 描述:生产者在发送消息后不等待任何确认,消息会立即认为已成功发送。- 效果: - 生产者不会等待 broker 的响应。- 性能最高,但可靠性最低,因为消息可能未真正写入Kafka就被认为发送成功,可能会丢失消息。- 适用场景:当对吞吐量要求非常高,且允许丢失消息的场景。
- acks = 1(默认值): - 描述:生产者发送消息时,等待消息的主副本确认成功写入后,认为消息发送成功。- 效果: - 生产者会等待主副本的响应(主副本所在的broker写入成功),但不等待副本确认。- 如果主副本成功写入但副本发生故障,可能导致消息丢失(主副本故障后尚未同步到副本)。- 适用场景:在性能和可靠性之间取得平衡的应用场景。
- acks = all 或 acks = -1: - 描述:生产者发送消息时,等待所有副本都成功确认写入后,认为消息发送成功。- 效果: - 生产者会等待所有同步副本(包括主副本和所有副本)的响应。- 这是最高级别的持久性设置,确保消息不会丢失,除非所有副本同时失败。- 适用场景:对消息可靠性要求极高的场景,可能会牺牲一定的性能来保证消息的持久性。
小结:
- 性能:
acks=0
性能最好,acks=1
性能居中,acks=all
性能最差但可靠性最高。 - 可靠性:
acks=all
提供最高的可靠性,acks=0
最不可靠。
Kafka生产者的
retries
参数用于设置当消息发送失败时,生产者应该重试的次数。这个参数与消息的可靠性和生产者的容错性有关,尤其在面对网络波动、临时的Kafka broker故障等情况下。
retries
参数的详细含义:
- 定义:
retries
表示生产者在发送消息失败时,可以尝试重发该消息的最大次数。 - 默认值:
2147483647
(即几乎无限次重试)。 - 使用场景:当生产者遇到临时的失败情况(如网络中断、Kafka broker的短暂不可用、分区不可用等),通过设置
retries
参数,生产者会尝试重新发送消息,而不是直接放弃发送。
工作机制:
- 发送失败:当消息发送失败时,生产者不会立刻抛出异常或丢弃消息,而是进入重试机制。
- 重试次数:生产者会根据
retries
参数指定的次数,最多重试发送消息的次数。 - 重试间隔:每次重试之间的时间间隔由
retry.backoff.ms
参数控制,默认值是100毫秒。它表示在两次重试之间,生产者会等待一定的时间。
注意事项:
- 幂等性(Idempotence):在Kafka的幂等性机制中,如果启用了幂等性(通过
enable.idempotence=true
),生产者在重试时会确保消息的去重和顺序性,避免重复消息。如果未启用幂等性,消息在重试时可能会被发送多次,导致重复消息。 - 重试对消息顺序的影响:在重试时,Kafka生产者默认情况下不保证消息的顺序。例如,如果第一条消息失败并进入重试,而第二条消息成功,可能导致消息顺序错乱。
- 配合
**acks**
使用:retries
参数通常配合acks
参数使用,尤其是在acks=all
时,可能会因为某些副本的临时不可用导致发送失败,此时retries
可以让生产者重试发送,等待副本恢复。
示例:
**retries = 3**
:如果生产者发送一条消息失败,它将最多尝试重新发送三次。如果三次重试之后仍然失败,则生产者将抛出异常。
小结:
- 提升可靠性:
retries
参数通过在失败时重试发送消息,可以提升生产者的容错能力,尤其适用于短暂的网络故障或Kafka集群不可用的情况。 - 避免过度重试:适当设置重试次数,避免过度重试占用系统资源。
retries
和超时机制的关系:
- 重试次数(
**retries**
): -retries
定义了生产者在遇到失败时,最多可以尝试重试的次数。例如,设置为3表示最多重试3次。- 每次失败后的重试是在生产者的重试机制中处理的,这些重试的次数和间隔(由retry.backoff.ms
控制)是在重试时间内进行的。 - 超时时间(
**delivery.timeout.ms**
): -delivery.timeout.ms
参数定义了生产者在认为消息发送失败之前等待的总时间,包括所有重试的时间。- 如果在delivery.timeout.ms
指定的时间内,生产者没有收到成功的确认(或超过了所有重试次数),则会丢弃消息,并抛出TimeoutException
。
如何协同工作:
- 发送消息:生产者发送消息,并根据
acks
设置等待确认。 - 超时:如果在指定的超时时间内(由
delivery.timeout.ms
控制),生产者没有收到确认,或者所有重试次数都用尽,但消息仍未成功发送,生产者会超时并丢弃消息。 - 重试:每次发送失败后,生产者根据
retries
参数进行重试。每次重试之间的等待时间由retry.backoff.ms
控制。
Broker端配置
min.insync.replicas
最少写入成功ISR多少个副本才算”已提交“,2 // 建议>=2
**min.insync.replicas=1**
:在副本集中的至少1个副本需要同步成功,才能将消息标记为成功。对于分区副本数为2的情况,这意味着如果主副本成功写入,消息将被认为已成功。这个设置提供了最低的保证。**min.insync.replicas=2**
:在副本集中的至少2个副本需要同步成功,才能将消息标记为成功。这要求至少两个副本都成功接收到消息,才能保证数据持久性。这在分区副本数为3的情况下较为常见。
结论
- 采用异步方式发送mq,broker宕机后发送必然失败,两分钟后超时,应用代码中没有针对该场景做好兜底策略,因此导致了消息的丢失
那么引入了下一个问题
为什么两分钟都没有重试成功?
选主过程通常很快就可以完成,难道是一直没有选主成功?不是,因为只有一台机器丢失了消息,其他机器很快就恢复了。
那么引入了下一个问题
为什么只有一台服务的kafka客户端出现了问题?
排查发现所有机器都及时的收到了下面的日志,也就是说发现了网络异常,触发请求服务端更新metadata,获取到最新的metadata后能与新选举出来的master进行建立连接通信,mq在超时时间内重试发送成功
[Producer clientId=producer-4] Received invalid metadata error in produce request on partition events-0 due to org.apache.kafka.common.errors.NetworkException: The server disconnected before a response was received.. Going to request metadata update now
只有这一台机器没有检索到该日志
该错误通常会在尝试向Kafka发送消息时感知到。以下是详细的解释:
错误发生的上下文
- 发送消息时: - 触发时机:当生产者尝试发送消息到Kafka时,它需要通过网络与Kafka broker通信,并根据当前的元数据(如分区信息)将消息发送到适当的分区。- 错误出现:如果在发送消息过程中,网络连接出现问题,或者生产者的元数据不再有效(如broker或分区发生变化),就可能触发类似的错误。
- 错误类型: - NetworkException:表示在发送请求过程中,网络连接出现问题,比如连接被意外断开,导致生产者未能收到broker的响应。- Invalid Metadata Error:表明生产者发现其元数据已过时,可能由于broker的变化或分区的重新分配等原因。
错误的检测和处理
- 感知时机:生产者在发送消息时检测到网络问题或元数据问题时,会报告相关的异常。通常,在消息发送的过程中,生产者会发现连接问题或元数据问题。
- 处理方式: - 自动重试:生产者会根据
retries
参数自动重试消息发送,并在需要时刷新元数据。- 元数据更新:生产者会自动触发元数据更新请求,以获取最新的broker和分区信息,从而解决由于元数据过时导致的问题。
也就是说最长客户端要5分钟才能感知到新选举master的信息。为了进一步确认该逻辑,排查机器感知到新master信息的时间点
要监控 Kafka broker 重新选举和所有客户端感知到的时间点,可以查看以下几种关键日志和监控指标:
排查客户端感知到新master信息的时间点
Kafka Broker 日志
Broker 日志关键字:
**Leader election**
:用于标识分区领导者选举的开始和结束。日志中通常会记录分区、当前领导者、新的领导者等信息。例如:
[Broker-1] [Partition: 0] Leader election for partition [my-topic-0] completed. New leader: Broker-2
**Electing new leader**
:表示开始进行新的领导者选举。例如:
[Broker-1] Electing new leader for partition [my-topic-0]
日志内容:
- 在
server.log
中,您可以找到有关领导者选举的详细信息,包括选举开始、完成的时间点,以及选举结果。
2. Kafka Producer 和 Consumer 日志
Producer 日志关键字:
**Metadata refresh**
:生产者日志中会记录元数据刷新的事件。成功刷新后,日志中会显示更新的信息。
[Producer] Updated metadata for topic [my-topic] partition [0]
Consumer 日志关键字:
**Partition assignment**
:消费者日志中会记录分区分配的事件。如果消费者重新获得分区,日志中会记录分配的时间点。
[Consumer] Assigned partitions [my-topic-0]
日志内容:
- 在生产者和消费者的日志中,您可以找到关于元数据刷新和分区分配的时间点。通过查看这些日志,可以推断出客户端感知到领导者选举的时间点。
3. Kafka Metrics
Broker Metrics:
**kafka.controller:type=KafkaController,name=ActiveControllerCount**
:用于监控当前活动的 Kafka Controller 的数量。Leader 选举期间,Kafka Controller 的变化会反映在这个指标中。
Producer Metrics:
**record-send-rate**
和**record-error-rate**
:通过观察生产者的发送速率和错误率,可以推断出生产者在领导者选举期间的行为变化。
Consumer Metrics:
**lag**
和**commit**
:监控消费者的延迟和提交情况。领导者选举可能会影响消费者的延迟和提交频率。
4. 监控工具
Prometheus 和 Grafana:
- 使用 Prometheus 和 Grafana,可以设置监控仪表板,监控 Kafka 集群的健康状况,包括领导者选举的频率和时机。
- 设置指标监控,如
kafka_server_replicamanager_leader_election_rate
和kafka_controller_leader_election_rate
,来观察领导者选举的频率和延迟。
Kafka Manager 和 Confluent Control Center:
- 这些工具提供了可视化的Kafka集群状态监控,包括领导者选举的相关信息和客户端的感知情况。
结论
没有看到相关kafka日志,因为很多日志是debug级别,生产没有打开这么详细的日志级别。查看相关官方文档找到一个解释:https://cwiki.apache.org/confluence/display/KAFKA/KIP-601%3A+Configurable+socket+connection+timeout+in+NetworkClient
KIP-601 引入的可配置
socket.connection.timeout.ms
可能是导致只有一台机器没有收到更新 metadata 日志的原因之一。这个改进允许客户端配置网络连接超时时间,以提高与 Kafka broker 之间的连接稳定性和可靠性。
KIP-601 关键点
KIP-601 允许客户端配置
socket.connection.timeout.ms
,这个设置控制了 Kafka 客户端在建立和维护网络连接时的超时时间。该超时设置用于:
- 连接建立:在尝试建立与 Kafka broker 的连接时,如果连接请求未在指定时间内完成,则会超时。
- 网络断开:如果在连接期间发生网络断开,并且超时时间内未能恢复连接,也会导致超时。
在 Kafka 客户端中,
socket.connection.timeout.ms
的默认值为 30,000 毫秒(即 30 秒)。
可能的影响
如果一台机器的网络连接超时配置(
socket.connection.timeout.ms
)较短,可能会导致以下情况:
- 超时断开:机器可能在尝试连接 Kafka broker 时因为超时断开,导致无法成功接收或更新 metadata。
- 日志丢失:由于网络问题,这台机器可能错过了元数据更新的日志或更新事件。
小结
KIP-601 的引入确实可能导致与 Kafka broker 连接不稳定的情况,如果
socket.connection.timeout.ms
设置不当,可能会影响 metadata 的更新。通过检查和调整相关配置、对比日志和网络状况,可以帮助确认并解决这类问题。
kafka客户端metadata刷新的机制
Kafka客户端的元数据刷新机制涉及到生产者和消费者如何更新和维护关于Kafka集群的分区和副本信息。以下是Kafka客户端元数据刷新的机制和工作流程:
1. 元数据刷新触发条件
- 生产者触发元数据刷新: - 首次连接:当生产者启动时,会向Kafka集群请求元数据,以获取所有分区和副本的信息。- 发送消息时遇到错误:如果生产者在发送消息时遇到如
InvalidMetadataException
或UnknownTopicOrPartitionException
错误,通常表示当前元数据已经过时。生产者会自动请求最新的元数据。- 定期刷新:生产者会周期性地请求元数据,以确保其信息是最新的。这个时间间隔由metadata.max.age.ms
参数控制。 - 消费者触发元数据刷新: - 首次连接:消费者在启动时向Kafka集群请求元数据,以获取分区和副本的信息。- 组协调器变化:当消费者群组的协调器发生变化,或消费者重新加入群组时,消费者会请求最新的元数据。- 定期刷新:消费者也会周期性地刷新元数据,确保分区分配信息的正确性。
2. 元数据刷新流程
- 发起元数据请求: - 客户端(生产者或消费者)向Kafka集群中的一个broker发起元数据请求,请求最新的集群和分区信息。
- Kafka broker处理请求: - Kafka broker接收到元数据请求后,返回最新的元数据,包括分区、副本和leader的信息。
- 更新本地缓存: - 客户端将从broker接收到的最新元数据更新到本地缓存中。- 在生产者中,更新后的元数据会被用来确定消息的目标分区;在消费者中,更新后的元数据会被用来更新分区的消费位点信息。- Kafka客户端的本地缓存元数据(metadata)是有效的,但仅限于在集群的元数据没有变化的情况下
- 处理错误和重试: - 如果客户端在请求元数据时遇到错误(如网络问题、broker故障),客户端会进行重试,直到成功获取到元数据。- 错误处理和重试策略由客户端的配置参数控制(如
metadata.fetch.timeout.ms
)。
3. 配置参数
**metadata.max.age.ms**
: - 控制生产者和消费者多久请求一次新的元数据。默认值通常为5分钟(300,000毫秒)。- 可以通过调整这个参数来改变元数据刷新的频率。**metadata.fetch.timeout.ms**
: - 指定请求元数据时的超时时间。如果在这个时间内没有收到响应,客户端会重试请求。**request.timeout.ms**
: - 控制客户端等待Kafka broker响应的最大时间,包括元数据请求的超时。
4. 处理动态变化
- 分区重新分配:如果Kafka集群中发生了分区重新分配或broker故障,客户端可能会在尝试发送消息时发现元数据过时,从而触发元数据刷新。
- 集群拓扑变化:集群拓扑的变化(如broker的增加或删除)也会导致客户端需要更新元数据,以确保消息能正确地发送到新的分区或broker。
小结
Kafka客户端的元数据刷新机制确保了生产者和消费者能够获取最新的集群和分区信息。通过定期刷新和在遇到元数据错误时请求更新,Kafka客户端能够保持信息的最新性,从而保证消息的正确发送和消费。配置参数如
metadata.max.age.ms
和
metadata.fetch.timeout.ms
允许调整元数据刷新的频率和超时设置,以适应不同的应用需求。
metadata更新感知延迟
当 Kafka 的 leader 副本(例如
broker-0
)宕机后,系统会进行重新选举,选出新的 leader 副本(例如
broker-1
)。在这种情况下,客户端是否能够立即感知到重新选举,取决于以下几个方面:
1. Metadata 更新机制
- 当一个 broker 宕机或 leader 选举发生时,客户端依赖metadata 更新机制来感知到集群拓扑变化。客户端会定期刷新 metadata(默认周期通常为5分钟)。如果 metadata 信息已过期或在执行操作时发现与 broker 连接失败,客户端会立即请求更新 metadata。
- Kafka Producer 和 Consumer 在尝试与旧的 leader 副本交互时,如果发现连接断开,会触发 metadata 刷新,更新新 leader 的信息。此时,客户端会感知到新的 leader。
2. 连接超时与错误回调
- 当生产者或消费者无法连接到宕机的 broker(如
broker-0
),会在一定时间内重试,并最终触发连接超时错误。这时 Kafka 客户端会通过回调机制(如生产者的callback
或异常处理)感知到错误,重新尝试连接新的 leader。 - 生产者的回调机制允许开发者在发送消息时提供一个
callback
函数,一旦消息发送失败(如因为 leader 副本失效),该回调函数会被触发,客户端可以根据错误类型进一步处理,例如重新发送消息或触发 metadata 刷新。
3. 感知延迟
- 由于 metadata 刷新是周期性的,如果没有主动发生消息发送失败或 consumer 读取失败的情况,客户端的感知通常会有一定延迟,直到下一次 metadata 刷新。
- 为了加快感知速度,Kafka 提供了
metadata.max.age.ms
参数,用于配置 metadata 刷新的时间间隔,设置较短的刷新间隔可以减少这种延迟。
4. Broker 重新选举后的通知
- Kafka 并没有提供明确的回调来直接通知客户端 broker 已经完成重新选举,但客户端会通过与 broker 的连接断开以及 metadata 更新过程间接得知选举完成。
- 客户端通过刷新 metadata 或遇到网络异常时,会尝试重新获取 partition 的 leader 信息,从而感知到新的 leader。
小结:
- 客户端在 broker 宕机后,不能立即感知重新选举完成,而是依赖 metadata 刷新机制和连接断开的回调来间接感知。
- 回调机制(如生产者的
callback
)可以在消息发送失败时提醒客户端问题,但不直接用于选举感知。 - 若需要更快速地感知 leader 变化,可以通过调短
metadata.max.age.ms
来缩短 metadata 刷新的时间。
总结
案例中丢失消息的原因
- 客户端版本为2.6.x,客户端机器可能在尝试连接 Kafka broker 时因为超时断开,导致无法成功接收或更新 metadata
- 由于客户端没有及时感知到broker重新选举的metadata数据,一直尝试向宕机的broker发送mq,在2分钟后超时,最终失败导致部分mq消息丢失
如何解决该问题场景?
升级版本至2.7.0(含)之上。官方如何修复的该问题
KIP-601 修复了两个主要问题:
- 可配置的连接超时:引入了
socket.connection.setup.timeout.ms
参数,允许客户端配置连接建立的超时时间。如果客户端在设定的时间内无法与 broker 建立连接,它将超时并进行失败处理。 1. socket.connection.setup.timeout.ms:客户端等待建立初始套接字连接的时间。如果在超时之前未建立连接,则网络客户端将关闭套接字通道。默认值为 10 秒。2. socket.connection.setup.timeout.max.ms:客户端等待建立初始套接字连接的最大时间。每次连续连接失败后,连接建立超时时间将呈指数增长,直至达到此最大值。为了避免连接风暴,将对退避应用 0.2 的随机化因子,从而产生一个介于计算值以下 20% 和以上 20% 之间的随机范围。默认值为 127 秒。 - 优化节点选择:在没有现有连接通道时,客户端会选择那些失败次数较少的节点,以优化重新连接的尝试。
修复该问题后kafka就安全可靠了吗?在某些场景依然会存在问题。要想保障消息的可靠,可以采用两个常见的可靠性保障方案
客户端可靠性
- 同步发送,并且感知到失败时写入DB,定时任务进行兜底重试,保证最终一致性
- 异步发送,增加redis缓存唯一key,成功接收到callback时移除redis缓存,定时任务兜底检查redis缓存中失败的mq进行重试
broker可靠性
- 采用高等级配置acks=ALL
kafka客户端按照批次发送消息会丢失消息吗?
Kafka 的批次消息(batch)在客户端宕机时可能会导致消息丢失,但具体取决于几个因素,包括客户端配置和 Kafka 的容错机制。
影响因素:
- 客户端的批次处理: - 生产者在发送消息时,通常会将多条消息打包成一个批次(batch),然后一起发送给 Kafka broker。如果客户端在批次发送完成之前宕机,那么这个批次中的消息还未发送到 Kafka,可能会丢失。
- 客户端的重试和幂等性: - 重试机制的局限性:Kafka 的重试机制(通过
retries
参数)可以处理网络故障或临时 broker 不可用的情况,但并不能应对客户端宕机。如果客户端在批次消息尚未发送或部分消息未确认(ACK)时宕机,客户端重启后没有保留重试上下文,这些消息是无法被重新发送的,因为客户端已丢失相关的内存状态。- 幂等性生产者:如果启用了enable.idempotence=true
,Kafka 可以保证每条消息只被 broker 接收一次,即使客户端重启后重试,消息不会重复发送。但这并不能防止客户端宕机导致的批次消息丢失,特别是在消息还没有被发送到 Kafka 的情况下。 - 事务性生产者: - 如果使用了事务性生产者(
transactional.id
配置),Kafka 可以提供更高的可靠性,确保整个事务内的所有消息要么成功提交,要么完全回滚。如果生产者在事务未完成前宕机,Kafka 会回滚未提交的事务,防止部分消息提交。这可以防止一部分消息丢失,但仍然无法恢复那些从未发送到 broker 的消息。 - acks 配置: -
acks=all
可以确保消息被写入到所有同步副本中后才认为发送成功。如果客户端在发送消息前宕机,消息没有到达 broker,自然也无法通过副本机制进行恢复。因此,未成功发送的批次消息依然会丢失。
小结:
Kafka 的批次消息如果客户端在消息发送之前宕机,未发送的消息会丢失。为了减少此类情况的发生,可以启用幂等性、事务性和合理的重试机制,但这些措施主要是为了避免重复和部分消息丢失,不能完全避免宕机时未发送批次的丢失。
版权归原作者 太阳伞下的阿呆 所有, 如有侵权,请联系我们删除。