一、消息中间件概述
1.大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力、可在高并发场景中起到流量削峰的作用
1)传统的同步调用,导致系统同步通信效率低
、
使用消息队列解决异步通信的问题,将注册信息按格式写入消息队列,发送邮件和发送注册短信的功能进行异步调用,从而加快系统的响应速度。
2)应用解耦,当分布式项目的某个系统如果直接调用另一个系统的接口,会使应用的耦合度升高。
当用了消息队列后,可以使用库存系统进行订阅监听消息队列来实现系统之间的调用,解除系统之间的耦合度,系统只需要面向消息队列即可。
3)流量削峰,比如在一个秒杀系统中,在同一时刻可能有很高很高的并发量,那么高的QPS可能会导致服务器瞬间崩掉,可以使用消息队列,来平缓某一时刻的并发量高峰,使服务器有序的从队列中拿出请求去处理。
2.消息服务中两个重要概念:
消息代理(message broker)和目的地(destination)
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3.消息队列主要有两种形式的目的地
队列(queue):点对点消息通信(point-to-point)
–消息发送者发送消息,消息代理将其放入一个队列中,消息接收者从队列中获取消息内容,消息读取后被移出队列
–消息只有唯一的发送者和接受者,但并不是说只能有一个接收者
主题(topic):发布(publish)/订阅(subscribe)消息通信
–发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息到达时同时收到消息
4.消息队列的两种规范:JMS(Java Message Service)JAVA消息服务 和 AMQP(Advanced Message Queuing Protocol)
JMS规范是基于JVM的消息代理规范,其实现产品有ActiveMQ、HornetMQ。
AMQP是一种高级消息队列协议,也是一个消息代理的规范,并兼容JMS,和平台、语言无关。RabbitMQ是AMQ的实现。
| JMS | AMQP |
定义 | Java api | 网络线级协议 |
跨语言 | 否 | 是 |
跨平台 | 否 | 是 |
Model | 提供两种消息模型: (1)、Peer-2-Peer(点对点) (2)、Pub/sub(发布、订阅) | 提供了五种消息模型: (1)、direct exchange (2)、fanout exchange (3)、topic change (4)、headers exchange (5)、system exchange 本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分; |
支持消息类型 | 多种消息类型: TextMessage MapMessage BytesMessage StreamMessage ObjectMessage Message (只有消息头和属性) | byte[] 当实际应用时,有复杂的消息,可以将消息序列化后发送。 |
综合评价 | JMS 定义了JAVA API层面的标准;在java体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差; | AMQP定义了wire-level层的协议标准;天然具有跨平台、跨语言特性。 |
5.Spring对消息队列的支持
–spring-jms提供了对JMS的支持
–spring-rabbit提供了对AMQP的支持
–需要ConnectionFactory的实现来连接消息代理
–提供JmsTemplate、RabbitTemplate来提供了操作消息中间件的api
–@JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
–@EnableJms、@EnableRabbit在启动类上可以开启对监听注解的支持
6.Springboot有关消息队列的自动配置类
JmsAutoConfiguration 和 RabbitAutoConfiguration
二、RabbitMQ
RabbitMQ简介:RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
从RabbitMQ的运行原理图来介绍RabbitMQ的核心概念
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。为publisher发出的内容。(类似商城系统秒杀的请求)
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。(商城系统客户端)
Broker
表示消息队列服务器实体
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别(类似于现实生活中的交换机,当数据传给交换机,由交换机根据策略决定把数据交给哪个网线)
Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。(存放消息)
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和Queue的绑定可以是多对多的关系。(决定了Exchange将消息放入哪个队列)
Connection
网络连接,比如一个TCP连接。
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。(不然重复打开或关闭一个TCP连接是一个很大的资源开销)
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。(秒杀系统的服务器)
三、RabbitMQ的运行机制
AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列。
•Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键, headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
1.Direct类型
消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。如果一个队列绑定到交换机要求路由键为“dog”,则只转发 routing key 标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式。
2.Fanout
每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。
3.Topic
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配一个单词。
四、Springboot整合RabbitMQ
1.在Linux系统上通过docker容器运行起RabbitMQ服务器,并暴露端口5672与15672
运行起来之后通过访问RabbitMQ的WEB控制台可查看是否安装成功
默认账号密码就是guest
2.在pom.xml中引入RabbitMQ的场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.通过看 RabbitAutoConfiguration类 可以得知Springboot给我们自动配置了连接工厂 ConnectionFactory ,对应有一个RabbitProperties类封装了 RabbitMQ 的配置,所以我们可以在配置文件中需要配置连接RabbitMQ服务器的属性
spring.rabbitmq.host=118.24.44.169
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#默认virtual-host为/
#spring.rabbitmq.virtual-host=
4.在Springboot对RabbitMQ的自动配置类中,给容器注入了一个RabbitTemplate组件,这个组件就是封装了对RabbitMQ操作的API。
以对direct交换机的的示例来演示RabbitTemplate的用法
@SpringBootTest
class SpringbootActivemqApplicationTests {
//自动注入容器中的RabbitTemplate
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void send() {
Map map = new HashMap();
map.put("123","你好");
//向exchange.direct这个交换器,发送一个路由键为xijian的map对象
rabbitTemplate.convertAndSend("exchange.direct","xijian",map);
}
@Test
void recieve(){
//接收消息并打印
Object xijian = rabbitTemplate.receiveAndConvert("xijian");
System.out.println(xijian);
}
}
我已经在客户端上创建好了一些Exchanges和Queues,并做好了内部的一些binding
如下
我们在控制台执行send()方法后,可见xijian这个队列的消息数变为了一个。
消息内容为序列化后的Map对象。
调用recieve()方法后,可在控制台接收到这个消息,并会将其自动反序列化,同时队列中的消息数变为了0。
5.自动配置类中还注入了 AmqpAdmin 类型的一个对象,负责对exchange,queue,binding的操作,具体操作很简单,不在具体演示,原理同上。
6.Springboot对于将对象转换为消息体的时候默认使用的是java对象的序列化,所以我们可以看到当生产出一条消息插入队列中时,消息内容为一堆序列码,我们可以更改Springboot对序列化的默认配置,可以更改为对Json的转换。
在RabbitTemplate对象中有一个 MessageConventer 的声明,默认使用的是java的序列化机制,其实现类中有一个关于Json序列化的实现类对象,将其通过配置类注入到容器中即可
package com.xijian.springbootactivemq.config;
import org.springframework.amqp.support.converter.AbstractJackson2MessageConverter;
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;
/**
* @author XiJian
* @date 2019/11/26 - 13:55
*/
@Configuration
public class RabbitMqConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
7.如果想实现发布-订阅的那种功能,即消息一旦被放入消息队列中,消费端就可以通过监听器将消息从队列中取出。
1)在Springboot启动类上加上@EnableRabbit注解,开启基于注解的RabbitMQ模式
2)在消费的方法上加上@RabbitListener(queues = "xijian")来对某一消息队列进行实时监听
一旦Springboot容器跑起来,当有消息被放入正在被监听的消息队列中后会立刻调用该方法进行对消息的消费。