MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。RabbitMQ是一个Erlang开发的AMQP(Advanced Message Queuing Protocol )的开源实现。
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
开发中消息队列通常有如下应用场景:
(1) 异步提速:
任务异步处理,将不需要同步处理的,且耗时较长的操作,由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
(2) 应用解耦:
应用程序解耦合,MQ充当中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合
(3) 削峰填谷:
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用MQ能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
(4) 可恢复性:
系统的一部分组件失效时,不会影响到整个系统。MQ降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
(5) 排序保证:
消息队列可以控制数据处理的顺序,因为消息队列本身使用的是队列这个数据结构, FIFO (先进先出),在一些场景数据处理的顺序很重要,比如商品下单顺序等。
市场上常见的消息队列有如下:
Dubbo协议:Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。
AMQP协议:即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。
JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。JMS规定了两种消息模式;而AMQP的消息模式更加丰富.
JMS | AMQP | |
---|---|---|
定义 | Java Api | Wire-protocol |
跨语言 | 否 | 是 |
跨平台 | 否 | 是 |
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
RabbitMQ官方地址:http://www.rabbitmq.com/
RabbitMQ提供了6种模式:Hello Word简单模式,work工作模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式(通配符模式),RPC远程调用模式(远程调用,不太算MQ;不作介绍)
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
我们在双11的时候,当我们凌晨大量的秒杀和抢购商品,然后去结算的时候,就会发现,界面会提醒我们,让我们稍等,以及一些友好的图片文字提醒。而不是像前几年的时代,动不动就页面卡死,报错等来呈现给用户。
积分兑换模块,有一个公司多个部门都要用到这个模块,这时候就可以通过消息队列解耦这个特性来实现。 各部门系统做各部门的事,但是他们都可以用这个积分系统进行商品的兑换等。其他模块与积分模块完全解耦。
发送邮件、用户大数据分析操作等 基于同步变异步功能实现
用户注册真实操作步骤:
正常情况注册,不出现高并发,假如有大量的用户注册,发生了高并发,就会出现如下情况:
邮件接口承受不住,或是分析信息时的大量计算使 cpu 满载,这将会出现虽然用户数据记录很快的添加到数据库中了,但是却卡在发邮件或分析信息时的情况,导致请求的响应时间大幅增长,甚至出现超时,这就有点不划算了。面对这种情况一般也是将这些操作放入消息队列(生产者消费者模型),消息队列慢慢的进行处理,同时可以很快的完成注册请求,不会影响用户使用其他功能。
docker pull rabbitmq:management
docker run -d --restart=always --name=rabbitmq -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:management
解释如下:
15672 (if management plugin is enabled.管理界面 )
15671 management监听端口
5672, 5671 (AMQP 0-9-1 without and with TLS 消息队列协议是一个消息协议)
4369 (epmd) epmd 代表 Erlang 端口映射守护进程
25672 (Erlang distribution)
浏览器中输入地址http://192.168.200.128:15672/
docker update --restart=always 容器ID
RabbitMQ在安装好后,可以访问http://localhost:15672;其自带了guest/guest的用户名和密码;如果需要创建自定义用户;那么也可以登录管理界面后,如下操作:
角色说明:
超级管理员(administrator),可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
监控者(monitoring),可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
策略制定者(policymaker),可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
普通管理者(management),仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
其他,无法登陆管理控制台,通常就是普通的生产者和消费者。
2.2.2. Virtual Hosts配置
RabbitMQ的权限管理;在RabbitMQ中可以虚拟消息服务器Virtual Host,每个Virtual Hosts相当于一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。相当于mysql的db。Virtual Name一般以/开头。
持久化:如果选durable,则队列消息自动持久化到磁盘上,如果选transient,则不会持久化;
自动删除:默认值no,如果yes,则在消息队列没有使用的情况下,队列自行删除。
自动删除:默认值no,如果是yes,则在将所有队列与交换机取消绑定之后,交换机将自动删除。
交换机类型:
内部交换器:默认值no,如果是yes,消息无法直接发送到该交换机,必须通过交换机的转发才能到达次交换机。本交换机只能与交换机绑定。
在spring boot项目中,只需要引入start-amqp起步依赖,即可整合RabbitMQ成功;我们基于SpringBoot封装的RabbitTemplate模板对象,可以非常方便的发送消息,接收消息(使用注解)。amqp的官方GitHub地址:https://github.com/spring-projects/spring-amqp
一般在开发过程中,我们有两个角色:
1、创建父工程:
2、生产者工程:
创建父maven空的工程:springboot-rabbitmq-parent
创建SpringBoot的生产者工程:rabbitmq-producer
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.9.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbit-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
@SpringBootApplication
public class SpringbootRabbitmqProducerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqProducerApplication.class, args);
}
}
1)配置文件application.properties,内容如下:
# RabbitMQ 服务host地址
spring.rabbitmq.host=192.168.200.128
# 端口
spring.rabbitmq.port=5672
# 虚拟主机地址
spring.rabbitmq.virtual-host=/kkb
# rabbit服务的用户名
spring.rabbitmq.username=kaikeba
# rabbit服务的密码
spring.rabbitmq.password=kaikeba
创建SpringBoot的消费者工程:rabbitmq-consumer
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.9.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
dependencies>
@SpringBootApplication
public class SpringbootRabbitmqConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRabbitmqConsumerApplication.class, args);
}
}
application.properties,内容如下:
# RabbitMQ 服务host地址
spring.rabbitmq.host=192.168.200.128
# 端口
spring.rabbitmq.port=5672
# 虚拟主机地址
spring.rabbitmq.virtual-host=/kkb
# rabbit服务的用户名
spring.rabbitmq.username=kaikeba
# rabbit服务的密码
spring.rabbitmq.password=kaikeba
在上图的模型中,有以下概念:
P: 生产者: 也就是要发送消息的程序
C: 消费者:消息的接受者,会一直等待消息到来。
queue: 消息队列,图中红色部分。可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。【最简单消息队列模式】
Work Queues与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
【多个节点分片任务处理,提升任务处理的效率】
而在订阅模型中,多了一个exchange角色,而且过程略有变化:
Exchange有常见以下3种类型:
Fanout:广播 将消息交给所有绑定到交换机的队列, 不处理路由键。只需要简单的将队列绑定到交换机上。fanout 类型交换机转发消息是最快的。
Direct:定向 把消息交给符合指定routing key 的队列. 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。如果一个队列绑定到该交换机上要求路由键“dog”,则只有被标记为 “dog” 的消息才被转发,不会转发 dog.puppy,也不会转发 dog.guard,只会转发dog。
其中,路由模式使用的是 direct 类型的交换机。
Topic:主题(通配符) 把消息交给符合routing pattern(路由模式)的队列. 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号 “#” 匹配一个或多个词,符号""匹配不多不少一个词。因此“audit.#” 能够匹配到“audit.irs.corporate”,但是“audit.” 只会匹配到 “audit.irs”。
其中,主题模式(通配符模式)使用的是 topic 类型的交换机。
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
【广播消息:一次性将消息发送给所有消费者,每个消费者收到消息均一致】
路由模式特点:队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)消息的发送方在向 Exchange发送消息时,也必须指定消息的RoutingKey。Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的Routing key完全一致,才会接收到消息.
图解:
【有选择性的接收消息】
Topic
类型与 Direct
相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符!
Routingkey
: 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词,多个词用点号分隔
*:匹配不多不少恰好1个词
举例:
item.#: 能够匹配 item.insert.abc.bbc
或者 item.insert
item.*: 只能匹配 item.insert
【基于通配符接收消息】
工作模式:
fanout
的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息广播发送到绑定的队列,每个队列的消息都是同等数量。direct
的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列topic
的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列