文章目录
一、消息中间件
1.1 概念
消息(Message)是什么?
- 是指应用之间传送的数据。比如文本字符串、JSON,内嵌对象等等。
消息队列中间件(Message Queue Middleware,简称MQ)是什么?
- 消息队列中间件,也称作消息队列或消息中间件。是指利用高效可靠的消息传递机制进行与平台无关的数据交流(异步的数据传输),并基于数据通信进行分布式系统的集成。通过提供消息队列模型和消息传递机制,可以在分布式环境下扩展进程间的通信。
两种传递模式:
- 第一种是点对点(P2P,Point-to-Point)模式。该模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接受消息,可用于消息的异步传输。
- 第二种是发布订阅(Pub/Sub)模式。该模式类似redis的发布订阅,定义了如何向内容节点发布和订阅消息,这个内容节点成为主题(类比于redis的频道),主题可以认为是消息传递的中介,消息发布者把消息发布到某个主题,消息订阅者从主题中订阅消息。主题的使用,让消息订阅者和消息发布者可以互相保持独立,不需要进行接触就可以保证消息的传递,该模式常用于消息的一对多广播场景。
常见的开源消息中间件:
- 开源的比较多,比如RabbitMQ、Kafka、ActiveMQ、RockerMQ等等。
- 每种MQ的优点侧重点不同,这里不深入探索比较,因为学习底层原理才能清楚的知道各个厂商自己产品优点在哪儿,适用于什么场景,历史悠远,短篇幅讲不清楚也说不明白。
消息中间件使用场景:
- 消息中间件适用于需要可靠的数据传送的分布式环境。采用消息中间件的系统中,不通的对象之间通过传递消息来激活对方的事件,从而完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在适合的时候再将消息发送给接收者。
- 消息中间件能在不同平台之间通信,它常被用来屏蔽各种平台和协议之间的特性,实现应用程序之间的协同。其优点在于能够在客户和服务器之间提供同步金额异步的连接,并且在任何时刻都可以将消息进行传送或存储转发,这也是它比远程调用更进步的原因。
图文模型解说:
- 执行流程:
- 程序A和程序B在两台服务器上,通过消息中间件的API接口发送消息来进行通信。
- 消息中间件将消息路由给程序B,这样消息就可以存在两个服务器上了。
- 灵活性体现在哪儿?
- 过程中,消息中间件会负责处理网络通信,当网络连接不可用时,消息中间件会存储消息;当网络恢复可用后再将消息转发给程序B。
- 当程序A发送消息时,此时程序B处于不运行状态,消息中间件可以保留这份消息。当程序B再运行时才开始消费消息。
- 这样也防止了程序A因为等待程序B消费小时而出现阻塞。
1.2 作用
1. 解耦。 我们可以在项目启动之前预测将来会遇到什么需求是及其困难的。消息中间件在处理过程中间插入一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这样可以让我们独立扩展或修改两边的处理过程,但需要确保它们遵守相同的接口约束。
2. 冗余(存储)。 当处理数据失败时,消息中间件可以把数据进行持久化,直到数据被处理完成,这样可以规避数据丢失的风险。在把一个消息从消息中间件中删除之前,需要处理系统表明指出该消息已经被处理完成了,之后才会从中间件中删除,从而确保数据可以安全保存直到被使用完毕。
3. 扩展性。 因为消息中间件解耦了应用的处理过程,所以可以提高消息入队和处理的效率,只要另外增加处理过程即可,不需更改代码,也不需调节参数。
4. 削峰。 在访问量剧增的情况下,程序很容易因为突发的超负荷请求而崩溃。而消息中间件能够是关键组件支撑突发访问压力,不会让程序因此崩溃。
5. 可恢复性。 做数据备份,当部分组件失效时,加入到中间件中的消息可以在组件恢复后进行处理。
6. 顺序保证。 大多数情况下的数据处理顺序很重要,大部分消息中间件支持一定程度上的顺序性。
7. 缓冲。 在任何重要的系统中,都会存在需要不同处理时间的元素,消息中间件通过一个缓冲层来帮助任务最高效率地执行,写入消息中间件地处理会尽可能快速,该缓冲层有助于控制和优化数据流经过系统地速度。
8. 异步通行。 在很多时候程序不想也不需要立即处理消息,消息中间件提供了异步处理机制,允许程序把一些消息放入消息中间件中,但并不立即处理它,在之后需要的时候再慢慢处理。
1.2.1 消息队列持久化
就是将队列里的数据存入磁盘,放在内存中会有随着服务器宕机时丢失的风险。
ActiveMQ | RabbitmQ | Kafka | RocketMQ | |
---|---|---|---|---|
文件存储 | 支持 | 支持 | 支持 | 支持 |
数据库 | 支持 | / | / | / |
1.2.2 消息队列分发策略
MQ消息队列主要有三大角色:消息生产者、消息服务节点和消费者。
MQ消费者如何获取消息的?
- 要么推(push),要么拉(pull)。典型的get就有推拉机制,发送的http请求就是一种典型的拉取数据库数据返回的过程。
- 而消息队列MQ是一种推送的过程,而这些推机制会适用到很多的业务场景,所以也有很多对应推机制策略。
- 这里先熟悉见见这些机制是干什么的,混混眼熟,机制原理咱们后面讲。
五种消息推送机制?
- 发布订阅:生产者发送消息到消息队列进行存储,消费者们只要订阅了,就会收到消息队列中的消息。
- 比如MQ中有100条消息,发布订阅机制就会给每个消费者发送100条信息。
- 轮询分发:生产者将消息投递到消息队列,消息队列会按照一定的机制将消息推送给消费者,这种规则是一种公平的分发;并不会因为消费者的延时而造成它的不公平性(不论你的服务器性能怎么样,都会是公平的,消息队列中的消息平均分发,不会造成数据的倾斜),一旦有一个消费者消费了消息,后面的消费者就不会重复的去消费消息;自动应答。
- 比如MQ中有100条消息,有3个消费者分别在不同的3台服务器上,第一台处理最快,第二台适中,第三台最慢。那么到最后这3台服务器各自都会消费33条消息,至于最后1条消息分配给谁,会根据内部机制来。
- 公平分发:公平分发会按照服务器的性能来分配,消费快的机器多分一点,消费慢的机器少分一点(能者多劳)。所以此种方式会造成数据的倾斜;一旦有一个消费者消费了消息,后面的消费者就不会重复的去消费消息;需要手动应答。
- 比如MQ中有100条消息,有3个消费者分别在不同的3台服务器上,第一台处理最快,第二台适中,第三台最慢。MQ会给第一台分配的消息多点,给第三台分配的少点,至于每台服务器最后各自消费了多少条消息咱们后面讲。
- 重发:当消费端出现故障时,消息队列中的应答机制没有收到消费成功的反馈信息,从而会造成消息的堆积,但是堆积的消息不会删除掉,它会重新发送给另一个消费的服务器来进行消费,以此类推;它的目的就是为了保证消息的一个可靠性(KafKa不支持重发)。
- 比如MQ中有100条消息,有3个消费者分别在不同的3台服务器上,第一台处理最快,第二台适中,第三台最慢。当第一台宕机后,消息会给第二台处理,第二台宕机后,会给第三台处理,从而在保证了消息是被消费出去了。
- 消息拉取:rbc通讯机制,消息被动拉取,不多讲RabbitMQ用的很少很少。
ActiveMQ | RabbitmQ | Kafka | RocketMQ | |
---|---|---|---|---|
发布订阅 | 支持 | 支持 | 支持 | 支持 |
轮询发布 | 支持 | 支持 | 支持 | / |
公平发布 | / | 支持 | 支持 | / |
重发 | 支持 | 支持 | / | 支持 |
消息拉取 | / | 支持 | 支持 | 支持 |
1.2.3 消息队列的高可用和高可靠
1.2.3.1 一主多从共享集群
高可用性:
- 当消息发送到MQ的主节点时,Master节点负责将数据写入硬盘或数据库,所有的从节点都可以从里面拿取数据,先取先得。
- 当Master挂掉后,slave节点也可以继续服务。从而形成高可用。
缺点:
- 主节点挂掉,生产者无法写入消息。
- 存储服务器宕机,数据丢失风险大,即使可以视情况恢复硬盘里的数据。
1.2.3.2 一主多从同步集群
高可用性:
- 当消息发送到MQ的主节点时,Master节点将所有数据拷贝到每一个从节点。当有多个消费者时,消费者就可以去不同的节点进行消费。
缺点:
- 占用带宽和网络资源。
- 同样的master节点挂了,无法写入。
1.2.3.3 多主多从同步集群
高可用性:
- 给每个MQ节点写入相同的消息,和上面的模型的区别不大。
1.2.3.4 多主转发集群
高可用性:
- 生产者给每个MQ服务器发送全部消息标签,里面包含了交换器名称、队列名称等信息,同时还会根据策略分发消息体,所以每个MQ上的数据都不一样(消息体),但消息的标签一样。
- 当消费者去第一台MQ机器上获取消息时,若该台服务器上有消息体则直接被消费;若没有则将转发请求给其他MQ主机,根据消息标签挨个寻找那个消息体,直到找到为止。
优点:
- 占用资源较少,高可靠。
1.2.3.5 Master-slave与Breoker-cluster组合方案
高可用性:
- 实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高。
缺点:
- 烧钱…
1.3 初识AMQP协议
因为各种消息中间件的出现,也衍生出许多为MQ而用的协议。那rabbitmq为什么会采用AMQP协议呢?其实AMQP协议是基于TCP\IP协议之上加工演变出来的一种协议。至于加了什么?为什么加?加之后的优点实现原理这里不详细谈,大家可以通过源码书籍来巩固完善自己的知识体系。首先我们需要知道什么是协议?
协议是什么?
- 协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。而建立协议需包含三要素:语法、语义、时序。
- 协议总是指某一层的协议。准确地说,它是在同等层之间的实体通信时,有关通信规则和约定的集合就是该层协议,例如物理层协议、传输层协议、应用层协议。
网络协议三要素:
1. 语法: 是用户数据与控制信息的结构与格式,以及数据出现的顺序。
2. 语义: 是解释控制信息与个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
3. 时序: 是对事件发生顺序的详细说明。为什么消息中间件不直接使用http协议?
- http请求报文头和响应报文头比较复杂,包含了cookie,数据的加密解密,状态码,响应码等附加的功能。但是对于一个消息而言,它就是负责数据传递,存储,分发功能。我们不需要搞这么复杂多此一举。一定要追求的是高性能,简洁快速。
- 大部分情况下http大部分都是短链接,在实际的交与过程中,一个请求到响应很有可能会中断,中断以后就不会进行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息进行持久化,就是为了保证消息和数据的高可靠和稳健的运行。
AMQP 协议有哪三层?
- 第一层Module Layer, 位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以利用这些命令实现自己的业务逻辑。例如,客户端可以使用 Queue.Declare 命令声明一个队列或者使用 Basic.Consume 订阅消费一个队列中的消息。
- 第二层Session Layer,位于中间层,主要负责将客户端的命令发送给服务器,再将服务端的应答返回给客户端,主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。
- 第三层Transport Layer,位于最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
消息中间件采用的协议类型有哪些?
1. AMQP协议: 全称叫做Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。
- 优点:可靠、通用。
- 支持者:RbbitMQ、ActiveMQ。
2. MQTT协议: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM 开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter 让房屋联网)的通信协议。
- 优点:轻量、格式简单、传输快、不能持久化、占用带宽小、嵌入式系统。
- 支持者:RbbitMQ、ActiveMQ。
3. STOMP协议: STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP 提供一个可互操作的连接格式,允许客户端与任意STOMP 消息代理(Broker)进行交互。(优点:命令模式(非topic\queue 模式)。)
4. XMPP协议: XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML 流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。(优点:通用公开、兼容性强、可扩展、安全性高,但XML 编码格式占用带宽大。)
5. 其他基于TCP/IP 自定义的协议: 有些特殊框架,比如Redis、kafka、zeroMq等根据自身需要未严格遵循MQ 规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接口进行传输,实现了MQ的功能。
二、RabbitMQ基础了解
2.1 发展起源
RabbitMQ是什么?
- RabbitMQ 是采用 Erlang 语言实现 AMQP (Advanced Message Queuing Protocol,高级消息队列协议) 的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。
RabbitMQ从何而来?
- 1983年,一个印度人Vivek Ranadive突发奇想,觉得软件系统中也应该有类似硬件总线的东西,可以把其它应用接到软件总线上,方便地实现应用间消息通信的工作。因此他创办Teknekron公司。
- 1985年,开始用于金融交易。发布订阅模式,用软件总线来处理各种不同类型的信息,只需要一个终端来接入软件总线,那么就可以订阅想要的消息了。同时诞生了第一个消息队列软件TIB(The Information Bus)。很快,这种数据传输模式便得到更广泛的应用,并进入了电信、新闻行业。1994年,路透社收购了Teknekron公司。
- 80年代后期,IBM开始研究自己的消息队列软件,1993年IBM MQ产品系列面世,之后的17年,MQ系列进化成了WebSphere MQ并统治着商业消息队列市场。那段时间,TIB也一直活在,后来更名为Rendezvous。
- 1997年,Teknekron以TIBCO的形式作为一家独立公司再度出现。同年,微软也开发了自己的消息通信软件MSMQ。
- 2001年,JMS诞生。因为那些公司的中间件价格昂贵,一般只应用于大型组织机构,它们需要可靠性、解耦及实时消息通信的功能。由于商业壁垒,商业 MO 供应商想要解决应用互通的问题,而不是去创建标准来实现不同的 MO 产品间的互通,或者允许应用程序更改 MQ 平台。为了打破这个壁垒,使得同一个应用可以消费不同的MQ产品,JMS就这样产生了。
- JMS 通过提供公共 Java API 的方式,隐藏单独 MQ 产品供应商提供的实际接口,解决了互通问题。从技术上讲,Java 应用程序只需针对JMS API 编程,选择合适的 MO 驱动即可,JMS 会打理好其他部分。ActiveMO 就是JMS的一种实现。不过尝试使用单独标准化接口来胶合众多不同的接口,最终会暴露出问题,使得应用程序变得更加脆弱。所以急需一种新的消息通信标准化方案。
- 2004年,JPMorgan Chase和iMatix合作开发了AMQP,这是一个开放标准,任何人都可以执行这一标准,针对这一标准编码的任何人都可以和任意AMQP供应商提供的MQ交互。
- 2006年,伦敦金融部门的企业家Alexis和Matthias等人成立了Rabbit Technologies公司来开发RabbitMQ。大家都觉得Rabbit这个名字不错,毕竟rabbit行动非常迅速,而且繁殖起来也很疯狂。刚好这时候AMQP草案也公开了,由于采用Erlang语言,让他们能快速开发并跟上AMQP标准前进的节奏。
- 2010年4月,被 SpringSource (VMWare的一个部门)收购,在 2013 年 5 月并入 Pivotal, 其实 VMWare、Pivotal 和 EMC 本质上是一家。不同的是 VMWare 是独立上市子公司,而 Pivotal 是整合了 EMC 的某些资源,现在并没有上市。
RabbitMQ 最初版本实现了 AMQP 的一个关键特性: 使用协议本身就可以对队列和交换器(Exchange) 这样的资源进行配置。对于商业 MQ 供应商来说,资源配置需要通过管理终端的特定工具才能完成。RabbitMQ 的资源配置能力使其成为构建分布式应用的最完美的通信总线,特别有助于充分利用基于云的资源和进行快速开发。RabbitMQ 特点:
- 可靠性。 RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认及发布确认等。 灵活的路由:在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ已经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。
- 扩展性。 多个 RabbitMQ 节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
- 高可用性。 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
- 支持多种协议。RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MOTT 等多种消息4中间件协议。
- 支持多语言。RabbitMQ 几乎支持所有常用语言,比如 Java、Python、Ruby、PHPC#、JavaScript 等。
- 管理界面。RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。
- 插件机制。RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。
2.2 模型架构
- RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。
- 我们可以把消息传递的过程想象成“寄快递”,“物流中心”会暂存并最终将物品通过快递员送到收件人的手上。而RabbitMQ就类似由发件人、物流中心和快递员组成的一个系统。算机术语层面来说,RabbitMQ模型更像是一种交换机模型。
- 从下面模型图中得出看出,过程中有多个组成部分:生产者、消费者和消息队列。消息队列里由交换器、路由键以及两者之间的绑定关系。
2.2.1 生产者(Producer)
- 生产者(Producer),就是投递消息的一方。
- 生产者创建消息,然后发布到 RabbitMQ 中。消息一般可以包含 2 个部分: 消息体和标签(Label)。
- 消息体,也称为有效载体(payload)。在实际应用中,消息体一般是一个带有业务逻辑结构的数据,比如一个 JSON 字符串,也可以进一步对这个消息体进行序列化操作。
- 消息标签,则用来表述这条消息,比如一个交换器的名称和一个路由键。当生产者把消息交给RabbitMQ之后,它会根据这个标签把消息发送给对该标签“感兴趣”的消费者 (Consumer)。
2.2.2 消费者(Consumer)
- 消费者(Consumer),就是接收消息的一方。
- 消费者连接到 RabbitMQ 服务器,并订阅到队列上。当消费者消费一条消息时,只是消费消息的中那个带有数据的消息体,并非消息标签。
- 在消息路由的过程中,消息标签会被丢弃。存入到队列中的消息只有消息体,消费者也只知道这条消费中的消息体,也就不知道消息的生产者是谁,因为此时消息的标签已经被丢了。而且消费者也不需要知道生产者是谁。
- 那么这个过程中,具体是谁在处理呢?消息中间件的服务节点Broker。
- Broker是什么?
- 对于 RabbitMQ来说,一个RabbitMQ Broker可以简单地看作一个 RabbitMQ服务节点或者RabbitMQ服务实例。大多数情况下也可以将一 RabbitMQ Broker看作一台 RabbitMQ服务器。
- 消息队列的运转流程:
- 生产者先将业务原始数据进行可能的包装,之后封装成消息,再发送 (AMQP 协议里这个动作对应的命令为 Basic.Publish) 到 Broker 中。
- 消费者订阅并接收消息 (AMQP 协议里这个动作对应的命令为Basic.Consume 或者Basic.Get),经过可能的解包处理得到原始的数据之后再进行业务处理逻辑。
- 处理业务的线程并不一定是要和接收消息的线程共用同一个线程。消费者进程可以使用一个线程去接收消息存入到内存中,比如使用Java中的BlockingQueue业务处理逻辑是用另一个线程从内存中读取数据,这样可以将应用进一步解耦,提高整个应用的处理效率。
2.2.3 队列(Queue)
- 队列(Queue),是RabbitMQ的内部对象,用于存储消息。
- RabbitMQ中消息都只能存储在队列中,这一点和 Kafka 这种消息中间件相反。Kafka 将消息存储在 topic (主题)这个逻辑层面,而相对应的队列逻辑只是 topic 实际存储文件中的位移标识。RabbitMQ的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
- 多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理。
- RabbitMQ不支持队列层面的广播消费,如果需要广播消费,需要进行二次开发,那样的处理逻辑会变得异常复杂,这跟我们是用RabbitMQ中间件背道而驰,不建议这么做。
2.2.4 交换器(Exchange)
- 交换器(Exchange: ),我们可以把RabbitMQ中的交换器看作一个简单的实体。生产者将消息发送到RabbitMQ中间件中的Exchange (交换器,通常也可以用大写的“X”来表示),由交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许直接丢弃。
- RabbitMQ中的交换器由四种类型,不同类型有不同的路由策略,后面再详细介绍。
2.2.4.1 fanout交换器
作用:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。
2.2.4.2 direct交换器
- 作用:direct类型的交换器路由规则简单,它会把消息路由到BindingKey和RoutingKey完全匹配的队列中。
- 如下图示例:
- 示例中,direct类型交换器与队列1的绑定键是warning,与队列2的绑定键是info、warning和debug三种。
- 当我们发送一条消息时,设置路由键为“warning”,那么路由键会匹配到队列1和队列2的绑定键warning,则消息会路由到 Queue_1和 Oueue_2。
- 当我们发送一条消息时,设置路由键为“info”或“debug”,那么路由键只能匹配当队列2的绑定键info和debug,消息只会路由到 Queue_2。
- 当我们发送一条消息时,以其他的路由键发送消息,则消息不会路由到这两个队列中。
2.2.4.3 topic交换器
topic 类型的交换器在匹配规则,是在direct 类型的交换器路由规则上进行了扩展,它与 direct类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同。
- 约定RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如“comrabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
- 约定BindingKey 和 RoutingKey 一样也是点号“”分隔的字符串;但是BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“#”用于匹配一个单词,“ * ”用于匹配多规格单词 (可以是零个)。
- 下图为例:
- 路由键为“com.qingjun.baimu”的消息会同时路由到 Queue_1和 Queue_2中。
- 路由键为“com.hidden.baimu”的消息只会路由到 Queue_2 中。
- 路由键为“com.beijing.demo”的消息只会路由到Queue_2中。
- 路由键为“wuhan.qingjun.demo”的消息只会路由到 Queue_1中。
- 路由键为“javautil.concurrent”的消息将会被丢弃或者返回给生产者 (需要设置mandatory 参数),因为它没有匹配任何路由键。
2.2.4.4 headers交换器
- headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。
- 在绑定队列和交换器时制定一组键值对,当发送消息到交换器时RabbitMQ会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对。如果完全匹配则消息会路由到该队列,否则不会路由到该队列。
- headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
2.2.5 路由键(RoutingKey)
- 路由键(RoutingKev)。生产者将消息发给交换器的时候,一般会指定一个 RoutingKey,用来指定这个消息的路由规则,而路由键需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
- 在交换器类型和绑定键(BindingKey) 固定的情况下,生产者可以在发送消息给交换器时通过指定路由键来决定消息流向哪里。
2.2.6 绑定键(BindingKey)
- 平时大家听到的都是路由键(RoutingKey),绑定键(BingingKey)一词很少出现在大家视野中。可以说两者在某些情况下是同一个东西,某些情况下又有些区别,这跟交换器的类型使用有关。
- RabbitMQ中通过绑定键将交换器与队列关联起来,在绑定的时候一般会指向一个绑定键,这样RabbitMQ才能正确地将消息路由到队列里。
- 生产者将消息发送给交换器时,需要一个路由键,当绑定键和路由键相匹配时,消息会被路由到对应的队列中。
- 当一个交换器绑定多个队列时,可以使用相同的绑定键。
- 绑定键并不是在所有的情况下都生效,它依赖于交换器类型。比如 fanout类型的交换器就会无视 BindingKey,而是将消息路由到所有绑定到该交换器的队列中。
路由键和绑定键的区别:
- 绑定键,官方解释为: the routing key to use for the binding,在绑定的时候使用的路由键。
- direct类型交换器里的RoutingKey 和 BindingKey就是同一个东西。
- topic类型交换器里的RoutingKey 和 BindingKey 之间需要做模糊匹配,两者并不是相同的。
- BindingKey 其实也属于路由键中的一种,
- 在使用绑定的时候,其中需要的路由键是 BindingKey。
- 在发送消息的时候,其中需要的路由键是 RoutingKey。
2.2.7 连接与信道(Connection、Channel)
- 无论是生产者还是消费者,都需要和消息服务节点RabbitMQ Broker建立连接,这个连接就是一条 TCP 连接,也就是Connection。
- 当TCP 连接建立起来,客户端紧接着可以创建一个 AMQP 信道(Channel),每个信道都会被指派一个唯一的ID。
- 信道是建立在 Connection 之上的虚拟连接,RabbitMQ 处理的每条 AMQP 指令都是通过信道完成的。
为什么是用信道?
- 可以减少性能开销,同时也便于管理。
- 若一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么必然需要建立很多个 Connection,也就是许多个 TCP 连接。然而对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。RabbitMQ 采用类似 NIO’(Non-blocking I/0) 的做法,选择 TCP 连接复用,每个线程保持一个信道,所以信道复用了 Connection 的 TCP 连接。
- 可以确保每个线程的私密性,就像拥有独立的连接一样。
- 当每个信道的流量不是很大时,复用单一的Connection 可以在产生性能瓶颈的情况下有效地节省 TCP 连接资源。但是当信道本身的流量很大时,这时候多个信道复用一个 Connection 就会产生性能瓶颈,进而使整体的流量被限制了。此时就需要开辟多个 Connection,将这些信道均摊到这些 Connection 中,
什么是NIO?
- NIO,也称非阻塞 I/0,包含三大核心部分: Channel (信道)、Buffer (缓冲区)和 Selector (选择器)。NIO 基于 Channel 和Buer 进行操作,数据总是从信道读取数据到缓冲区中,或者从缓冲区写入到信道中。Selector 用于监听多个信道的事件(比如连接打开,数据到达等)。因此,单线程可以监听多个数据的信道。
2.3 RabbitMQ运转流程
生产者发送消息的过程:
数据传输前:
- 生产者连接到RabbitMQ Broker,建立一个连接(Connection),然后开启一个信道(Channel)。
- 生产者声明一个交换器,并设置相关属性。比如设置交换器类型、是否持久化等等。
- 生产者声明一个队列,并设置相关属性。比如是否持久化、是否自动删除等等。
- 生产者通过路由键将交换器和队列绑定起来,此时这个路由键成为绑定键。
数据进入时:
- 生产者发送消息到RabbitMQ Broker,其中包含了路由键、交换器等信息。
- 相应的交换器根据接收到的路由键与绑定键匹配查看对应的队列。
- 当规则匹配中了,则将生产者发送过来的消息存入相应的队列中。
- 若没匹配中,则根据生产者配置的属性选择丢弃还是回退给生产者。
- 关闭信道。
- 关闭连接。
消费者接收消息的过程:
- 消费者连接到 RabbitMQ Broker,建立一个连接(Connection),开启一个信道(Channel)。
- 消费者向 RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数以及做一些准备工作。
- 等待 RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息。
- 消费者确认 (ack) 接收到的消息。
- RabbitMQ 从队列中删除相应已经被确认的消息。
- 关闭信道。
- 关闭连接。
2.4 AMQP协议命令的流转过程
2.4.1 生产者流转过程
- 当客户端与 Broker 建立连接的时候,会调用 factory.newConnection 方法进一步封装成 Protocol Header 0-9-1 的报文头发送给 Broker,以此通知 Broker 本次交互采用的是AMQP0-9-1 协议。
- Broker 返回 connection.start 来建立连接,在连接的过程中涉及Connection.Start/.Start-OK、Connection.Tune/.Tune-Ok、Connection.
Open/.Open-0k 这6 个命令的交互。- 当客户端调用 connection.createchannel 方法准备开启信道的时候,其包装Channel.Open 命令发送给 Broker,等待 channel.Open-ok 命令。
- 当客户端发送消息的时候,需要调用 channel.basicPublish 方法,对应的 AOMP 命令为 Basic.Publish,注意这个命令和前面涉及的命令略有不同,这个命令还包含了 ContentHeader 和 Content Body。Content Header 里面包含的是消息体的属性,例如,投递模式、优先级等,而 Content Body 包含消息体本身。
- 当客户端发送完消息需要关闭资源时,涉及 Channel.close/.close-ok 与Connection.close/.close-ok 的命令交互。
2.4.2 消费者流转过程
- 当客户端与 Broker 建立连接的时候,会调用 factory.newConnection 方法进一步封装成 Protocol Header 0-9-1 的报文头发送给 Broker,以此通知 Broker 本次交互采用的是AMOP0-9-1 协议。
- Broker 返回 connection.start 来建立连接,在连接的过程中涉及Connection.Start/.Start-OK、Connection.Tune/.Tune-Ok、Connection.
Open/.Open-0k 这6 个命令的交互。- 消费者客户端向Broker发送 Basic.Consume 命令 (即调用channel.basicConsume 方法) 将 channel 置为接收模式。
- Broker 回复Basic.consume-ok 以告诉消费者客户端准备好消费消息。紧接着 Broker 向消费者客户端推送(Push) 消息,即 Basic.Deliver 命令,有意思的是这个和 Basic.Publish 命令一样会携带 Content Header 和 Content Body。
- 消费者接收到消息并正确消费之后,向 Broker 发送确认,即 Basic.Ack 命令。
- 在消费者停止消费的时候,主动关闭连接,这点和生产者一样,涉及Channel.Close/.Close-ok和Connection.Close/.Close-Ok。