主要内容
JMS规范:JAVA消息服务
基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现
AMQP规范:高级消息队列协议
高级消息队列协议,也是一个消息代理的规范,兼容JMS
RabbitMQ是AMQP的实现
RabbitMQ:是AMQP的实现。
1.两者对比:
2.Spring底层对两者都是支持的。
spring-jms提供了对JMS的支持
spring-rabbit提供了对AMQP的支持
需要ConnectionFactory的实现来连接消息代理
提供JmsTemplate、RabbitTemplate来发送消息
@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
@EnableJms、@EnableRabbit开启支持
3.Spring Boot自动配置
JmsAutoConfiguration
RabbitAutoConfiguration
1. 概述
1. 大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力
2. 消息服务中两个重要概念:
消息代理(message broker):
消息中间件的服务器,要想向消息队列中发送内容,就要连接消息代理(消息中间件服务器)
消息发送至是将消息发送给消息中间价的服务器,再由消息中间价的服务器发送到指定的目的地。
目的地(destination)
队列:点对点
主题:发布/订阅
关系:
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3. 消息队列主要有两种形式的目的地
1.队列(queue):点对点消息通信(point-to-point)
消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,
消息读取后被移出队列。
消息只有唯一的发送者和接受者,但并不是说只能有一个接收者。
多个接收者时:谁抢到,就是谁的,抢到后队列中就没了。
2.主题(topic):发布(publish)/订阅(subscribe)消息通信
发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么
就会在消息到达时同时收到消息。
4. 应用场景
应用场景:提升异步通信能力。
关键字:异步读取。
应用场景:解耦
引用消息队列,来传递数据。达到解耦两个微服务的目的。
应用场景:秒杀/流量削峰
将消息队列设定为10000,那么前10000名能进消息队列,秒杀成功。其他的直接抛弃请求。
2. 安装RabbiMQ
2.1 RabbitMQ简介
1. RabbitMQ简介:
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
2. 核心概念
1. Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,
这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、
delivery-mode(指出该消息可能需要持久性存储)等。
2. Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
3. Exchange
是消息中间件服务器的一个组件,其上可能绑定这许多消息队列,消息分发给哪个队列,由路由键决定的。
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,
不同类型的Exchange转发消息的策略有所区别。
4. Queue
Queue消息队列,用来保存消息直到发送给消费者。
它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,
等待消费者连接到这个队列将其取走。
5. Binding
绑定。
消息发给消息服务器后,由其中交换器派分给消息队列。那么就意味着,消息队列和交换器存在着
一种绑定规则。
每个路由键就对应着一个这种规则,即基于路由键将交换器和消息队列连接起来的路由规则,这种规则也可以称为一个绑定。
所以可以将交换器理解成一个由绑定构成的路由表。
Exchange 和Queue的绑定可以是多对多的关系。
6. Connection
网络连接,比如一个TCP连接。
7. Channel
如果每次去消息队列中取一个消息,都建立一条TCP连接的话。这是非常耗费资源的。
所以只建立一条连接,从消息队列取数据时再建立多个信道,以便复用这个TCP连接。
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,
AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。
因为对于操作系统来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP 连接。
8. Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
9. Virtual Host:vhost
可以将消息中间件服务器划分成若干个虚拟主机,每个虚拟主机可以理解成是一个mini的RabbitMQ服务器。
他们是可以独立运行的,而且隔离。
虚拟主机A: /A
虚拟主机B: /B
虚拟主机C: /C
虚拟主机,表示一批交换器、消息队列和相关对象。
虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost 本质上就是一个mini 版的RabbitMQ
服务器,拥有自己的队列、交换器、绑定和权限机制。
vhost 是AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的vhost 是/ 。
10. Broker
表示消息队列服务器实体
2.2 RabbitMQ的运行机制
一个消息是如何从生产者到达消费者呢?
AMQP 中消息的路由过程和Java 开发者熟悉的JMS 存在一些差别,AMQP 中增加了Exchange和Binding的角色。
生产者把消息发布到Exchange 上,消息最终到达队列并被消费者接收,
而Binding 决定交换器的消息应该发送到那个队列。
核心是交换器和绑定规则:有不同的分发策略。
1. Exchange类型
Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:
direct、fanout、topic、headers。
headers 匹配AMQP 消息的header 而不是路由键,headers 交换器和direct 交换器完全一致,
但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
1. Direct Exchange:单播模式
消息中的路由键(routing key)和Binding 中的binding key 一致时。
交换器就将消息发到对应的队列中。
是否可以理解为:
消息里面有一个键:routing key
消息队列一个键:binding key
如果是Direct Exchange:那么就只要一对一的分发。
2. Fanout Exchange:类似广播模式
每个发到fanout 类型交换器的消息都会分到所有绑定的队列上去,fanout 交换器不处理路由键。
fanout 类型转发消息是最快的。
3. Topic Exchange:有选择性的进行广播
对路由键做一个模糊匹配,有选择性非发给某些个队列。
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,
此时队列需要绑定到一个模式上。
它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。
它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配一个单词。
2.3 RabbitMQ镜像
1. docker pull rabbitmq:3-management
pull带management标签的RabbitMQ镜像,有带web的管理界面。
2. 启动镜像
docker run -d -p 5672:5672 -p15672:15672 --name myRabbitMQ 43f79d83563f
注意:代管理界面的RabbitMQ,有两个端口。
把本地主机的5672映射到docker容器的5672:客户端和RabbitMQ进行通信的端口。
把本地主机的15672映射到docker容器的15672:管理界面,访问web页面的端口。
3. 访问测试一下
默认登录账号密码:
guest
guest
3. 整合RabbitMQ
3.1 创建交换器,消息队列和绑定消息。
1. 创建交换器
1.1. exchange.direct
持久化durable:下一次重启RabbitMQ该交换器还在。
1.2. exchange.fanout
1.3. exchange.topic
2. 添加消息队列
atguigu
atguigu.news
atguigu.emps
gulixueyuan.news
3. 将消息队列和exchange.direct交换器进行绑定
3.1 进入绑定操作页面
3.2 绑定第一个队列:路由键atguigu
3.3 绑定第二个队列:路由键atguigu.news
3.4 绑定第三个队列:路由键atguigu.emps
3.5 绑定第四个队列:路由键gulixueyuan.news
4. 将消息队列和exchange.fanout交换器进行绑定
5. 将消息队列和exchange.topic交换器进行绑定
#:匹配0或多个单词
*:匹配一个单词
atguigu队列的路由键:atguigu.#
atguigu.news队列的路由键:atguigu.#
atguigu.emps队列的路由键:atguigu.#
atguigu.news队列的路由键:*.news
gulixueyuan.news队列的路由键:*.news
6. 解绑
3.2 测试发送一个消息
1. 给exchange.direct发送消息
2. 查看发送的消息
3. 给exchange.fanout发送消息
4. 查看发送的消息
所有的消息队列都收到消息。
5. 给exchange.topic发送消息
给exchange.topic发送消息:路由键是atguigu.news
能匹配路由键是:atguigu.# *.news的队列
给exchange.topic发送消息:路由键是给hello.news
能匹配路由键是:*.news的队列
查看一下:
选择Ack message requeue false:获取一个删除一个。不然只会显示一个消息。
4. 整合RabbitMQ
4.1 RabbitMQzi自动配置原理
1. 创建一个项目
2. 看一下RabbitMQ给我们自动配置了哪些组件?
打开RabbitMQ的自动配置类:RabbitAutoConfiguration
1. 连接工厂:能获取和RabbitMQ的连接
CachingConnectionFactory
2. 连接工厂的连接信息从RabbitProperties获得
3. 点进RabbitProperties
RabbitProperties封装了RabbitMQ的配置。
其中的属性和主配置文件中前缀spring.rabbitmq的配置绑定。
4. 配置连接属性
spring:
rabbitmq:
host: 192.168.92.130
username: guest
password: guest
5. RabbitMQ给我们自动配置RabbitTemplate
给RabbitMQ发送和接收消息的。
6. RabbitMQ给我们自动配置AmqpAdmin
是RabbitMQ的系统管理功能组件
4.2 测试:向exchange.direct发送一个消息(代码实现)
1. application.yum配置
spring:
rabbitmq:
host: 192.168.92.130
username: guest
password: guest
2. 测试发送消息方法
@SpringBootTest
class SpringbootAdvanced02AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
/**
* rabbitTemplate.send(exchange, routeKey, message);
*
* message需要自己构造:
* 可以定制消息体和消息头
*
* public Message(byte[] body, MessageProperties messageProperties) {
* this.body = body;
* this.messageProperties = messageProperties;
* }
*/
//object默认被当做消息体,只需要传入要发送的消息对象,会自动序列化发送给rabbitMQ
//rabbitTemplate.convertAndSend(exchange, routeKey, object);
/**
* 使用我们创建的exchange.direct
* 使用atguigu.news路由键
* 要发送的内容:map
*/
Map<String, Object> map = new HashMap<String, Object>();
map.put("msg", "这是第一个消息");
map.put("data", Arrays.asList("hello", 123, true));
rabbitTemplate.convertAndSend("exchange.direct", "atguigu.news", map);
}
}
3. 查看结果
测试成功,用JDK的序列化方式将map发送过来。
4.3 测试:从atguigu.news队列中接受/获取一个消息(代码实现)
测试接收/获取数据:消息队列中的数据一被接收就没有了
/**
* 测试接收/获取数据:消息队列中的数据一被接收就没有了
*/
@Test
public void receive(){
Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
System.out.println(o.getClass());
System.out.println(o);
}
消息队列中的数据一被接收就不存在了。
4.4 如何将数据转成JSON再发出去呢?
为什么之前是按照JDK的序列化转换对象?
RabbitTemplate中有一个消息转换器MessageConverter
MessageConverter是一个org.springframework.amqp.support.converter接口
这个转换器是一个SimpleMessageConverter,他默认是用JDK的序列化工具,进行序列化。
private MessageConverter messageConverter = new SimpleMessageConverter();
所以我们可以自己写一个配置
分析一下:
自动配置类在注入RabbitTemplate时,调用了configure方法,其中进行了messageConverter的配置。
如果没有messageConverter就用它默认的,如果有就用自定义的。
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter messageConver(){
return new Jackson2JsonMessageConverter();
}
}
所以不用在进行其他配置,就可以使用该messageConver。
1. 结果成功的序列化成了JSON。
4.5. 测试广播
/**
* 测试广播
*/
@Test
public void sendMsg(){
rabbitTemplate.convertAndSend("exchange.fanout", "", new Book("三国演义","罗贯中"));
}
5. RabbitMQ监听方法
0. 注意,获取到的消息要封装book实体类,实体类必须有空参
public class Book {
private String name;
private String author;
public Book() {
}
public Book(String name, String author) {
this.name = name;
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
}
1. 这个监听的方法,会将消息队列中的消息取走。
/**
* 注意类上加上@Service注解
* 监听来自消息队列中book相关的内容
*/
@Service
public class BookService {
/**
* 监听消息队列以后调用的。
*
* 要让这个@RabbitListener起作用,要开启基于注解的RabbitMQ模式。
* @param book
*/
@RabbitListener(queues = "atguigu.news")
//会将消息中的内容封装成一个book对象,可能都是有注解实现的
public void receive(Book book){
//这要这个消息队列中有内容,这个方法就会被调用。
System.out.println("收到消息: "+book);
}
}
1.1 要让这个@RabbitListener起作用,要开启基于注解的RabbitMQ模式。
//要让这个@RabbitListener注解起作用,要开启基于注解的RabbitMQ模式。
@SpringBootApplication
@EnableRabbit //开启基于注解的RabbitMQ
public class SpringbootAdvanced02AmqpApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAdvanced02AmqpApplication.class, args);
}
}
2. 监听消息头,消息体
@RabbitListener(queues = "atguigu")
public void receive02(Message message){
System.out.println(message.getBody());
//消息头
System.out.println(message.getMessageProperties());
}
3. 测试一下
启动主入口方法
在启动测试方法:用一个关播交换器帮我们给所有消息队列发送消息。
看RabbitListener能不能监听到queues = "atguigu.news"中的添加消息
/**
* 测试广播
*/
@Test
public void sendMsg(){
rabbitTemplate.convertAndSend("exchange.fanout", "", new Book("红楼梦","曹雪芹"));
}
6. AmqpAdmin管理组件的使用
我们前面在RabbitMQ的web页面已经创建好了Exchange和Queue。
如果我们需要在程序中创建怎么做呢?
我们用程序临时的创建一些Exchange,Quere,Binding等,由AmqpAdmin管理来实现。
他帮我们创建和删除Exchange,Quere,Binding。
RabbitAutoConfiguration自动给我们配置了这个类,所以我们只要注入使用即可。
1. 测试一下创建Exchange
Exchange是一个接口:有direct/fanout/topic等实现
@Autowired
AmqpAdmin amqpAdmin;
@Test
public void creatExchange(){
//Exchange是一个接口:有direct/fanout/topic等实现
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));
System.out.println("创建完成");
}
2. 创建Queue
@Test
public void creatQueue(){
//Queue是一个对象
amqpAdmin.declareQueue(new Queue("amqpadmin.queue2", true));
}
3. 测试用amqpadmin进行绑定
/**
* 测试用amqpadmin进行绑定
*/
@Test
public void binding(){
amqpAdmin.declareBinding(new Binding("amqpadmin.queue2", Binding.DestinationType.QUEUE,
"amqpadmin.exchange", "amqp.haha", null));
}
测试成功。