1.MQ如何保证消息不丢失
首先要明确哪些环节可能会造成丢数据:生产者(网络问题)、broker(page cache缓存来不及刷盘,服务挂了)、消费者(异步执行业务)。
**生产者:**统⼀的思路就是⽣产者确认。简单来说,就是⽣产者发出消息后,给⽣产者⼀个确定的 通知,这个消息在Broker端是否写⼊完成了。
在RocketMQ中,提供了三种不同的发送消息的⽅式:
- 异步发送,消息发出去就行,不需要Broker确认。效率很⾼,但是会有丢消息的可能。
- 同步发送,⽣产者等待Broker的确认,一般会设置超时时间。消息最安全,但是效率很低。
- 异步发送,⽣产者另起⼀个线程等待Broker确认,收到Broker确认后直接触发回调⽅法。消息安全和效率之间⽐较均衡,但是会加⼤客户端的负担。
此外,RocketMQ还提供了事务消息,其核⼼思想还是通过Broker主动触发⽣产者的回调⽅法,从⽽确认消息是否成功发送到了Broker。当生产者查询本地事务状态为UNKNOWN时,会进行状态回查。
Kafka也同样提供了这种同步和异步的发送消息机制。
- 直接send发送消息,返回的是⼀个Future。这就相当于是异步调⽤。
- 调⽤future的get⽅法才会实际获取到发送的结果。⽣产者收到这个结果后,就可以知道消息是否成功发到broker了。 这个过程就变成了⼀个同步的过程。
在RabbitMQ中,则是提供了⼀个Publisher Confirms⽣产者确认机制,添加两个回调,⼀个处理成功的ack响应,⼀个处理失败的nack响应。
生产者数据发送失败可以重试或直接抛业务异常。
broker数据会优先写⼊到缓存,然后过⼀段时间再写⼊到磁盘。就是这个时间内服务要是挂了,就会丢数据。我们能做的就是尽可能缩短这个时间。
RocketMQ的Broker提供了⼀个很明确的配置项flushDiskType,可以选择刷盘模式。有两个可选项, SYNC_FLUSH 同步刷盘和ASYNC_FLUSH 异步刷盘。同步刷盘的实现⽅式其实也是以10毫秒的间隔去调⽤刷盘操作。这⼀套同步刷盘机制,可以通过绝⼤部分业务场景的验证。这其实就是⼀种平衡。
Kafka可以设置刷盘的频率,时间或者数量触发阈值都会触发刷盘:
flush.ms : 多⻓时间进⾏⼀次强制刷盘。
log.flush.interval.messages:表示当同⼀个Partiton的消息条数积累到这个数量时,就会申请⼀次刷盘操作。默 认是Long.MAX。
log.flush.interval.ms:当⼀个消息在内存中保留的时间,达到这个数量时,就会申请⼀次刷盘操作。他的默认值是 空。如果这个参数配置为空,则⽣效的是下⼀个参数。 log.flush.scheduler.interval.ms:检查是否有⽇志⽂件需要进⾏刷盘的频率。默认也是Long.MAX。
RabbitMQ官⽹给了更明确的说法。即便声明成了持久化对列,RabbitMQ的服务端也不会实时调⽤fsync,因此⽆法保证服务端消息断电不丢失。明确就建议了,如果对消息安全性有更⾼的要求,可以使⽤Publisher Confirms机制来进⼀ 步保证消息安全。这其实也是对Kafka和RocketMQ同样适⽤的建议。
主从同步时broker不丢数据要如何做?
对于RocketMQ,主从同步没有自动的故障切换,主节点有完整的数据,即使宕机了也不会有新数据写进来,只要磁盘没有损坏,等它重启了,就依然保证有完整的数据。
至于dledger集群,优先保证的是集群内的数据⼀致性,⽽并不是保证不丢失。但结合客户端的⽣产者确认机制,基本可以认为不会造成消息丢失。
对与Kafka会实现故障切换,从节点会升级为主节点,所以要设置acks=all/-1: 所有ISR确认,最安全。
acks=all //等待所有ISR确认
min.insync.replicas=2 //最小ISR数
replication.factor=3 //3副本
unclean.leader.election.enable=false //禁止不干净选举
消费者要确认自己业务处理成功后,再给broker一个响应,表示消息倍正确处理了。
RocketMQ和Kafka是根据Offset机制重新投递,⽽RabbitMQ的Classic Queue经典对列,则是把消息重新⼊队。因此,正常情况下,Consumer消费消息这个过程,是不会造成消息丢失的,相 反,可能需要考虑下消息幂等的问题。
如果MQ服务全部挂了,如何保证不丢失?
缓存降级,生产者将要发给MQ的信息发到数据库或者其他降级缓存中,确保业务可以继续进行。再启动一个线程,不断的往MQ中发信息,确保MQ服务恢复过来后,这些 消息可以尽快进⼊到MQ中,继续往下游Conusmer推送,⽽不⾄于造成消息丢失。
2. MQ如何保证消息的顺序性
讨论MQ的消息顺序性,其实是在强调局部有序,⽽不是全局有序。在RocketMQ和Kafka中把 Topic的分区数设置成1,这类强⾏保证消息全局有序的⽅案,想想就好了,最好别用。
RocketMQ的顺序消费机制,需要两个⽅⾯的保障。
1》Producer将⼀组有序的消息通过MessageSelector写⼊到同⼀个MessageQueue中。
2》Consumer每次集中从⼀个MessageQueue中拿取消息。(在服务端进行并发控制,实现MessageListenerOrderly接口时,会给⼀个MessageQueue加锁,拿到MessageQueue上所有的消息,然后再去读取下⼀个MessageQueue的消息。)
RocketMQ如果消息处理失败,会把消息所在的队列暂时supent一段时间后,再重试。如果重试失败进入死信队列,则后续该订单的步骤也要放到死信队列中。
Kafka的顺序消费:
1》Producer可以通过分区计算机制,将消息写到同一个分区。
2》Consumer对某⼀个Partition拉取消息时,天⽣就是单线程的,能保证局部顺序消费的。
如果消息失败,过段时间重试,不会把整个队列锁住。消息有序性的涉及没有RocketMQ设计的那么完善。。
RabbitMQ,以他的Classic Queue经典对列为例,他的消息被⼀个消费者从队列中拉取后,就直接从队列中把消息删除了。如果消息处理失败,会重新入队,这样就打乱了局部有序,只有消息处理成功才能保证其局部有序。
所以业务场景需要保证消息的有序,RocketMQ是可以优先考虑的。
3. MQ如何保证消息幂等性
⽣产者发送消息到服务端如何保持幂等?
RocketMQ的处理⽅式,是会在发送消息时,给每条消息分配⼀个唯⼀的ID。通过这个ID,就可以判断消息是否重复投递。
⽽对于Kafka,则会通过他的幂等性配置,防⽌⽣产者重复投递消息造成的幂等性问题。broker会为每一个生产者PID+分区维护⼀个序列号(SN),当Producer要往同⼀个Partition发送消息时,会维护⼀个递增的sequenceNumber,sequenceNumber < SN ,消息已经写⼊ 了,不需要再重复写⼊,反之大很多,会认为中间可能有数据丢失了,会抛一个OutOfOrderSequenceException异常。
消费者保持幂等,最主要是要找到⼀个唯⼀性的指标。消息过来的时候先过滤一下,比如布隆过滤器,如果不存在就继续。
4. MQ如何快速处理积压的消息
RocketMQ、Kafka和RabbitMQ的Stream Queue流式对列的消息积压能力还可以,但是长期的消息积压导致日志文件过期,未消费过的数据会丢失。RabbitMQ的Classic Queue经典对列和Quorum Queue仲裁对列 的 消息积压会影响获取数据的性能,需要及时干预。
而消息积压的根本原因还是Consumer处理消息的效率太低,所以要从从业务上提升Consumer消费消息的性能以及增加一定数目的Consumer实例。
RabbitMQ,如果是Classic Queue经典对列,同⼀个Queue,在多个Consuemr之间依次分配消息的。所以这时,如果Consumer消费能⼒不够,那么直接加更多 的Consumer实例就可以了。
RocketMQ、Kafka类似,一个queue最多只能被一个消费者组内的一个消费者消费。所以增加消费者实例只能用临时方案,可以创建⼀个新的Topic,配置⾜够多的MessageQueue。然后把 Consumer实例的Topic转向新的Topic,并紧急上线⼀组新的消费者,只负责消费旧Topic中的消息,并转存到 新的Topic中。这个速度明显会⽐普通Consumer处理业务逻辑要快很多。然后在新的Topic上,就可以通过添 加消费者个数来提⾼消费速度了。之后再根据情况考虑是否要恢复成正常情况。