一、应用场景:
(1) 异步操作:
任务异步处理将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
(2) 解耦:
应用程序解耦合MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合
(3) 削峰:
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用MQ能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
(4) 可恢复性:
系统的一部分组件失效时,不会影响到整个系统。MQ降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
二、概述:
(1)两种主流方式:
AMQP:
是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。
JMS:
即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
AMQP 与 JMS 区别
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。JMS规定了两种消息模式;而AMQP的消息模式更加丰富.
(2)市场上常见的消息队列有如下:
- ActiveMQ:基于JMS实现, 比较均衡, 不是最快的, 也不是最稳定的.
- ZeroMQ:基于C语言开发, 目前最好的队列系统.
- RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好, 数据基本上不会丢失
- RocketMQ:基于JMS,阿里巴巴产品, 目前已经捐献给apahce, 还在孵化器中孵化.
- Kafka:类似MQ的产品;分布式消息系统,高吞吐量, 目前最快的消息服务器, 不保证数据完整性.
三、 RabbitMQ工作原理
组成部分说明如下:
- Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
- Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
- Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
- Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
- Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
消息发布接收流程:
-----发送消息
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
----接收消息
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
相关定义:
- Broker: 简单来说就是消息队列服务器实体
- Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
- Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
- Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
- Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
- VHost: 虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
- Producer: 消息生产者,就是投递消息的程序
- Consumer: 消息消费者,就是接受消息的程序
- Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。
四、安装及配置rabbitMQ
(1) linux安装rabbitMQ容器
- 下载镜像
docker pull rabbitmq:management
- 创建容器
docker run -di --name=myrabbitmq -p 5671:5617 -p 5672:5672 -p4369: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://服务器ip:15672/
- 设置容器开机自动启动
docker update --restart=always 容器ID
(2) 用户以及Virtual Hosts配置
1、 用户角色
RabbitMQ在安装好后,可以访问http://服务器ip:15672;其自带了guest/guest的用户名和密码;如果需要创建自定义用户;那么也可以登录管理界面后,如下操作:
角色说明:
- 超级管理员(administrator)可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
- 监控者(monitoring)可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
- 策略制定者(policymaker)可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
- 普通管理者(management)仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
- 其他无法登陆管理控制台,通常就是普通的生产者和消费者。
2、 Virtual Hosts配置
像mysql拥有数据库的概念并且可以指定用户对库和表等操作的权限。RabbitMQ也有类似的权限管理;在RabbitMQ中可以虚拟消息服务器Virtual Host,每个Virtual Hosts相当于一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。相当于mysql的db。Virtual Name一般以/开头。
- 创建Virtual Hosts
- 设置Virtual Hosts权限
- 创建交换机
- 创建队列
- 交换机绑定队列
- 发消息
- 接收消息
五、Spring Boot整合RabbitMQ
生产者工程:
- application.yml文件配置RabbitMQ相关信息;
- 在生产者工程中编写配置类,用于创建交换机和队列,并进行绑定
- 注入RabbitTemplate对象,通过RabbitTemplate对象发送消息到交换机
消费者工程:
- application.yml文件配置RabbitMQ相关信息
- 创建消息处理类,用于接收队列中的消息并进行处理
创建父工程
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
生产者工程&消费者工程
yml文件一样
spring:
rabbitmq:
host: 122.51.207.162
port: 5672
virtual-host: /laoshentou # 地址
username: # rabbitmq账号
password: # rabbitmq密码
各自创建springboot启动类
创建交换机/队列代码
package com.rabbitmq.producer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
/**
* 主题交换器的名称
*/
public static final String TOPIC_EXCHANGE = "topicExchange";
/**
* 搜索队列
*/
public static final String SEARCH_QUEUE = "searchQueue";
/**
* 插入队列
*/
public static final String INSERT_QUEUE = "insertQueue";
/**
* 通配符
*/
public static final String ROUTING_KEY = "it.#";
/**
* 主题模式交换机
*/
@Bean
public Exchange topicExchange() {
return new TopicExchange(TOPIC_EXCHANGE);
}
/**
* 搜索队列
*/
@Bean
public Queue searchQueue() {
return new Queue(SEARCH_QUEUE);
}
/**
* 插入队列
*/
@Bean
public Queue insertQueue() {
return new Queue(INSERT_QUEUE);
}
/**
* 主题交换机绑定搜索队列
*/
@Bean
public Binding bindingSearchQueueToTopicExchange() {
return BindingBuilder.bind(searchQueue()).to(topicExchange()).with(ROUTING_KEY).noargs();
}
/**
* 主题交换机绑定插入队列
*/
@Bean
public Binding bindingInsertQueueToTopicExchange() {
return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(ROUTING_KEY).noargs();
}
}
生产者代码:
package com.rabbitmq.producer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = ProducerApplication.class)
@RunWith(SpringRunner.class)
public class TestSimple {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 简单模式:一个生产者,一个消费者,不需要设置交换机(使用的是默认的交换机,routing key与队列名一致)
*/
@Test
public void testSimpleSend() {
//发消息,形参为指定的队列名和要发送的消息内容
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("simple_queue", "测试简单模式!" + i);
}
}
/**
* 工作队列模式:一个生产者,多个消费者(竞争关系),不需要设置交换机(使用的是默认的交换机,routing key与队列名一致)
*/
@Test
public void testWorkSend() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("work_queue", "work模式:" + i);
}
}
/**
* 广播模式/发布订阅模式
* 一个生产者发,多个消费者通过不同队列接,所有人都可以接到
* 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
*/
@Test
public void testPubAndSubSend() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("fanout_exchange", "", "发布订阅/广播模式:" + i);
}
}
/**
* 路由模式
* 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key
* 当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
* - 交换机direct_exchange将routing key为insert的消息发给绑定了对应routing key的队列direct_queue_insert
* direct_exchange ==> routing key(insert) ==> direct_queue_insert
* direct_exchange ==> routing key(update) ==> direct_queue_update
*/
@Test
public void testDirectSend1() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("direct_exchange", "insertKey", "路由模式(insertKey):" + i);
}
}
@Test
public void testDirectSend2() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("direct_exchange", "updateKey", "路由模式(updateKey):" + i);
}
}
/**
* 通配符/主题模式
* 需要设置类型为topic的交换机,交换机和队列进行绑定,并且制定通配符方式的routing key
* 当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
* item后面加一个`.`的routing key(例如item.goods) 使用item.*可以接受
* item后面加一个或多个`.`的routing key(例如item.goods.spu) 使用item.#可以接受
* - 交换机topic_exchange将routing key为item.goods的消息发送到绑定的队列,routing key为item.*和item.#的队列都能接收到
* - 交换机topic_exchange将routing key为item.goods.spu的消息发送到绑定的队列,routing key为item.#的队列才能接收到
*/
@Test
public void testTopicSend1() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("topic_exchange", "item.goods", "通配符/主题模式(item.goods):" + i);
}
}
@Test
public void testTopicSend2() {
for (int i = 0; i < 5; i++) {
rabbitTemplate.convertAndSend("topic_exchange", "item.goods.spu", "通配符/主题模式(item.goods.spu):" + i);
}
}
}
消费者代码
简单模式
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "simple_queue")
public class SimpleListener {
@RabbitHandler
public void testListener(String message) {
System.out.println("简单模式:" + message);
}
}
工作队列模式1
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "work_queue")
public class WorkListener1 {
@RabbitHandler
public void testListener(String message) {
System.out.println("工作队列模式1:" + message);
}
}
工作队列模式2
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class WorkListener2 {
@RabbitListener(queues = "work_queue")
public void testListener1(String message) {
System.out.println("工作队列模式2:" + message);
}
@RabbitListener(queues = "work_queue")
public void testListener2(String message) {
System.out.println("工作队列模式3:" + message);
}
}
发布订阅/广播模式
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class PubAndSunListener {
@RabbitListener(queues = "pubandsub1_queue")
public void testListener1(String message){
System.out.println("发布订阅/广播模式1:"+message);
}
@RabbitListener(queues = "pubandsub2_queue")
public void testListener2(String message){
System.out.println("发布订阅/广播模式2:"+message);
}
}
路由模式
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DirectListener {
@RabbitListener(queues = "direct_queue_insert")
public void testListener1(String message) {
System.out.println("路由模式(insertKey):" + message);
}
@RabbitListener(queues = "direct_queue_update")
public void testListener2(String message) {
System.out.println("路由模式(updatekey):" + message);
}
}
通配符/主题模式
package com.rabbitmq.consumer.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class TopicListener {
@RabbitListener(queues = "topic_queue_item*")
public void testListener1(String message){
System.out.println("通配符/主题模式(item.goods):" + message);
}
@RabbitListener(queues = "topic_queue_item#")
public void testListener2(String message){
System.out.println("通配符/主题模式(item.goods.spu):" + message);
}
}
六、rabbitMQ五种模式
(1)简单模式
(2)工作队列模式
(3)发布订阅模式/广播模式
(4)路由模式
(5)通配符模式
(6)RabbitMQ模式总结
工作模式:
1、简单模式 HelloWorld : 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机,routingKey与队列名一致)
2、工作队列模式 Work Queue: 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe: 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing: 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic: 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列