MQ的作用:
(1)流量削峰:比如双十一会出现瞬间大流量请求,这些请求又不能直接丢弃,则可考虑将请求按照先后顺序存入MQ,然后再按顺序进行处理;
(2)消息异步处理:比如用户注册的场景,一个用户注册之后,可能还涉及到用户的积分信息初始化,但是这个信息不影响用户注册的完成,所以积分信息的初始化可通过MQ异步完成;
(3)系统解耦:一个商城,可分为用户模块、订单模块、支付模块等,各模块之间的通信可通过MQ完成;
使用MQ带来的挑战:
(1)系统复杂性提升;
(2)学习成本;
(3)系统可用性降低:MQ通信可能出现网络问题等;
RabbitMQ
rabbit 工作模型
(1)producer 生产者
(2)Exchange 交换机,不存储消息,只存储消息和队列之间的绑定关系
1)direct 直连:使用明确的绑定键,适用于业务目的明确的场景;
basicPublish(“direct_exchange”,"BingKey",“msg”); //将消息发送到指定交换机
2)Topic 主题:
使用支持通配符的绑定键;适用于根据业务主题过滤消息的场景;
* : 有且只有一个单词
# : 有0个或者多个
basicPublish(“topic_exchange”,"TOPIC.BingKey",“msg”); //将消息发送到指定交换机
绑定关系: *.BingKey 以BingKey结尾,且前面只有一个单词;
3)Fanout 广播
无需绑定键。适用于通用类业务消息;
(3)queue 队列 :存放消息
(4)channel 信道: 存放消息通信的连接,防止每次发送消息都需要重新建立连接;
(5)consumer 消费者
高阶应用:
(1)如何实现订单的延迟关闭?
1)先存储到数据库,用定时任务扫描;
使用定时任务每分钟扫描半小时之前且状态为未付款的订单;存在问题:如果订单量过大,一个线程处理起来会产生延时;
2)死信队列
利用消息的过期时间(TTL),将过期的消息发送到死信交换机,再绑定到死信队列,由对应的消费者进行消费;
过期时间的设置方式:a、直接设置消息的过期时间;b、设置队列的过期时间;c、使用rabbitMq插件;
插件:rabbitmq-delayed-message-exchange 可以创建具有延迟投递功能的交换机;
由生产者发送消息的时候指定broker延迟发送给消费者的时间;
消息什么时候会变成死信消息:
(1)消息到达过期时间任未被消费;
(2)消费者拒绝了消息,且未设置重回的队列;
(3)队列达到最大长度,先入队的消息会进入死信交换机(DLX);
问题:
(1)服务端消息存满了怎么办?
服务端流控:
1)队列长度设置:x-max-length x-max-length-byte
2) 内存控制:
3)磁盘控制
消息的可靠投递:
(1)首先在代码里面,是先操作数据库,再发送消息,避免因为数据库回滚导致的数据不一致;
(2)如果在消息发送过程中,出现了消息丢失或重复怎么办呢?
RabbitMQ在设计的时候提供了很多保证消息可靠投递的机制;但是可靠性和性能两者不可兼得,需要在使用的时候加以选择;
根据RabbitMQ的工作模型,消息可靠投递的保证可从以下几个方面进行:
(1)生产者发送消息到Broker(消息服务器)的可靠性;
(2)消息从Exchange路由到Queue的可靠性(无法路由到正确的队列);
(3)消息在Queue中存储的可靠性;
(4)消费者订阅Queue并消费消息的可靠性;
对应解决方案:
(1)服务端确认机制
在生产者发送消息给RabbitMQ服务端的时候,服务端会给出应答。
应答模式:
1)Transaction事务模式
在生产者创建channel的时候将信道设置成事务模式,然后就可以发布消息给RabbitMQ了。如果生产者的channel.txCommit();方法调用成功,说明事务提交成功,消息一定成功到达了RabbitMQ中;
缺点:消息的发送是阻塞的,一条消息没有发送完毕,不能发送下一条;
2)Confirm确认模式
a、普通确认模式
生产者通过channel.confirmSelect();将信道设置为Confirm模式,一旦消息被投递到交换机以后,RabbitMQ服务端会发送一个确认(Basic.ACK)给生产者;
缺点:如果批量确认100条消息,前面99条都成功了,最后一条失败了,那么这100条都需要重新发送;
b、异步确认模式
一边发送一边确认的模式;
添加一个ConfirmLister,并用SortedSet来维护未被确认的消息;
(2)无法正确路由的消息处理:
a、消息回发
让服务端回发给生产者;
b、消息路由到备份交换机
(3)消息在队列中存储的可靠性
如果没有消费者,队列一直存储在数据库中,如果RabbitMQ的服务或者硬件异常,可能会导致内存中的数据丢失,所以需要把消息和元数据(队列、交换机、绑定关系)保存到磁盘;
(4)消息投递到消费者
消费者消息确认机制:自动或手动地发送ACK给服务端;
正常情况下,消费者收到消息后给服务端一个ACK,但是生产者是不知道的,这是符合解耦的思想的。
但如果特殊场景下需要保证一致性,生产者需要知道消费者有没有成功消费:
1)消费者收到消息,处理完成后,调用生产者的API;
2)消费者收到消息,处理完成后,发送一条响应给生产者;
这种消费者回调的机制,也有可能出现API调用失败或者响应失败的情况,那么可以设置一个补偿机制;
约定响应超时时间,如果没有回复则认为消费失败,则进行重发;
消息幂等性
如果生产者正常,消息也正常消费,只是调用回调API或者响应的时候失败,导致重复发送消息的情况怎么解决?
消息重复可能的原因:
1)生产者问题,生产者发送消息到broker的时候,开启了cofirm模式但未收到确认,消费者重复投递;
2)消费者消费消息的时候未发送ACK,导致消息重复消费;
由于生产者并不知道业务上什么样的消息才算重复,因为只能在消费端进行控制。
消费端可以针对每条消息生成业务ID,通过日志或者消息落库来判断重复;
消息的最终一致性
人为进行干预,保证数据的最终一致性;比如银行系统的对账和冲账
消息的顺序性
一个队列对应多个消费者时,由于消费者消费消息的速度不一样,不能保证消息的顺序性,因此一般情况下,将不同的业务消息存放到不同的队列。
RabbitMQ集群与高可用
RabbitMQ如何支持集群?
rabbitmq通过erlang.cookie来验证身份,需要在所有节点上保持一致;
服务的端口:5672 UI的端口:15672 集群的端口:25672
需要注意的是,RabbitMQ集群无法搭建在广域网上,需要在同一个机房。
RabbitMQ节点的类型:
1)磁盘节点
将元数据(队列名字、交换机类型和名字、绑定关系、Vhost等信息)保存在磁盘;默认为磁盘节点;
集群中至少需要一个磁盘节点,否则服务宕机的时候无法恢复数据;
2)内存节点
内存节点会将磁盘节点的地址存放在磁盘(否则重启之后无法恢复数据)。如果是持久化的消息,会同时存放在内存和磁盘;
一般情况,我们的应用连接内存节点(读写快),用磁盘节点备份数据;
RabbitMQ集群模式
1)普通集群
普通集群模式下,节点之间只会同步元数据(交换机、队列、绑定关系、Vhost);
每个节点上存储不同的消息,如果一个节点操作不在自己服务器上的消息,此时会进行消息的转发。
存在问题:如果节点挂了,那么对应的数据也就全部丢失,无法实现高可用。
2)镜像集群
消息内容会在节点间进行同步,系统可用性更高,但是会消耗系统性能。
常见面试题
1、RabbitMQ消息快速积压怎么处理?
出现积压的原因:
1)没有消费者消费消息;
2)生产者产生消息速度过快,消费者消费能力跟不上;
3)消费者消费消息以后没有给Broker发送ACK,导致队头消息不出队,后续消息不能消费;
解决途径:
(1)如果出现生产者不断重复发送消息的情况,需要立即停掉;
(2)Broker:
a、broker磁盘存储受限,升级硬件;
b、如果无法升级硬件,可以通过磁盘和内存限制控制堆积的消息数(当内存或磁盘超过配置的阈值时,Broker会暂停读取发布消息的客户端的连接);
c、对消息设置过期属性;
d、如果消息不重要,可以直接丢弃;写一个程序消费消息,然后直接丢弃;
(3)消费者
a、增加消费者个数,或者多线程消费;
b、如果是因为队头的消息没有ACK导致的,可以重启消费者,重新消费,然后发送ACK;
c、消费者代码问题,则修复程序;
2、Channel和Vhost的作用是什么?
Channel:减少TCP资源消耗,也是最重要的编程接口;
Vhost:提高硬件资源利用率,实现资源隔离;
3、RabbitMQ的路由方式有哪些?分别适合于什么样的场景?
4、无法被路由的消息去了哪里?
5、消息什么时候会变成死信消息?