这个问题要从 3 个方面来保证消息不丢失:即生产者、服务端、消费者。
1.producer 生产端是如何保证消息不丢失的
1.1 ack的配置策略
acks 有3个值可选 0、1和-1(或者all),默认值为1,值为字符串类型,不是整数类型
- 0:producer发送后即为成功,无需分区 partition 的 leader 确认写入成功
- 1:producer发送后需要接收到分区的 leader 发送确认收到的回复
- -1:producer发送后,需要 ISR 中所有副本都成功写入,才能收到成功响应
配置 acks = all (或-1)
生产者在发送消息之后,需要等待ISR(所有与leader副本保持一定程度同步的副本(包括Leader)组成ISR(In-Sync Replicas))中所有的副本都成功写入消息之后才能够收到来自服务端的成功响应,在配置环境相同的情况下此种配置可以达到最强的可靠性。
即:在发送消息时,需要leader 向fllow 同步完数据之后,也就是ISR队列中所有的broker全部保存完这条消息后,才会向ack发送消息,表示发送成功。
1.2 retries的配置策略
在kafka中错误分为2种,一种是可恢复的,另一种是不可恢复的。
可恢复性的错误:
如遇到在leader的选举、网络的抖动等这些异常时,如果我们在这个时候配置的retries大于0的,也就是可以进行重试操作,那么等到leader选举完成后、网络稳定后,这些异常就会消失,错误也就可以恢复,数据再次重发时就会正常发送到broker端。需要注意retries(重试)之间的时间间隔,以确保在重试时可恢复性错误都已恢复。
不可恢复性的错误:
如:超过了发送消息的最大值(max.request.size)时,这种错误是不可恢复的,如果不做处理,那么数据就会丢失,因此我们需要注意在发生异常时把这些消息写入到DB、缓存本地文件中等等,把这些不成功的数据记录下来,等错误修复后,再把这些数据发送到broker端。
1.3 如何选取
(1)高可用型
- 配置:acks = all,retries > 0 retry.backoff.ms=100(毫秒) (并根据实际情况设置retry可能恢复的间隔时间)
- 优点:这样保证了producer端每发送一条消息都要成功,如果不成功并将消息缓存起来,等异常恢复后再次发送。
- 缺点:这样保证了高可用,但是这会导致集群的吞吐量不是很高,因为数据发送到broker之后,leader要将数据同步到fllower上,如果网络带宽、不稳定等情况时,ack响应时间会更长
(2)折中型
- 配置:acks = 1 retries > 0 retries 时间间隔设置 (并根据实际情况设置retries可能恢复的间隔时间)
- 优点:保证了消息的可靠性和吞吐量,是个折中的方案
- 缺点:性能处于2者中间
(3)高吞吐型
- 配置:acks = 0
- 优点:可以相对容忍一些数据的丢失,吞吐量大,可以接收大量请求
- 缺点:不知道发送的消息是 否成功
2.broker端是如何保证消息不丢失的
unclean.leader.election.enable=false
设置为 false(默认参数为 true),意思是,当存有你最新一条记录的 replication 宕机的时候,Kafka 自己会选举出一个主节点,如果默认允许还未同步你最新数据的 replication 所在的节点被选举为主节点的话,你的数据将会丢失,因此这里应该按需将参数调控为 false;
3.consumer端是如何保证消息不丢失的
在consumer消费阶段,对offset的处理,关系到是否丢失数据,是否重复消费数据,因此,我们把处理好offset就可以做到exactly-once && at-least-once(只消费一次)数据。
当enable.auto.commit=true时
表示由kafka的consumer端自动提交offset,当你在pull(拉取)30条数据,在处理到第20条时自动提交了offset,但是在处理21条的时候出现了异常,当你再次pull数据时,由于之前是自动提交的offset,所以是从30条之后开始拉取数据,这也就意味着21-30条的数据发生了丢失。
当enable.auto.commit=false时
由于上面的情况可知自动提交offset时,如果处理数据失败就会发生数据丢失的情况。那我们设置成手动提交。
当设置成false时,由于是手动提交的,可以处理一条提交一条,也可以处理一批,提交一批,由于consumer在消费数据时是按一个batch来的,当pull了30条数据时,如果我们处理一条,提交一个offset,这样会严重影响消费的能力,那就需要我们来按一批来处理,或者设置一个累加器,处理一条加1,如果在处理数据时发生了异常,那就把当前处理失败的offset进行提交(放在finally代码块中)注意一定要确保offset的正确性,当下次再次消费的时候就可以从提交的offset处进行再次消费。