一、前言
引入 MQ
消息中间件最直接的目的:系统解耦以及流量控制
-
系统解耦: 上下游系统之间的通信相互依赖,利用
MQ
消息队列可以隔离上下游环境变化带来的不稳定因素。 -
流量控制: 超高并发场景中,引入
MQ
可以实现流量 “削峰填谷” 的作用以及服务异步处理,不至于打崩服务。
引入 MQ
同样带来其他问题:数据一致性。
在分布式系统中,如果两个节点之间存在数据同步,就会带来数据一致性的问题。消息生产端发送消息到
MQ
再到消息消费端需要保证消息不丢失。
二、怎么保证消息的不丢失
1、生产者
对生产者来说,其发送消息到 Kafka 服务器的过程可能会发生网络波动,导致消息丢失。对于这一个可能存在的风险,我们可以通过合理设置 Kafka 客户端的 request.required.acks 参数来避免消息丢失。该参数表示生产者需要接收来自服务端的 ack 确认,当收不到确认或者超市时,便会抛出异常,从而让生产者可以进一步进行处理。
该参数可以设置不同级别的可靠性,从而满足不同业务的需求,其参数设置及含义如下所示:
- request.required.acks = 0 表示 Producer 不等待来自 Leader 的 ACK 确认,直接发送下一条消息。在这种情况下,如果 Leader 分片所在服务器发生宕机,那么这些已经发送的数据会丢失。
- request.required.acks = 1 表示 Producer 等待来自 Leader 的 ACK 确认,当收到确认后才发送下一条消息。在这种情况下,消息一定会被写入到 Leader 服务器,但并不保证 Follow 节点已经同步完成。所以如果在消息已经被写入 Leader 分片,但是还未同步到 Follower 节点,此时 Leader 分片所在服务器宕机了,那么这条消息也就丢失了,无法被消费到。
- request.required.acks = -1 表示 Producer 等待来自 Leader 和所有 Follower 的 ACK 确认之后,才发送下一条消息。在这种情况下,除非 Leader 节点和所有 Follower 节点都宕机了,否则不会发生消息的丢失。
如上所示,如果业务对可靠性要求很高,那么可以将 request.required.acks 参数设置为 -1,这样就不会在生产者阶段发生消息丢失的问题。
2、Kafka 服务器
当 Kafka 服务器接收到消息后,其并不直接写入磁盘,而是先写入内存中。随后,Kafka 服务端会根据不同设置参数,选择不同的刷盘过程,这里有两个参数控制着这个刷盘过程:
# 数据达到多少条就将消息刷到磁盘
#log.flush.interval.messages=10000
# 多久将累积的消息刷到磁盘,任何一个达到指定值就触发写入
#log.flush.interval.ms=1000
如果我们设置 log.flush.interval.messages=1,那么每次来一条消息,就会刷一次磁盘。通过这种方式,就可以降低消息丢失的概率,这种情况我们称之为同步刷盘。 反之,我们称之为异步刷盘。与此同时,Kafka 服务器也会进行副本的复制,该 Partition 的 Follower 会从 Leader 节点拉取数据进行保存。然后将数据存储到 Partition 的 Follower 节点中。
对于 Kafka 服务端来说,其会根据生产者所设置的 request.required.acks 参数,选择什么时候回复 ack 给生产者。对于 acks 为 0 的情况,表示不等待 Kafka 服务端 Leader 节点的确认。对于 acks 为 1 的情况,表示等待 Kafka 服务端 Leader 节点的确认。对于 acks 为 1 的情况,表示等待 Kafka 服务端 Leader 节点好 Follow 节点的确认。
但要注意的是,Kafka 服务端返回确认之后,仅仅表示该消息已经写入到 Kafka 服务器的 PageCache 中,并不代表其已经写入磁盘了。这时候如果 Kafka 所在服务器断电或宕机,那么消息也是丢失了。而如果只是 Kafka 服务崩溃,那么消息并不会丢失。
因此,对于 Kafka 服务端来说,即使你设置了每次刷 1 条消息,也是有可能发生消息丢失的,只是消息丢失的概率大大降低了。
3、消费者
对于消费者来说,如果其拉取消息之后自动返回 ack,但消费者服务在处理过程中发生崩溃退出,此时这条消息就相当于丢失了。对于这种情况,一般我们都是采用业务处理完之后,手动提交 ack 的方式来避免消息丢失。
在我们在业务处理完提交 ack 这种情况下,有可能发生消息重复处理的情况,即业务逻辑处理完了,但在提交 ack 的时候发生异常。这要求消费者在处理业务的时候,每一处都需要进行幂等处理,避免重复处理业务。
根据我们上面的分析,Kafka 只能做到宕机这个级别。
三、总结
消息可靠性级别,一定是跟业务重要性关联在一起的。我们无法抛开业务本身的重要性来谈可靠性,只能是取一个平衡的值。
根据我的经验来说,除非是金融类或国民级别的应用,否则只需要考虑到服务器宕机的级别就可以了。而如果是金融级别或国民级别的应用,那么就需要考虑到城市毁灭的可靠性级别。但地球毁灭这个,我想谁也不会去考虑吧。
对于大多数的应用,考虑服务器宕机级别的情况下,对于 Kafka 消息来说,只需要考虑如下几个内容即可:
- 生产者。 根据业务重要性,设置好 acks 参数,并做好业务重试,以及告警记录即可。
- Kafka 服务端。 根据业务重要性,设置好刷盘参数即可,一般来说都不需要设置成同步刷盘。
Kafka通过以下几个方面来确保消息不丢失:
-
持久化存储:Kafka使用持久化日志文件来存储消息,即将消息写入到硬盘上的文件中。这样即使发生硬件故障,消息仍然可以从磁盘中恢复。
-
复制机制:Kafka使用复制机制来提供高可用性和故障容忍性。每个主题的分区都可以配置多个副本,其中一个副本作为领导者(leader),负责处理读写请求,其他副本作为追随者(follower),与领导者保持数据同步。如果领导者副本发生故障,Kafka会自动选举一个新的领导者。这样即使发生副本节点故障,消息仍然可以从其他副本中读取和恢复。
-
确认机制:生产者在发送消息后,可以选择等待消息被成功写入并得到确认(ack)后再返回。生产者可以配置不同的确认级别,如0(不等待确认)、1(等待领导者确认)或all(等待所有副本确认)。在配置为all时,只有当所有副本都成功写入消息后,生产者才会收到确认。这样可以确保消息写入的可靠性。
-
批量发送:Kafka允许生产者将多个消息进行批量发送,减少网络开销和延迟。生产者可以将一批消息一起发送到服务器,减少网络传输次数。这样即使发生网络故障,也会有一部分消息成功发送到服务器。
综上所述,Kafka通过持久化存储、复制机制、确认机制和批量发送等方式来确保消息不丢失。这些机制可以提供高可靠性和容错性,并保证消息的可靠传递。