Web-Rabbit MQ-知识体系目录
一、MQ消息队列背景
1. 定义
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器,多用于分布式系统间通信(分布式通讯两种方式:直接远程调用和第三方间接通讯)。
2. 作用
1. 应用解耦
将系统间分割开来,解除其关联性,降低耦合型。
2. 异步提速
原来同步操作时可能需要全部执行完了再回复,现在只需要送到MQ然后就回复,不用再等待,剩下交给其它系统异步处理。
3. 削峰填谷
在高峰期,请求量增大,使用了MQ之后,消息存放在消息队列,限制消费消息的速度为1000,减轻了压力,这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“"削”掉了;但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。
3. 缺点
1. 系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响,可以搭集群。
2. 系统复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。如何保证消息没有被重复消费?怎么处理消息丟失情况?那么保证消息传递的顺序性?
3. 一致性问题
A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败。原本这几个是要同步完成已成功都成功,不成功需要回滚的,如何保证消息数据处理的一致性?
4. 使用场景
- 生产者不需要从消费者处获得反馈。也就是说可以不用等待处理完了(同步)再返回结果,这样可以实现异步。
- 容许短暂的不一致性,比如网购成功反馈。
- 解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。
5. 常见MQ产品
二、AMPQ
0. 参考文献
1. 定义
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP规范发布。
2. AMQP模型
关于模型的介绍见参考文献,下面仅总结
1. 过程
生产者赋予消息一些属性,并发送给交换机,不同的交换机根据不同的路由规则将消息路由给相应的队列,消费者通过某些属性消费队列中的消息。
路由的过程就是将【消息中的路由键 (routing Key)】与【交换机、队列绑定的路由键(binding Key)】进行匹配的过程。
2. 交换机
AMQP 0-9-1的代理中提供了四种交换机:
- Direct exchange(直连交换机)
- Fanout exchange(扇型交换机)
- Topic exchange(主题交换机)
- Headers exchange(头交换机)
区别主要是,交换机和队列之间【消息路由规则】不同,详细可以参考中文文档
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
1. 默认交换机
由消息代理预先声明好的没有名字(名字为空字符串)的直连交换机,每一个新建的队列都会与之绑定,路由键就是队列名称(默认连接所有队列,默认队列名字为 BindingKey
的直连交换机)。
2. 直连交换机
根据消息中的路由键,将消息发送至所有binding Key= routing key
的队列中,完全匹配型。
3. 扇形交换机
不管路由键,无脑广播至所有与之绑定的队列中。
4. 主题交换机
用.
分割,用#
模糊匹配零个、一个或多个单词,如a.b.#,a.#.b等;
5. 头交换机
根据消息头来绑定,具体看文章
三、RabbitMQ
可以理解为一个数据库软件
1. 简介
Rabbit技术公司基于AMQP标准,采用Erlang语言开发的开源消息代理软件。07年,RabbitMQ1.0发布。消息中间件软件就像数据库一样,所以可以类比数据库。 RabbitMQ提供了比较便于操作的用户界面,可以在网站上访问。
2. 安装
具体安装过程可以参考网上的。
3. RabbitMQ 基础架构
连接数据库时需要输入用户名、密码、ip地址、端口、数据库,连接消息中间件时也需要用户名、密码、IP地址、端口、Virtual host。
Virtual host理解为数据库,队列理解为表;不同的登录用户拥有对不同(Virtual host)的不同操作权限,刚安装时同样有个默认用户名和密码作为超级管理员。
- Broker: 接收和分发消息的应用, RabbitMQ Server就是 Message Broker。
- Virtual host: 出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个 RabbitMQ server提供的服务时,可以划分出多个host,每个用户在自己的vhost创建 exchange/queue等。
- Connection :publisher/ consumer和 broker之间的TCP连接。
- Channel: 如果每一次访问 RabbitMQ都建立一个Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。 Channel是在 connection內部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread创建单独的 channel进行通讯, AMQP method包含了 channel id帮助客户端和message broker识别 channe,所以 channel之间是完全隔离的。 Channel作为轻量级的 Connection 极大减少了操作系统建立 TCP connection的开销,Channel类提供了大量的API创建队列、交换机、消费规则等,可以查看RabbitMQ Java客户端API指南。
- Exchange: message到达 broker的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue中去。常用的类型有:direct( point-to- point), topIc( publish- subscribe) and fanout( multicast )。
- Queue: 消息最终被送到这里等待 consumer取走。
- Binding: exchange和 queue之间的虚拟连接, binding中可以包含 routing key. Binding信息被保存到exchange中的查询表中,用于message的路由依据。
- routing key: 消息的属性,生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效,扇形交换机是无脑对连接的队列全部路由,不需要这个。
- Binding key:在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。
MQ代理 | 数据库软件 |
---|---|
Virtual host | 数据库 |
4. 消息传递流程
1. 连接到代理
- 生产方想发送消息,消费方想消费消息,就好像存取数据库的数据需要先连接数据库软件一样,必须先连接到MQ代理软件,建立连接和通道。需要借助的API类是创建
Connection
和Channel
对象。 - 一般每个客户端对应一个channel,通过channel既可以消费消息(消费者)又可以发送消息(生产者),但通常一个客户连接要么作为消费者要么作为生产者。
- 一个Channel基本上囊括了绝大部分MQ相关的操作,创建队列/交换机、消费消息、发送消息、确认消息等等。
连接的代码
连接到消息中间件,消费端和生产端一样的,指定ip
、端口
、用户名
、密码
、Virtual host
,RabbitMQ提供了相应JAVA API,类比JDBC连接数据库:
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("localhost");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/test");
//连接用户名;默认为guest
connectionFactory.setUsername("admin");
//连接密码;默认为guest
connectionFactory.setPassword("admin");
//创建连接
Connection connection = connectionFactory.newConnection();
// 创建通道
Channel channel = connection.createChannel();
2. 创建交换机和队列
-
交换机和队列是AMQP的高级模块,消费方连接到交换机,通过交换机来路由消息到队列,交换机和队列之间绑定(即建立路由规则、规则不一,与交换机种类相关),消费方和队列连接,可以选择不同的方式来消费队列中的消息。
-
在RabbitMQ管理控制台用户界面可以看到创建的交换机和队列。
-
创建交换机和队列需要涉及到的API有
channel.exchangeDeclare()
、channel.queueBind()
、channel.exchangeBind()
、channel.queueDeclare()
。具体参见 Web | RabbitMQ基础 | API -
消费方和接收方都可以建立通道后,通过channel对象的方法建立队列和交换机,一般两方都会建立同一个相同名称交换机和相同名称队列的代码,并非重复建立,只是做一个保障而已,同名交换机和队列建立后是不会重复建立的。
3. 发布消息
生产方通过通道向交换机发送消息,发布消息需要用到Channel.basicPublish()
4. 消息路由
交换机会将消息按路由规则路由到对应的队列,也可以说交换机的类型决定了路由方向,除了默认交换机外,其它交换机都需要手动绑定已经建立的队列,对应的API是:Channel.queueBind()
。
路由失败时
如果一个消息发布时设置了“mandatory”标识符,但又不能路由,代理将会把它返回给发送客户端(通过AMQP.Basic.Return命令)。为了得到这种消息被返回的通知,客户端可以实现“ReturnListener”接口,然后调用Channel.setReturnListener
方法。如果客户端没有为通道配置一个返回监听器,那么相关的返回消息将被安静的丢弃。
5. 消费方消费消息
5.1 通过订阅接收消息(Push API)
接收消息的最有效的方法是使用Consumer
接口发起订阅。当消息到达队列时,将自动被投递,而无需显式的请求消息,关于消费者标签说明如下:。
- 一个Consumer接口实现代表一个消费方,各自的订阅是通过消费者标签(consumerTag)来引用。consumerTag就是消费者id,可以在在
channel.basicConsume()
时候参数中指定。 - 为了让RabbitMQ生成节点唯一的tag,使用不带消费者tag参数或者传空字符串给该参数的
Channel.basicConsume()
方法。 - 不同consumer的consumerTag不能相同,在一个连接上使用重复的消费者标签是绝对禁止的,它会引发连接自动恢复的问题,而且会混淆对消费者的监控。
- 实现一个
Consumer
的最简单的方法是继承DefaultConsumer
。然后,将该子类的一个对象传入basicConsume
方法即可发起订阅。
5.2 主动获取消息(Pull API)
要想显式的获取消息,使用Channel.basicGet()
方法。返回值是一个GetResponse
的实例,可以从该实例中提取出header信息(属性)和消息体。
6.总结与扩展
6.1 RabbitMQ运转流程
在入门案例中:
- 生产者发送消息
- 生产者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker;
- 声明队列并设置属性;如是否排它,是否持久化,是否自动删除;
- 将路由键(空字符串)与队列绑定起来;
- 发送消息至RabbitMQ Broker;
- 关闭信道;
- 关闭连接;
- 消费者接收消息
- 消费者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker
- 向Broker 请求消费相应队列中的消息,设置相应的回调函数;
- 等待Broker回应闭关投递响应队列中的消息,消费者接收消息;
- 确认(ack,自动确认)接收到的消息;
- RabbitMQ从队列中删除相应已经被确认的消息;
- 关闭信道;
- 关闭连接;
6.2 生产者流转过程说明
- 客户端与代理服务器Broker建立连接。会调用newConnection() 方法,这个方法会进一步封装Protocol Header 0-9-1 的报文头发送给Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 协议,紧接着Broker 返回Connection.Start 来建立连接,在连接的过程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 这6 个命令的交互。
- 客户端调用connection.createChannel方法。此方法开启信道,其包装的channel.open命令发送给Broker,等待channel.basicPublish方法,对应的AMQP命令为Basic.Publish,这个命令包含了content Header 和content Body()。content Header 包含了消息体的属性,例如:投递模式,优先级等,content Body 包含了消息体本身。
- 客户端发送完消息需要关闭资源时,涉及到Channel.Close和Channl.Close-Ok 与Connetion.Close和Connection.Close-Ok的命令交互。
6.3 消费者流转过程说明
- 消费者客户端与代理服务器Broker建立连接。会调用newConnection() 方法,这个方法会进一步封装Protocol Header 0-9-1 的报文头发送给Broker ,以此通知Broker 本次交互采用的是AMQPO-9-1 协议,紧接着Broker 返回Connection.Start 来建立连接,在连接的过程中涉及Connection.Start/.Start-OK 、Connection.Tune/.Tune-Ok ,Connection.Open/ .Open-Ok 这6 个命令的交互。
- 消费者客户端调用connection.createChannel方法。和生产者客户端一样,协议涉及Channel . Open/Open-Ok命令。
- 在真正消费之前,消费者客户端需要向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和Channl.Close-Ok 与Connetion.Close和Connection.Close-Ok的命令交互。
6.4 理解总结
- 交换机/队列并非说与哪个消息、通道或者生产者/消费者绑定了,只是生产者在发送消息时指定了消息的相应路由属性并指定了交换机,具体怎么路由就看交换机的类型(不同类型交换机会有不同的路由规则)和消息的某些属性(比如:路由键)了。消费方则是在消费时指定了消费哪个队列中的消息。
- 通道可以发送消息,也可以获取消息消费,消费者和生产者都依赖于通道来操作MQ,所以客户端既可以是消费者也可以是生产者。
- 一般每个客户端对应一个通道,连接到MQ代理。