消息中间件是基于队列与消息传递技术,在网络环境中为应用系统提供同步或异步、可靠的消息传输的支撑性软件系统
什么是MQ?
MQ的定义
用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息队列模型,可以在分布式环境下扩展进程的通信。
MQ的主要特性
- 是一个独立运行的服务。生产者发送消息,消费者接收消息并消费,需要先跟服务器建立连接。
- 采用队列作为数据结构,有先进先出的特点。
- 具有发布订阅(publis/subscribe)的模型,消费者可以获取自己需要的消息。
MQ的作用
-
异步
- 同步通信:发出一个调用请求之后,在没有得到结果之前,就不返回。由调用者主动等待这个调用的结果
- 异步通信:异步与同步是相反的,调用在发出之后,这个调用就直接返回了,所以没有返回结果。也就是说,当一个异步过程调用发出后,调用者不会马上得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用
例子:银行转账
-
解耦
系统耦合示意图:

// 伪代码
public void returnGoods(){
stockService.updateInventory();
payService.refund();
noticeService.notice();
}
可以看到,如果没有用到MQ的时候,如果做一个取消订单的操作。代码是依次执行的,如果有任何一行报错,那么下面的代码将无法执行,其实,更新库存、退回金额、通知消息等操作是没有先后顺序的。当然,也可以用多线程来实现同时执行多个操作。但是线程池的管理本身也是一个比较麻烦的事情。这时候就体现了 MQ的好处
引入 MQ 以后的示意图

