开篇
MQ全程为message queue,即消息队列。是一种跨进程、异步通信机制、用于上下游传递消息。RabbitMQ是由Erlang语言开发,基于AMQP
协议(Advanced Message Queuing Protocol 高级消息队列协议)实现的消息队列,它是一种应用程序之间的通信方法,消息队列在实际开发应用中有着非常广泛的使用。下面主要介绍RabbitMQ的基础、架构以及在开发过程中遇到的一些常用问题,还有在面试过程中一些长问的问题。
RabbitMQ官网:https://www.rabbitmq.com
RabbitMQ简介
2007年Rabbit公司基于AMQP标准协议开发的RabbitMQ1.0发布。AMQP的主要特性是面向消息、队列、路由、可靠性、安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性有着很高的要求场景,对性能和吞吐量的要求在其次。
RabbitMQ基础架构如下图:
关于RabbitMQ的概念
来介绍下上图中一些名词相关的概念:
- producer:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
- consumer:消息的消费者,表示一个从一个消息队列中取得消息的客户端应用程序。
- broker:接受客户端的链接,实现AMQP实体的服务。
- connection:生产者、消费者与broker之间的TCP链接。
- channel:如果每一次访问RabbitMQ都建立一个connection,在有消息量大的时候建立大量的TCP链接的开销太大,效率也低。channel是在connection内部建立的逻辑链接,channel之间完全隔离,这样就减少了系统频繁创建TCP链接的开销。
- exchange:message到达broker的第一站,根据分发规则,匹配routing key,将消息分发到queue中去。常用的类型有direct(点对点)、topic(规则匹配)、fanout(广播)
- queue:消息最终被送到这里等待consumer。
- binding:exchange和queue之间的虚拟链接,binding中包含routing key,binding信息保存到exchange的查询表中,用于message的分发。
- virtual host:出于多租户和安全因素,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost中创建exchange、queue等。
RabbitMQ的特点
- 可靠性:使用了一些机制来保证可靠性,比如持久化、生产确认、消费确认机制等。
- 灵活的路由:在消息进入队列之前,通过exchange来路由消息,对于典型的路由功能,RabbitMQ已经提供了一些内置的exchange来实现,针对更复杂的路由功能,可以将多个exchange绑定在一起,可以能通过插件机制来自己实现exchange。
- 消息集群:多个server组成一个集群。
- 高可用:队列可以在集群中的机器上进行镜像,部分节点出问题的情况下队列仍然可用。
- 多种协议:支持多种消息队列协议,如STOMP、MQTT等。
- 多语言客户端:几乎支持所有的常用语言,比如Java、ruby等。
- 管理界面:提供了易用的用户界面,用户可以监控和管理消息。
- 跟踪机制:如果消息异常,RabbitMQ提供了消息的跟踪机制,使用者可以找出发生了什么。
- 插件机制:提供了很多插件,也可以自己实现插件。
常用Exchange介绍
RabbitMQ内置了几种常用的exchange,包含direct、topic、fanout、headers,下面主要介绍几种常用的exchange类型。
direct类型
直连交换机,完全匹配路由key,所有发送到direct exchange的消息会被转发到routeKey中指定的queue中。消息传递时,routeKey必须要完全匹配才会被队列接手,否则消息会被抛弃。
topic类型
主题交换机,根据匹配路由规则来分发消息,#
匹配一个或多个词,*
匹配不多不少一个词。比如“log.#”能够匹配到“log.info.test”,“log.*”能够匹配到“log.err”。
fanout类型
广播交换机,不处理路由,只需要将队列绑定到交换机上,发送到交换机的消息会被转发到与该交换机绑定的所有的队列上。
RabbitMQ集群模式
RabbitMQ提供了三种模式,分别为单机模式、普通集群模式、镜像集群模式,下面分别介绍下这三种模式。
单机模式
这个就是demo级别的,即单机情况不做集群,一般也就是你本地启动玩玩写写测试用的,没有人在生产用这种模式的。
普通集群模式
多台机器上启动多个RabbitMQ实例,以两个节点(node-1,node-2)为例来进行说明。对于queue来说,消息实体只存在于其中一个节点node-1或者node-2,node-1和node-2两个节点仅有相同的元数据(即队列相关的结构)。当消息进入node-1后,consumer从node-2节点消费时,RabbitMQ会临时在node-1、node-2间进行消息传输,把A中的消息取出来并经过B在发送给consumer,所以consumer以ing改尽量连接每一个节点,从中获取消息。负责无论consumer连接node-1或者node-2,出口总在node-1,会产生瓶颈,如果当node-1节点故障后,node-2无法获取node-1的消息。如果做了持久化,那么必须要等到node-1恢复后,才能继续消费,如果消息没有做持久化,就会出小消息丢失的情况。
镜像集群模式
一般配合HAProxy配置为高可用集群,把需要的队列做成镜像队列,存在与多个节点属于 RabbitMQ的HA方案。该模式解决了普通模式中的问题,其实质和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用。
完成镜像队列设置之后,每各队列会被复制到各个节点,各个节点状态保持一致。因为 RabbitMQ本身不提供负载均衡,需要搭建负载均衡器来提供负载转发,可以选择HAProxy 和Nginx。
RabbitMQ面试常问题
- 什么是RabbitMQ?
- 为什么要使用RabbitMQ?
- 使用RabbitMQ的场景?
- 如何保证消息不丢失?
- 如何避免消息的重复投递或者重复消费?
- 如何确保生产者成功发送至RabbitMQ?如何保证消费者成功消费了消息?
- 使用RabbitMQ有什么好处?
- 使用了MQ对系统有什么影响?
还有什么问题评论讨论
1、什么是RabbitMQ?
采用 AMQP 高级消息队列协议的一种消息队列技术,最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦。
2、为什么要使用RabbitMQ?
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能;
- 拥有持久化的机制,进程消息,队列中的信息也可以保存下来;
- 实现消费者和生产者之间的解耦;
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作;
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单;
3、使用RabbitMQ的场景?
- 服务间异步通信;
- 顺序消费;
- 延迟消息;
- 请求削峰;
4、如何保证消息不丢失?
消息持久化,当然前提是队列必须持久化。
5、如何避免消息的重复投递或者重复消费?
在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id
,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;在消息消费时,要求消息体中必须要有一个bizId
(对于同一业务全局唯一,如支付 ID、订单 ID、帖子 ID 等)作为去重的依据,避免同一条消息被重复消费。
6、如何确保生产者成功发送至RabbitMQ?如何保证消费者成功消费了消息?
发送方确认模式
将信道设置成confirm
模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。
一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一 ID)。
如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条 nack(notacknowledged,未确认)消息。
发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。
接收方确认机制
消费者接收每一条消息后都必须进行确认(消息接收和消息确认是两个不同操作)。只有消费者确认了消息,RabbitMQ才能安全地把消息从队列中删除。
这里并没有用到超时机制,RabbitMQ仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,RabbitMQ给了Consumer足够长的时间来处理消息。保证数据的最终一致性;
下面列举几种特殊情况:
- 如果消费者接收到消息,在确认之前断开了连接或取消订阅,RabbitMQ 会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)
- 如果消费者接收到消息却没有确认消息,连接也未断开,则 RabbitMQ认为该消费者繁忙,将不会给该消费者分发更多的消息。
7、使用RabbitMQ有什么好处?
- 服务间高度解耦;
- 异步通信性能高;
- 流量削峰;
8、使用了MQ对系统有什么影响?
系统可用性降低
系统引入的外部依赖越多,越容易挂掉,本来你就是A系统调用BCD三个系统的接口就好了,人ABCD四个系统好好的,没啥问题,你偏加个MQ进来,万一MQ挂了咋整?MQ挂了,整套系统崩溃了,你不就完了么。系统复杂性提高
硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。一致性问题
A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。
写在最后
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,最好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了10倍。但是关键时刻,用,还是得用的。最后希望这篇能给大家带来收获,喜欢加关注,后续持续更新!