kafka如何保证数据的消息不丢失(最简洁)

一、kafka 本身配置层面
1.1、replication.factor 默认值1
创建kafka的topic时候,每个分区设置的副本数, 根据broker数量酌情设置, 建议业界通常做法设置为3

1.2、min.insync.replicas 默认值1
消息至少要被写入到min.insync.replicas这么多副本才算成功时候,通常使用小于replication.factor,当request.required.acks = all时生效

二、kafka的生产者层面
2.1、设置副本确认
request.required.acks 有3个取值,分别是0,1,all

0:表示无需等待broker确认就认为写入成功,风险大,一般不使用

1:表示当leader确认写入成功就认为成功了,有数据丢失风险,不建议使用

all:leader和follower都确认写入成功,才能认为是写入成功了,推荐使用(-1在新版本已弃用)

2.2、设置重试次数
retries 默认值0,不重试。kafka返回写入消息失败,生产者进行重新发送,直到成功, 根据业务合理设置重试次数

2.3、消息补偿机制
虽设置了重试次数,但不可能一直重试,如重试3次后仍然失败,这时候可把失败消息放入本地消息表,用定时任务轮询消息表, 重新生产推送到kafka

三、kafka的消费者层面
3.1 关闭自动 offset
设置 enable.auto.commit = false , 默认值true,自动提交

3.2 手动提交offset
使用kafka的Consumer的类,用方法consumer.commitSync()提交

或者使用spring-kafka的 Acknowledgment类,用方法ack.acknowledge()提交(推荐使用

第二:如何确保消息成功地发送至 MQ?

1. 实现:提交事务。      channel.txSelect:用于将当前的信道设置成事务模式、channel.txCommit:用于提交事务、channel.txRollback:用于回滚事务

2. 实现: 发送方确认模式(publisher confirm)。(即信道设置成confirm模式): channel.confirmSelect();将信道设置成confirm模式。

一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),
一旦消息被投递到RabbitMQ服务器之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),
 这就使得生产者知晓消息已经正确到达了目的地了。如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。

性能比较:
可以看出,事务机制最慢,使用事务机制会“吸干”RabbitMQ的性能。 普通confirm机制虽有提升但是不多,
批量confirm和异步confirm性能最好,大家可以根据自己喜好自行选择使用哪种机制,个人建议使用异步confirm机制。

 

第三:如何确保消息接收方消费了消息MQ?

消息确认模式有:
AcknowledgeMode.NONE:自动确认
AcknowledgeMode.AUTO:根据情况确认
AcknowledgeMode.MANUAL:手动确认

1.消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
2.自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。
3.如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
4.如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者。
5.如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限。

 

第四:如何保证MQ消息的可靠传输?

以我们常用的RabbitMQ为例,
丢失又分为:生产者丢失消息、消息队列丢失消息、消费者丢失消息;

1. 生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供confirm模式来确保生产者不丢消息;

2. 消息队列RabbitMQ丢数据:消息持久化。就是消息写入之后会持久化到磁盘。

持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,
才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,
生产者收不到 ack,你也是可以自己重发的。

3. 消费端弄丢了数据:关闭 RabbitMQ 的自动ack可以通过一个api来调用就行,每次代码里确保处理完的时候,再在程序里ack一把。

 

第五:如何避免消费者手动ack失败,导致该消息会阻塞在队列中,后续的消息也会被阻塞住导致消息无法消费?

针对:rabbitMQ
rabbitMQ提供配置参数来开启消费者重试机制,也能配置配置最大重试次数和重试间隔时间,
rabbitMQ对于消息消费失败达到一定次数后,就会放弃该消息,
我们可以手动实现,如果消费失败达到最大重试次数后,将数据转发到死信队列上,
由死信队列的消费者来实现消息的持久化到数据库或者日志文件中,一般重试次数我们设置为3此,间隔时间为5s。

 

第六:Kafka中如何实现死信队列&重试队列?

死信队列: 当一条消息初次消费失败,消息队列 MQ 会自动进行消息重试;达到最大重试次数后,若消费依然失败,
则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 MQ 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中,
这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

回退队列: 与此对应的还有一个“回退队列”的概念,试想如果消费者在消费时发生了异常,那么就不会对这一次消费进行确认(Ack),
进而发生回滚消息的操作之后消息始终会放在队列的顶部,然后不断被处理和回滚,导致队列陷入死循环。为了解决这个问题,
可以为每个队列设置一个回退队列,它和死信队列都是为异常的处理提供的一种机制保障。实际情况下,回退队列的角色可以由死信队列和重试队列来扮演。

重试队列: 其实可以看成是一种回退队列,具体指消费端消费消息失败时,为防止消息无故丢失而重新将消息回滚到Broker中。
与回退队列不同的是重试队列一般分成多个重试等级,每个重试等级一般也会设置重新投递延时,重试次数越多投递延时就越大。


Kafka不支持重试机制也就不支持消息重试,也不支持死信队列,
因此使用kafka做消息队列时,如果遇到了消息在业务处理时出现异常的场景时,需要额外实现消息重试的功能。
demo举例: https://artisan.blog.csdn.net/article/details/113846000

如果不想自己实现消息重试机制,建议使用RocketMQ作为消息队列,RocketMQ的消息重试机制相当完善,

 详见:   https://help.aliyun.com/document_detail/43490.html

 

第七:如何避免MQ消息重复投递或重复消费?

关键在于消息处理逻辑的幂等性,也就是说同一条消息无论被消费多少次,产生的结果和消息被消费一次是一样的.
(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧,(或者inSertIgnore有就更新,没有就插入)
(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性
(3)业务发送方带入唯一UUID,订单id做,消费方用唯一标示去查询,status作为标示是否处理过。

 

第八:如何保证消息的顺序性?

一个消费者对应一个队列,并且确保需要保证顺序的消息路由在同一个队列中。

 

posted @ 2022-04-22 17:41  威兰达  阅读(2223)  评论(0编辑  收藏  举报