MQ消息队列的作用
- 解耦
解耦是消息队列要解决的最本质问题。
- 最终一致性
最终一致性指的是两个系统的状态保持一致,要么都成功,要么都失败。有些消息队列是不能保证最终一致性。
- 广播
消息队列必备的功能是可以进行广播的。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。
- 错峰与流控
典型的使用场景就是秒杀业务用于流量削峰场景。
由于篇幅的关系,本文重点介绍消息队列比较,详细应用场景请参考流量削峰漏斗模型:《什么是流量削峰?如何解决秒杀业务的削峰场景》
RocketMQ、KafKa和RabitMQ对比
特性 | ActiveMQ | RabbitMQ | RocketMQ | KafKa |
---|---|---|---|---|
开发语言 | java | erlang | java | scala |
单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
时效性 | ms级 | us级 | ms级 | ms级以内 |
集群管理方式 | name server | zookeeper | ||
高可用性 | 高(主从架构) | 高(主从架构)采用镜像模式实现,数据量大时会有性能瓶颈 | 非常高(分布式架构) | 非常高(分布式架构) |
主从切换 | 自动切换,最早加入集群的slave会成为master,因为新加入的slave不会同步master之前的数据,所以可能会出现部分数据丢失。 | 不支持自动切换,master失效后不能向master发送消息,consumer大概30s(默认)可感知此事件,此后从slave消费;如果master无法恢复,异步复制时可能会出现部分信息丢失 | 自动切换,N个副本,允许N-1个失效;master失效后自动从slave选择一个为主 | |
消息写入性能 | RAM约为RocketMQ的1/2,DISK的性能约为RAM性能的1/3。 | 很好,每条10个字节测试,单机单broker约7w/s,单机3broker约12w/s | 非常好,每条10个字节测试:百万条/s | |
功能特性 | 成熟的产品,在很多公司得到应用,有较多的文档;各种协议支持较好 | 基于erlang开发,所以并发能力很强,性能极好,延迟很低,管理界面较丰富。 | MQ功能较完备,扩展性较佳 | 只支持主要的MQ功能,像消息查询回溯等功能没有提供,大数据场景使用广泛。 |
为啥RabbitMQ能做到us级,其他的都是ms级呢?
主要有以下几个原因。
- erlang语言天然支持高并发。
- 基于内存镜像模式实现,RabbitMQ的us级主要指基于RAM模式。
RocketMQ、KafKa和RabitMQ选择
- Kafka
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。
大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka了。
- RocketMQ
天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。
RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。
- RabbitMQ
RabbitMQ,结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。
如何保证消息的一致性和如何进行消息的重试机制?
如何保证消息的一致性
保证消息的一致性有三种方式。
-
定时补偿+幂等消费(TCC)
-
推拉结合
-
分布式事务
如何进行消息的重试机制
下面只分析RocketMQ的消息重试。
RocketMQ只有当消费模式为集群模式时,Broker才会自动进行重试,对于广播消息是不会重试的。消费者从Broker拉取消息失败时,RocketMQ会通过消息重试机制重新投递消息,这肯定不会一直投递。当投递到最大重试次数之后,就会加入到死信队列,我们只要对死信队列的数据做人工补偿。
认定为消费失败规则
RocketMQ规定,以下三种情况统一按照消费失败处理并会发起重试。
- 业务消费方返回ConsumeConcurrentlyStatus.RECONSUME_LATER
- 业务消费方返回null
- 业务消费方主动/被动抛出异常
注意 对于抛出异常的情况,只要我们在业务逻辑中显式抛出异常或者非显式抛出异常,broker 也会重新投递消息,如果业务对异常做了捕获,那么该消息将不会发起重试。因此对于需要重试的业务,消费方在捕获异常的时候要注意返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 或 null 并输出异常日志,打印当前重试次数。(推荐返回ConsumeConcurrentlyStatus.RECONSUME_LATER)
个人不建议捕获异常,让MQ认定为失败。
RocketMQ重试时间窗口
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
死信的业务处理方式
默认的处理机制中,如果我们只对消息做重复消费,达到最大重试次数之后消息就进入死信队列了。
我们也可以根据业务的需要,定义消费的最大重试次数,每次消费的时候判断当前消费次数是否等于最大重试次数的阈值。
如:重试三次就认为当前业务存在异常,继续重试下去也没有意义了,那么我们就可以将当前的这条消息进行提交,返回 broker 状态ConsumeConcurrentlyStatus.CONSUME_SUCCES,让消息不再重发,同时将该消息存入我们业务自定义的死信消息表,将业务参数入库,相关的运营通过查询死信表来进行对应的业务补偿操作。
RocketMQ 的处理方式为将达到最大重试次数(16 次)的消息标记为死信消息,将该死信消息投递到 DLQ 死信队列中,业务需要进行人工干预。实现的逻辑在 SendMessageProcessor 的 consumerSendMsgBack 方法中,大致思路为首先判断重试次数是否超过 16 或者消息发送延时级别是否小于 0,如果已经超过 16 或者发送延时级别小于 0,则将消息设置为新的死信。死信 topic 为:%DLQ%+consumerGroup。
发送失败如何重试
设置生产者在 3s 内没有发送成功则重试 3 次的代码如下。
/**同步发送消息,如果 3 秒内没有发送成功,则重试 3 次*/
DefaultMQProducer producer = new DefaultMQProducer("DefaultProducerGroup");
producer.setRetryTimesWhenSendFailed(3);
producer.send(msg, 3000L);
参考
https://www.jianshu.com/p/fec054f3e496
https://gitbook.cn/books/5d340810c43fe20aeadc88db/index.html