一、消息介绍: JMS和AMQP规范:
消息中间件:
什么是同步和异步模式?
举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->慢慢等待处理完毕
同步,就是你和别人聊天,你回了消息后你就要一直等待他发消息,直到你们两个不再联系为止,在此期间不能做任何事情
异步,你等待对面发送消息的期间可以做其他事情,例如吃饭、打游戏等。等到吃完饭或游戏打完后慢慢回消息。
什么是消息中间件?
消息中间件:是利用高效可靠的消息传递机制进行异步的数据传输,并基于数据通信进行分布式系统的集成。通过提供消息队列模型和消息传递机制,可以在分布式环境下扩展进程间的通信。
中间件是帮助应用程序与其他应用程序、网络、硬件、操作系统交互或通信的软件(将业务和底层逻辑解耦)。
消息中间件(默认JMS)一般有两种传递模式:
点对点模式:消息生产者将消息发送到队列中,消息消费者从队列中接收消息,消息生产者只能有一个,而消息消费者可以有多个,消息一旦被某一个消费者消费后就会被在队列中移出,其他消费者就不能再收到该消息(即只能有一个收到消息)。消息可以在队列中进行异步传输。
发布/订阅模式:发布订阅模式是通过一个内容节点来发布和订阅消息,这个内容节点称为主题(topic或channel),消息发布者将消息发布到某个主题,消息订阅者订阅这个主题的消息,主题相当于一个中介。主题是的消息的发布与订阅相互独立,不需要进行基础即可保证消息的传递,发布/订阅模式在消息的一对多广播是采用(即所有的消息消费者都能收到消息)。
消息中间件可以做什么?
1、应用程序之间不采取直接通信,而是使用消息中间作为中介,做到数据的异步通信。开发人员不需要考虑网络协议和远程调用的问题,只需要通过各消息中间件所提供的api,就可以简单的完成消息推送,和消息接收的业务功能。
2、消息的生产者将消息存储到队列中,消息的消费者不一定马上消费消息,可以等到自己想要用到这个消息的时候,再从相应的队列中去获取消息。这样的设计可以很好的解决,大数据量数据传递所占用的资源,使数据传递和平台分开,不再需要分资源用于数据传输,可以将这些资源用去其他想要做的事情上。
消息队列的两个规范:
JMS和AMQP两种规范的消息队列:
JMS:Java消息服务:
j2ee提供的基于JVM消息代理的规范。 ActiveMQ、HornetMQ是JMS实现的消息中间件
AMQP:高级消息队列协议:
也是一种消息代理规范,兼容JMS。 RabbitMQ是AMQP实现的消息中间件
两种规范的区别:
Spring的支持:
二、RabbitMQ介绍:
什么是RabbitMQ?
RabbitMQ是AMQP规范的的一个实现产品。(在 高级-消息)
RabbitMQ核心概念:
Message:
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher:
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange:
交换器,用来接收生产者发送的消息并将这些消息路由(下面讲解)给服务器中的队列。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
Queue:
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,排队等待消费者连接到这个队列将其取走。
Binding:
绑定,用于消息队列Queue和交换器Exchange之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Exchange 和 Queue 的绑定可以是多对多的关系。
Connection:
网络连接,比如一个TCP连接。
Channel:
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
Consumer:
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host:
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,
RabbitMQ 默认的 vhost 是 / 。
Broker:
表示消息队列服务器实体
AMQP中的消息路由:
AMQP比JMS中增加了Exchange和Binding角色。生产者将消息发布给Broker(服务器),然后给到一个Exchange(交换机)上,消息最终到达队列并被消费者接收,而Binding决定交换器的消息应该到哪个队列上。
交换器和Binding不同消息到达队列的规则就不一样。Exchange有4中,前三中是点对点,后三种是发布/订阅(最后一个不常用)
Exchange的4种消息的分发策略: direct(默认),fanout, topic, 和headers
direct:消息中的路由键(routing key)如果和Binding中的 binding key 完全一致,交换器就将消息发布到对应的队列中(单播通信,点对点)。
fanout:当接收到消息后,不管消息的路由键是什么,他都会将消息给他下面绑定的所有队列都发一份(广播,是速度最快的)。
topic:允许对消息中的路由键(routing key)的key做模糊匹配,将消息发送给匹配的key的队列。
两头都支持两个通配符:"#"和"*":
#匹配0或多个单词;*匹配一个单词;
headers:匹配的是消息中的header而不是路由键,他和direct完全一致,但是性能差很多,几乎用不到了
三、RabbitMQ安装测试: windows也类似。
安装RabbitMQ: docker
下载安装带有management版本的。这些带有web的管理界面: docker pull rabbitmq:3.8.7-management
运行docker:
客户端端口:5672
管理界面管理web页面的端口:15672
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq 容器id
打开网页: 用户名密码都是guest
具体操作RabbitMQ: 按照下面的图创建消息交换器和队列:研究exchange的4种类型的特点
创建Exchange交换器:
创建queues消息队列:
绑定交换器和消息队列: 将上面创建的三个交换器全部绑定点击的四个队列。
- 路由键和绑定的队列queue值相同(这里队列的路由键会和消息里的路由键匹配)
- 不过topic的路由键routing key 指定为: xxx.#、*.xxx、#.xxx等(#0到多个单词,*一个单词)
开始发送消息: 分别测试每个Exchang类型发送不同的消息,发送之后去队列里面查看接收情况
direct: 点对点通信,给路由键完全一致的队列发送消息。当消息被消费者接收后就消失。
fanout: 广播通信,和路由键无关。所有绑定的队列都会收到消息。
topic: 模糊匹配,因为是*和#所以匹配的所有队列都会收到消息
接收消息:
四、整合RabbitMQ:
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
打开RabbitMq的自动配置类:RabbitAutoConfiguration
在第二个RabbitTemplaConfiguration中注册了以下bean:
rabbitTemplateConfigurer: 将配置文件的数据转换为RabitMQ能使用的数据
RabbitTemplate:等价于Redis的RedisTemplate,重要的bean
AmqpAdmin: RabbitMQ的系统管理组件,不用来发送消息和接收消息,他用来创建一个队列,创建一个交换器等,这样就不用手动创建了。
配置连接:
spring:
rabbitmq:
addresses: 192.168.2.111 #默认localhost
username: guest #用户名密码都是guest
password: guest
port: 5672 #默认5672
virtual-host: / #默认/
测试发送和接收消息: 先解决乱码问题! 后面写了。无论单播还是广播,只要发送给对应的交换器Exchange就会自动转换!
package com.springboot.msg;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
@SpringBootTest
class Springboot12AmqpApplicationTests {
//注入RabbitMQ
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void direct(){
/**
* 测试单播,点对点: direct
* 发送消息: send、convertAndSend
* 接收消息: receiveAndConvert
*/
/*
发送主要使用下面两个方法: 一般都使用第二种
// Message需要自己定义消息体内容和消息头
rabbitTemplate.send(exchange, routingKey, Message message);
//Object默认当做消息体,只需要传入要发送的对象,自动序列化发送给 RabbitMQ
rabbitTemplate.convertAndSend(exchange, routingKey, Object message); //参数对应创建的管理界面中写
接收消息:
*/
//发送消息:
HashMap<String , Object> map = new HashMap<>();
map.put("msg","第一个参数");
map.put("data","number2");
//无论单播还是广播,只要将消息发送给对应的交换器就会自动转换
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",map);
// 接收数据
Object o = rabbitTemplate.receiveAndConvert("atguigu.news");
// 数据能够准确的转义过来。 并且消息队列里的该数据也就不存在了
System.out.println(o.getClass()); //class java.util.HashMap
System.out.println(o); //{msg=第一个参数, data=number2}
}
@Test
public void fanout(){
/**
* 测试广播: 广播的交换器是: exchange.fanout
* 所有队列都能收到!
*/
ArrayList<Object> list = new ArrayList<>();
list.add("张三");
list.add("23岁");
rabbitTemplate.convertAndSend("exchange.fanout","和key无关",list);
}
}
- 查看是否接收到信息: 虽然乱码了! 但是收到了。
- 解决乱码: 数据是通过转换器转换数据的
// 在RabbitTemplate里面的 208行左右有: 其中指定了序列化的方式,但是我们当然希望数据以 JSON 的格式传输。因此要自己定义并覆盖!
private MessageConverter messageConverter = new SimpleMessageConverter();
- 使用Jackson: 推荐,不过每次发送消息都要调用
HashMap<String , Object> map = new HashMap<>();
map.put("msg","第一个参数");
map.put("data","number2");
ObjectMapper objectMapper = new ObjectMapper();
String m = objectMapper.writeValueAsString(map);
rabbitTemplate.convertAndSend("exchange.direct","atguigu.news",m);
- 自定义MessageConverter: 无论哪次发送消息后都不用再管了
这是MessageConverter的所有实现类,默认是蓝色的Simplexxx,这里要使用Json的。
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class myAMQPConfig {
@Bean
public MessageConverter messageConverter(){ //别导错包 amqp 包下的,下面用转Json
return new Jackson2JsonMessageConverter();
}
}
监听消息: 其他系统或业务会监听消息队列中是否有消息入队,如果有,这些系统或业务等就进行某种操作
如: 有订单系统和库存系统,这两个系统的数据都是通过消息队列进行交互。如果一个用户下了单就会将消息放入消息队列中,而库存系统就必须要实时监听消息队列中的消息,一旦有新的订单进来,库存系统就要进行库存相关的操作
特点:
* 监听会把消息队列中的全部消息取出然后消费!即使消息中的数据类型是无法匹配参数类型的消息也会被消费!!
* 如果参数类型是Object,会将消息的全部属性获取到!!(下面方框中的一大串数据都是)
* 监听消费消息 的优先级高于 接收消息的方法 (rabbitTemplate.receiveAndConvert("路由键");) 监听会先消费消息
在配置类上添加 @EnableRabbit
- @RabbitListener(queues={"xxx"})
- 该方法开启监听 名字为xxx的消息队列。queues为数组类型,可以同时监听多个消息队列!
- 如果插入队列的消息类型是Student类型,就会获取过来为监听的方法使用。如果不是,参数值就为null,并且取出该消息!
第二个测试方法:Object参数获取消息的全部数据:
obj: (Body:'{"msg":"第一个参数","data":"number2","student":{"id":1,"name":"张三"}}' MessageProperties [headers={__ContentTypeId__=java.lang.Object, __KeyTypeId__=java.lang.Object, __TypeId__=java.util.HashMap}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=exchange.direct, receivedRoutingKey=atguigu.news, deliveryTag=1, consumerTag=amq.ctag-aBFcC85DVd3CkiJ6kqhEDA, consumerQueue=atguigu.news])
AmqpAdmin:帮助管理队列和交换器。
- 使用消息的时候并不一定都把所有的队列和交换器都创建好的,所以我们可以一边使用一边创建
1、注入AmqpAdmin: 已经RabbitAutoConfiguration中自动配置好了(最上面有写)
2、使用:都是amqpAdmin对象的方法。 具体方法和使用看源码更省事!!
declareXxx: declare开头的都是创建一些组件
delete/remove: 删除组件
declareExchange(Exchange e): 创建交换器。参数是接口,具体实现可以指定创建什么类型的交换器(direct、fanout、topic)
Exchange的名字默认是随机的,可以通过参数指定名字。
declareQueue(Queue q): 创建队列。Queue本身就是一个类,所以参数可以直接new Queue()。 两个参数的构造:(队列名字,是否持久化)
Binding(queueName/exchangeName, <-Type, exchangeName, routingKey,Map<String, Object> arguments): 绑定队列和交换器。
第一个参数: 可以通过Exchange绑定Queue,也可以反过来绑定;
第二个参数: 是第一个参数的类型,是Exchange就为Exchange类型,Queue就是Queue类型;
第三个参数: 按理说第一个参数是Queue,第三个参数是Exchange;
第四个参数: 现场指定的key;
第五个参数: 一般为null;
全部方法:可以参考使用下面方法