kafka如何做到消息不丢失? 具体需要Producer端,Broker端,Consumer都做一些工作才能保证消息一定被消费,即,
- 生产者不少生产消息;
- 服务端不丢失消息;
- 消费者也不能少消费消息
三种语义
At Most Once 语义:
- 将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,即 At Most Once 语义。
- 此语义可以保证数据不重复,但是不能保证数据不丢失。
At Least Once 语义:
- 将服务器的 ACK 级别设置为-1(all),可以保证 Producer 到 Server 之间不会丢失数据,即 At Least Once 语义。
- 此语义可以保证数据不丢失,但是不能保证数据不重复。
Exactly Once 语义:
At Least Once + 幂等性 = Exactly Once
-
幂等性:所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据, Server 端都只会持久化一条。
-
要启用幂等性,只需要将 Producer 的参数中 enable.idempotence 设置为 true 即可(此时 ack= -1)。 Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。原理:开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时, Broker 只会持久化一条。
-
但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区、跨会话的 Exactly Once。(也就是说它只解决单次会话、单个分区里的消息重复问题)
生产者不少生产消息
使用带回调的发送消息的方法。 如果消息没有发送成功,那么Producer会按照配置的重试规则进行重试,如果重试次数用光后,还是消息发送失败,那么kafka会将异常信息通过回调的形式带给我们,这时,我们可以将没有发送成功的消息进行持久化,做后续的补偿处理。
配置可靠性参数
1 配置 acks = -1
- acks=0,表示生产者不等待任何服务器节点的响应,只要发送消息就认为成功。
- acks=1,表示生产者收到 leader 分区的响应就认为发送成功。
- acks=-1,表示只有当 ISR 中的副本全部收到消息时,生产者才会认为消息生产成功了。这种配置是最安全的,因为如果 leader 副本挂了,当 follower 副本被选为 leader 副本时,消息也不会丢失。但是系统吞吐量会降低,因为生产者要等待所有副本都收到消息后才能再次发送消息。
2 配置 retries = 3
- 参数 retries 表示生产者生产消息的重试次数,这里的3属于一个建议值,如果重试次数超过3次后,消息还是没有发送成功,可以根据自己的业务场景对发送失败的消息进行额外处理,比如持久化到磁盘,等待服务正常后进行补偿。
3 配置 retry.backoff.ms=300
- 参数retry.backoff.ms 表示重试的时间间隔,单位是毫秒,300ms是一个建议值,如果配置的时间间隔太短,服务可能仍然处于不可用状态。
服务端不丢失消息
配置 replication.factor > 1
参数replication.factor表示在服务端的分区副本数,配置 > 1后,即使分区的leader挂掉,其他follower被选中为leader也会正常处理消息。
配置 min.insync.replicas > 1
min.insync.replicas 指的是 ISR 最少的副本数量,原理同上,也需要大于 1 的副本数量来保证消息不丢失。
简单介绍下 ISR。ISR 是一个分区副本的集合,每个分区都有自己的一个 ISR 集合。但不是所有的副本都会在这个集合里,首先 leader 副本是在 ISR 集合里的,如果一个 follower 副本的消息没落后 leader 副本太长时间,这个 follower 副本也在 ISR 集合里;可是如果有一个 follower 副本落后 leader 副本太长时间,就会从 ISR 集合里被淘汰出去。也就是说,ISR 里的副本数量是小于或等于分区的副本数量的。Leader 维护了一个动态的 in-sync replica set (ISR:同步副本),意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后,leader 就会给 producer 发送 ack。如果 follower长时间未向leader同步数据,则该 follower 将被踢出 ISR,该时间阈值由replica.lag.time.max.ms参数设定。而如果Leader 发生故障,就会从 ISR 中选举出新的 leader。
确保 replication.factor > min.insync.replicas。
如果两者相等,那么只要有一个副本挂机,整个分区就无法正常工作了。我们不仅要改善消息的持久性,防止数据丢失,还要在不降低可用性的基础上完成。推荐设置成 replication.factor = min.insync.replicas + 1。
配置 unclean.leader.election.enable = false
unclean.leader.election.enable 指是否能把非 ISR 集合中的副本选举为 leader 副本。unclean.leader.election.enable = true,也就是说允许非 ISR 集合中的 follower 副本成为 leader 副本。因为非ISR集合中的副本消息可能已经落后leader消息很长时间,数据不完整,如果被选中作为leader副本,可能导致消息丢失。
消费者不少消费消息
手动提交消息
1 配置 enable.auto.commit=false
enable.auto.commit 这个参数表示是否自动提交,设置成false后,将消息提交的权利交给开发人员。因为设置自动提交后,消费端可能由于消息消费失败,但是却自动提交,导致消息丢失问题。
2 手动提交消息的正确方式 先处理消息,后提交offset,
但是这种情况可能会导致消息已经消费成功,但是提交offset的时候,consumer突然宕机,导致消息提交失败,等到consumer重启后,可能还会收到已经成功处理过的消息,消费了重复的消息,所以手动提交消息需要做一些幂等性的措施。
消息不重复
生产端不重复生产消息 由于网络原因,Producer端对消息进行了重试,但是,Broker端可能之前已经收到了消息,这样就导致broker端收到了重复的消息。
kafka在0.11.0 版本后,给每个Producer端分配了一个唯一的ID,每条消息中也会携带一个序列号,这样服务端便可以对消息进行去重,但是如果是两个Producer生产了两条相同的消息,那么kafka无法对消息进行去重,所以我们可以在消息头中自定义一个唯一的消息ID然后在consumer端对消息进行手动去重。
消费端不重复消费消息
由于为了保证不少消费消息,配置了手动提交,由于处理消息期间,其他consumer的加入,进行了重平衡,或者consumer提交消息失败,进而导致接收到了重复的消息。 我们可以通过自定义唯一消息ID对消息进行过滤去重重复的消息。
对于消息重复
这个影响不是很严重,无论是生产者重复推送数据,还是消费者重复拉取数据,只要在消费端落库时,手动做去重就可以了。
消息丢失
对于消息丢失
consumer端丢失消息的情形比较简单:如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。由于Kafka consumer默认是自动提交位移的,所以在后台提交位移前一定要保证消息被正常处理了,因此不建议采用很重的处理逻辑,如果处理耗时很长,则建议把逻辑放到另一个线程中去做。为了避免数据丢失,可以采用手动提交offset:
- (1)enable.auto.commit=false 关闭自动提交位移、
- (2)在消息被完整处理之后再手动提交位移
生产者丢失消息是最复杂的情形了。
生产者(Producer) 使用 send 方法发送消息实际上是异步的操作,我们可以通过 get()方法获取调用结果,但是这样也让它变为了同步操作,但是一般不推荐这么做!可以采用为其添加回调函数的形式。这个回调函数会在 Producer 收到 ack 时调用,此处就和acks参数配置[1、0、-1]密切相关了,
如果消息发送失败的话,我们检查失败的原因之后重新发送即可!另外这里推荐为 Producer 的retries(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次,你3次一下子就重试完了。