引入MQ之后,订单系统只需要配置一个MQ的ip地址即可,不需要关心其他子系统。将消息发送给MQ之后,接下来就是由各个下游的业务系统自己创建队列,然后监听队列消费消息。如果以后系统需要加一个记录日志的系统,那么订单系统也不需要改代码。日志系统直接实现消息队列的接口就可以了。这样,我们就实现了系统之间依赖关系的解耦
-
削峰(流量削峰)
第三个主要功能,是实现流量削峰。
在很多的电商系统里面,有一个瞬间流量达到峰值的情况,比如京东的618,淘宝的双11,还有小米抢购。普通的硬件服务器肯定支撑不了这种百万或者千万级别的并发量,就像2012 年的小米一样,动不动服务器就崩溃。如果通过堆硬件的方式去解决,那么在流量峰值过去以后就会出现巨大的资源浪费。那要怎么办呢?如果说要保护我们的应用服务器和数据库,限流也是可以的,但是这样又会导致订单的丢失,没有达到我们的目的。
为了解决这个问题,我们就可以引入MQ,MQ 既然是队列,一定有队列的特性,我们知道队列的特性是什么?(先进先出FIFO)
这样,我们就可以先把所有的流量承接下来,转换成MQ 消息发送到消息队列服务器上,业务层就可以根据自己的消费速率去处理这些消息,处理之后再返回结果。就像我们在火车站排队一样,大家只能一个一个买票,不会因为人多就导致售票员忙不过来。如果要处理快一点,大不了多开几个窗口(增加几个消费者)
为什么要用MQ?
- 对于数据量大或者处理耗时长的操作,我们可以引入MQ 实现异步通信,减少客户端的等待,提升响应速度
- 对于改动影响大的系统之间,可以引入MQ 实现解耦,减少系统之间的直接依赖。
- 对于会出现瞬间的流量峰值的系统,我们可以引入MQ 实现流量削峰,达到保护应用和数据库的目的。
使用MQ(消息队列)带来的一些问题
- 系统可用性降低:原来是两个节点的通信,现在还需要独立运行一个服务,如果MQ服务器或者通信网络出现问题,就会导致请求失败。
- 系统复杂性提高: 为什么说复杂?第一个就是你必须要理解相关的模型和概念,才能正确地配置和使用MQ。第二个,使用MQ 发送消息必须要考虑消息丢失和消息重复消费的问题。一旦消息没有被正确地消费,就会带来数据一致性的问题。
所以,我们在做系统架构的时候一定要根据实际情况来分析,不要因为我们说了这
么多的MQ 能解决的问题,就盲目地引入MQ
RabbitMQ简介
基本特性
- 高可靠:RabbitMQ 提供了多种多样的特性让你在可靠性和性能之间做出权衡,包括持久化、发送应答、发布确认以及高可用性。
- 灵活的路由:通过交换机(Exchange)实现消息的灵活路由。
- 支持多客户端:对主流开发语言(Python、Java、Ruby、PHP、C#、JavaScript、Go、Elixir、Objective-C、Swift 等)都有客户端实现。
- 集群与扩展性:多个节点组成一个逻辑的服务器,支持负载。
- 高可用队列:通过镜像队列实现队列中数据的复制
- 权限管理:通过用户与虚拟机实现权限管理
- 插件系统:支持各种丰富的插件扩展,同时也支持自定义插件。
- 与Spring 集成:Spring 对AMQP 进行了封装。
AMQP(Advanced Message Queuing Protocol)协议
AMQP:高级消息队列协议,是一个工作于应用层的协议,最新的版本是1.0 版本。

除了RabbitMQ 之外,AMQP 的实现还有OpenAMQ、Apache Qpid、Redhat
Enterprise MRG、AMQP Infrastructure、ØMQ、Zyre
除了AMQP 之外, RabbitMQ 支持多种协议, STOMP 、MQTT 、HTTP and
WebSockets
可以使用WireShark 等工具对RabbitMQ 通信的AMQP 协议进行抓包。
工作模型
由于RabbitMQ 实现了AMQP 协议,所以RabbitMQ 的工作模型也是基于AMQP的。理解这张图片至关重要

-
Broker
我们要使用RabbitMQ 来收发消息,必须要安装一个RabbitMQ 的服务,可以安装在Windows 上面也可以安装在Linux 上面,默认是5672 的端口。这台RabbitMQ 的服务器我们把它叫做Broker,中文翻译是代理/中介,因为MQ 服务器帮助我们做的事情就是存储、转发消息。 -
Connection
无论是生产者发送消息,还是消费者接收消息,都必须要跟Broker 之间建立一个连接,这个连接是一个TCP 的长连接 -
Channel
如果所有的生产者发送消息和消费者接收消息,都直接创建和释放TCP 长连接的话,对于Broker 来说肯定会造成很大的性能损耗,因为TCP 连接是非常宝贵的资源,创建和释放也要消耗时间
所以在AMQP 里面引入了Channel 的概念,它是一个虚拟的连接。我们把它翻译成通道,或者消息信道。这样我们就可以在保持的TCP 长连接里面去创建和释放Channel,大大了减少了资源消耗。另外一个需要注意的是,Channel 是RabbitMQ 原生API 里面的最重要的编程接口,也就是说我们定义交换机、队列、绑定关系,发送消息消费消息,调用的都是Channel 接口上的方法 -
Queue
现在我们已经连到Broker 了,可以收发消息了。在其他一些MQ 里面,比如ActiveMQ 和Kafka,我们的消息都是发送到队列上的。队列是真正用来存储消息的,是一个独立运行的进程,有自己的数据库(Mnesia)。
消费者获取消息有两种模式,一种是Push 模式,只要生产者发到服务器,就马上推送给消费者。另一种是Pull 模式,消息存放在服务端,只有消费者主动获取才能拿到消息。消费者需要写一个while 循环不断地从队列获取消息吗?不需要,我们可以基于事件机制,实现消费者对队列的监听。
由于队列有FIFO 的特性,只有确定前一条消息被消费者接收之后,才会把这条消息
从数据库删除,继续投递下一条消息 -
Exchange
在RabbitMQ 里面永远不会出现消息直接发送到队列的情况。因为在AMQP 里面引入了交换机(Exchange)的概念,用来实现消息的灵活路由。
交换机是一个绑定列表,用来查找匹配的绑定关系。
队列使用绑定键(Binding Key)跟交换机建立绑定关系。
生产者发送的消息需要携带路由键(Routing Key),交换机收到消息时会根据它保
存的绑定列表,决定将消息路由到哪些与它绑定的队列上。
注意:交换机与队列、队列与消费者都是多对多的关系 -
Vhost
我们每个需要实现基于RabbitMQ 的异步通信的系统,都需要在服务器上创建自己
要用到的交换机、队列和它们的绑定关系。如果某个业务系统不想跟别人混用一个系统,怎么办?再采购一台硬件服务器单独安装一个RabbitMQ 服务?这种方式成本太高了。
在同一个硬件服务器上安装多个RabbitMQ 的服务呢?比如再运行一个5673 的端口?
没有必要,因为RabbitMQ 提供了虚拟主机VHOST。
VHOST 除了可以提高硬件资源的利用率之外,还可以实现资源的隔离和权限的控
制。它的作用类似于编程语言中的namespace 和package,不同的VHOST 中可以有
同名的Exchange 和Queue,它们是完全透明的。
这个时候,我们可以为不同的业务系统创建不同的用户(User),然后给这些用户
分配VHOST 的权限。比如给风控系统的用户分配风控系统的VHOST 的权限,这个用户可以访问里面的交换机和队列。给超级管理员分配所有VHOST 的权限。
我们说到RabbitMQ 引入Exchange 是为了实现消息的灵活路由,到底有哪些路由
方式?
消息队列的路由方式
1. 直连 Direct
队列与直连类型的交换机绑定,需指定一个精确的绑定键。
生产者发送消息时会携带一个路由键。只有当路由键与其中的某个绑定键完全匹配时,这条消息才会从交换机路由到满足路由关系的此队列上。多个队列也可以使用相同的绑定键。

例如 : channel.basicPublish(“MY_DIRECT_EXCHANGE”,”spring”,”msg 1”); 只有第一个队列能收到消息。
2.主题 Topic
队列与主题类型的交换机绑定时,可以在绑定键中使用通配符。两个通配符:
- (#号代表 0 个或者多个单词)
- (* 不多不少一个单词)
单词(word)指的是用英文的点“.”隔开的字符。例如 abc.def 是两个单词。

第一个队列支持路由键以 abc 开头的消息路由,后面可以有单词,也可以没有。
第二个队列支持路由键以 java 开头,并且后面是一个单词的消息路由。
第三个队列支持路由键以 jvm 结尾,并且前面是一个单词的消息路由。
例如:
channel.basicPublish(“MY_TOPIC_EXCHANGE”,“abc.fjd.klj”,“msg 2”); 只有第一个队列能收到消息。
channel.basicPublish(“MY_TOPIC_EXCHANGE”,“abc.jvm”, “msg 3”); 第一个队列和第三个队列能收到消息。
3.广播 Fanout
广播类型的交换机与队列绑定时,不需要指定绑定键。因此生产者发送消息到广播类型的交换机上,也不需要携带路由键。消息达到交换机时,所有与之绑定了的队列,都会收到相同的消息的副本。

例如:
channel.basicPublish(“MY_FANOUT_EXCHANGE”, “”, “msg 4”); 三个队列都会收到 msg 4。
本文详细阐述了RabbitMQ的基本概念,如MQ模型、异步通信、解耦与流量削峰的应用,以及RabbitMQ的核心特性如高可靠、交换机路由和权限管理。讨论了为何使用MQ以及其带来的问题和挑战,最后聚焦于RabbitMQ实例,包括Exchange的路由方式和VHOST的权限隔离。
172万+

被折叠的 条评论
为什么被折叠?



