MQ
一、MQ比较
【比较】:Kafka以高吞吐量闻名,不过其数据稳定性一般,而且无法保证消息的有序性。日志收集有使用,业务模块中则使用RabbitMQ。
RocketMQ基于Kafka的原理,弥补了Kafka的缺点,继承了其高吞吐优势,其客户端目前以Java为主。但是阿里巴巴开源产品的稳定性需要慎重。
RabbitMQ基于面向并发的语言Erlang开发,吞吐量不如Kafka,但是对于一般的公司体量来说已经够用。而且消息可靠性较好,并且消息延迟极低,集群搭建比较方便。支持多种协议,并且有各种语言的客户端,比较灵活。Spring对RabbitMQ的支持也比较好,使用起来比较方便。
二、RabbitMQ如何确保消息不丢失
RabbitMQ针对消息传递过程中可能发生问题的各个地方,给出了针对性的解决方案:
2.1、生产者发送消息时可能因为网络问题导致消息没有到达交换机:
【办法】RabbitMQ提供了publisher confirm机制
生产者发送消息后,可以编写ConfirmCallback函数;
消息成功到达交换机后,RabbitMQ会调用ConfirmCallback通知消息的发送者,返回ACK
消息如果未达到交换机,RabbitMQ也会调用ConfirmCallBack通知消息的发送者,返回NACK
消息超时未发送成功也会抛出异常。
2.2、消息到达交换机后,如果未能到达队列,也会导致消息丢失:
【办法】RabbitMQ提供了publisher return机制
生产者可以定义ReturnCallBack函数;
消息到达交换机,未到达队列,RabbitMQ会调用ReturnBack通知发送者,告知失败原因。
2.3、消息到达队列后,MQ宕机也可能导致丢失消息
【办法】RabbitMQ提供了持久化功能,集群主从备份功能
消息持久化,RabbitMQ会将交换机、队列、消息持久化到磁盘,宕机重启可以恢复消息
镜像集群,仲裁队列,都可以提供主从备份功能,主节点宕机,从节点会自动切换为主,数据依然在
2.4、消息投递给消费者后,如果消费者处理不当,也可能导致消息丢失
【办法】SpringAMQP基于RabbitMQ提供了消费者确认机制、消费者重试机制,消费者失败处理策略
消费者确认机制:
a、消费者处理消息成功,未出现异常时,Spring返回ACK给RabbitMQ,消息才被移除
b、消费者处理消息失败,抛出异常,宕机,Spring返回NACK或者不返回结果,消息不被异常
消费者重试机制:
a、默认情况下,消费者处理失败时,消息会再次回到MQ队列,然后投递给其它消费者Spring提供的消费者重试机制,则是在处理失败后不返回NACK,而是直接在消费者本地重试。多次重试都失败后,则按照消费者失败处理策略来处理消息。避免了消息频繁入队带来的额外压力
消费者失败策略:
a、当消费者多次本地重试失败时,消息默认会丢失
b、Spring提供了Republish策略,在多次重试都失败,耗尽重试次数后,将消息重新投递给指定的异常交换机,并且会携带上异常栈信息,帮助定位问题
三、RabbitMQ如何避免消息堆积
消息堆积问题产生的原因往往是因为消息发送的速度超过了消费者消息处理的速度。因此处理方案有以下三点:
1、提高消费者处理速度
2、增加更多消费者
3、增加队列消息存储上线
3.1、提高消费者处理速度
消费者处理速度是由业务代码决定的,所以我们能做的包括以下:
a、尽可能优化业务代码,提高业务性能
b、接收到消息后,开启线程池,并发处理多个消息
【优点】成本低,修改代码即可
【缺点】开启线程池会带来额外的性能开销,对于高频、低时延的任务不合适。推荐任务执行周期较长的业务
3.2、增加更多消费者
一个队列绑定多个消费者,共同争抢任务,自然可以提供消息处理的速度
【优点】能用钱解决的问题都不是问题。实现简单粗暴
【缺点】问题是没钱,成本太高
3.3、增加队列消息存储上限
在RabbitMQ的1.8版本后,加入了新的队列模式:Lazy Queue
这种队列不会将消息保存在内存中,而是在收到消息后直接写入磁盘中,理论上没有存储上限。可以解决消息堆积问题。
【优点】磁盘存储更安全,存储无上限,避免内存存储带来的Page Out问题,性能更稳定。
【缺点】磁盘存储受到IO性能的限制,消息时效性不如内存模式,但是影响不大。
四、RabbitMQ如何保证消息的有序性
RabbitMQ是队列存储,具备先进先出的特点,只要消息的发送是有序的,那么理论上接收也是有序的。不过当一个队列绑定了多个消费者时,可能出现消息轮询投递给消费者的情况,而消费者的处理顺序就无法保证了。
因此,要保证消息的有序性,需要做到下面几点:
1、保证消息发送的有序性
2、保证一组有序的消息都发送到同一个队列
3、保证一个队列只包含一个消费者
五、如何防止MQ消息被重复消费
消息重复消费原因多种多样,不可避免。所以只能从消费端入手,只要能保证消息处理的幂等性就可以确保消息不被重复消费。
而幂等性的保证又有很多方案:
1、给每一条消息都添加一个唯一的id,在本地记录消息表及消息状态,处理消息时基于数据库表id唯一性做判断。
2、同样是记录消息表,利用消息状态字段实现基于乐观锁的判断,保证幂等性
3、基于业务本身的幂等性。比如根据id的删除、查询业务天生幂等;新增、修改等业务可以考虑基于数据库id唯一性、或者乐观锁机制确保幂等。本质与消息表方案类似。
六、如何确保RabbitMQ高可用
1、做好交换机、队列、消息持久化
2、搭建RabbitMQ的镜像集群,做好主从备份。也可以使用仲裁队列代替镜像集群
七、MQ可以解决那些问题
RabbitMQ能解决的问题很多:
1、解耦合:将几个业务关联的微服务调用修改为基于MQ的异步通知,可以解除微服务之间的耦合。提高业务性能。
2、流量削峰:将突发的业务请求放入MQ中,作为缓冲区。后端的业务根据自己的处理能力从MQ中获取消息,逐个处理任务。流量曲线变得平滑很多
3、延迟队列:基于RabbitMQ的死信队列或者DelayExchange插件,可以实现消息发送后,延迟接收的效果。
八、死信队列
DLX【Dead-Letter-Exchange】,也叫死信交换机。当消息在一个队列中变成死信(dead
message)之后,他能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称为死信队列。
8.1、产生原因
1、消息被拒绝
2、消息过期
3、队列达到最大长度
8.2、使用
使用死信队列,只需要在定义队列的时候设置队列参数
x-dead-letter-exchange
指定交换机即可。
在RabbitMQ管理界面中结果:
流程:
8.3、死信队列设置
1、过期进入死信队列。
2、操作最大长度进入死信队列。比如这个队列现在只能容纳两条信息,那么多出来的,就自动的进入死信队列了。
八、消息幂等性
一个请求(一条消息),不管重复来多少次,结果是不会改变的。