目录
配套资料,免费下载
链接:https://pan.baidu.com/s/1jA217UgqXpONi_fV-aOzqw
提取码:bm2g
复制这段内容后打开百度网盘手机App,操作更方便哦
1、RabbitMQ介绍
RabbitMQ
RabbitMQ是使用Erlang语言来编写的并且RabbitMQ是基于AMQP协议的。Erlang语言在数据交互方面性能优秀,有着和原生Socket一样的延迟,这也是RabbitMQ高性能的原因所在。可谓“人如其名”,RabbitMQ像兔子一样迅速。RabbitMQ除了像兔子一样跑的很快以外,还有这些特点:
- 开源、性能优秀,稳定性保障
- 提供可靠性消息投递模式、返回模式
- 与Spring AMQP完美整合,API丰富
- 集群模式丰富,表达式配置,HA模式,镜像队列模型
- 保证数据不丢失的前提做到高可靠性、可用性
AMQP协议
提到RabbitMQ,就不得不提AMQP协议。AMQP协议是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。先了解一下AMQP协议中间的几个重要概念:
- Server:接收客户端的连接,实现AMQP实体服务。
- Connection:连接,应用程序与Server的网络连接,TCP连接。
- Channel:信道,消息读写等操作在信道中进行。客户端可以建立多个信道,每个信道代表一个会话任务。
- Message:消息,应用程序和服务器之间传送的数据,消息可以非常简单,也可以很复杂。有Properties和Body组成。Properties为外包装,可以对消息进行修饰,比如消息的优先级、延迟等高级特性;Body就是消息体内容。
- Virtual Host:虚拟主机,用于逻辑隔离。一个虚拟主机里面可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名称的Exchange或Queue。
- Exchange:交换器,接收消息,按照路由规则将消息路由到一个或者多个队列。如果路由不到,或者返回给生产者,或者直接丢弃。RabbitMQ常用的交换器常用类型有direct、topic、fanout、headers四种,后面详细介绍。
- Binding:绑定,交换器和消息队列之间的虚拟连接,绑定中可以包含一个或者多个RoutingKey。
- RoutingKey:路由键,生产者将消息发送给交换器的时候,会发送一个RoutingKey,用来指定路由规则,这样交换器就知道把消息发送到哪个队列。路由键通常为一个“.”分割的字符串,例如“com.rabbitmq”。
- Queue:消息队列,用来保存消息,供消费者消费。
我们完全可以直接使用 Connection 就能完成信道的工作,为什么还要引入信道呢?
试想这样一个场景, 一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么必然需要建立很多个 Connection,也就是许多个 TCP 连接。然而对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。 RabbitMQ 采用 TCP 连接复用的方式,不仅可以减少性能开销,同时也便于管理 。
下图是AMQP的协议模型:
正如图中所看到的,AMQP协议模型有三部分组成:生产者、消费者和服务端。
生产者是投递消息的一方,首先连接到Server,建立一个连接,开启一个信道;然后生产者声明交换器和队列,设置相关属性,并通过路由键将交换器和队列进行绑定。同理,消费者也需要进行建立连接,开启信道等操作,便于接收消息。接着生产者就可以发送消息,发送到服务端中的虚拟主机,虚拟主机中的交换器根据路由键选择路由规则,然后发送到不同的消息队列中,这样订阅了消息队列的消费者就可以获取到消息,进行消费。最后还要关闭信道和连接。
RabbitMQ是基于AMQP协议实现的,其结构如下图所示,和AMQP协议简直就是一模一样。
常用交换器
RabbitMQ常用的交换器类型有direct、topic、fanout、headers四种。
Direct Exchange
该类型的交换器将所有发送到该交换器的消息被转发到RoutingKey指定的队列中,也就是说路由到BindingKey和RoutingKey完全匹配的队列中。
Topic Exchange
该类型的交换器将所有发送到Topic Exchange的消息被转发到所有RoutingKey中指定的Topic的队列上面。Exchange将RoutingKey和某Topic进行模糊匹配,其中“*”用来匹配一个词,“#”用于匹配一个或者多个词。例如“com.#”能匹配到“com.rabbitmq.oa”和“com.rabbitmq”;而"login.*"只能匹配到“com.rabbitmq”。
Fanout Exchange
该类型不处理路由键,会把所有发送到交换器的消息路由到所有绑定的队列中。优点是转发消息最快,性能最好。
Headers Exchange
该类型的交换器不依赖路由规则来路由消息,而是根据消息内容中的headers属性进行匹配。headers类型交换器性能差,在实际中并不常用。
2、RabbitMQ安装
2.1、安装Erlang
下载地址:https://www.erlang.org/downloads
温馨提示:如果下载不下来,配套资料中已经帮你都下载好了
安装步骤:打开安装包,默认下一步即可。
环境变量:新建ERLANG_HOME,变量值为安装目录
修改 Path 环境变量,新增一条 %ERLANG_HOME%\bin
,如下所示:
测试是否安装成功,请重新打开一个 cmd 命令行窗口,输入 erl
2.2、安装RabbitMQ
下载地址的首页:https://www.rabbitmq.com/download.html
安装版下载地址:https://www.rabbitmq.com/install-windows.html
解压版下载地址:https://www.rabbitmq.com/install-windows-manual.html
将 rabbitmq-server-windows-3.8.9.zip 解压到 C:\DevTools\
环境变量:新建RABBITMQ_SERVER,变量值为解压后目录
修改 Path 环境变量,新增一条 %RABBITMQ_SERVER%\sbin
,如下所示:
安装RabbitMQ控制台,请到 C:\DevTools\rabbitmq_server-3.8.9\sbin
目录下,输入命令 rabbitmq-plugins enable rabbitmq_management
启动RabbitMQ服务,输入命令:rabbitmq-server.bat
启动成功后,如下图所示:
Rabbitmq启动成功,浏览器中http://localhost:15672,登录账户:guest,登录密码:guest
3、RabbitMQ集成
3.1、单播模式
DirectExchange是RabbitMQ的默认交换机,直接使用routingKey匹配队列,是一种精准匹配。
3.1.1、消息生产者
编写配置信息:application.yaml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
编写配置类:com.caochenlei.integrationrabbitmqproducer.config.DirectConfig
- 配置一个q.direct的消息队列并绑定在e.direct交换机上。(交换机的匹配规则为rk.direct)
@Configuration
public class DirectConfig {
@Bean
public Queue directQueue() {
//队列名字,是否持久化
return new Queue("q.direct", false);
}
@Bean
public DirectExchange directExchange() {
//交换器名称、是否持久化、是否自动删除
return new DirectExchange("e.direct", false, false);
}
@Bean
public Binding directBinding(Queue directQueue, DirectExchange directExchange) {
//将指定的交换器绑定到指定的队列,路由键是rk.direct
return BindingBuilder.bind(directQueue).to(directExchange).with("rk.direct");
}
}
编写生产者:com.caochenlei.integrationrabbitmqproducer.bean.DirectProducer
- 添加一个send()方法,发送消息至e.direct交换机且routingKey为rk.direct
@Component
public class DirectProducer {
@Autowired
private AmqpTemplate amqpTemplate;
public void send(String message) {
amqpTemplate.convertAndSend("e.direct", "rk.direct", message);
}
}
测试生产者:com.caochenlei.integrationrabbitmqproducer.IntegrationRabbitmqProducerApplicationTests
@SpringBootTest
class IntegrationRabbitmqProducerApplicationTests {
@Autowired
private DirectProducer directProducer;
@Test
void testDirectSend() {
directProducer.send("张三");
}
}
查看队列:http://localhost:15672/#/queues
查看交换机:http://localhost:15672/#/exchanges
查看绑定:http://localhost:15672/#/exchanges/%2F/e.direct
3.1.2、消息消费者
编写配置信息:application.yaml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
编写消费者:com.caochenlei.integrationrabbitmqconsumer.bean.DirectConsumer
- 添加receive()方法,监听队列名称为q.direct的队列消息
@Component
public class DirectConsumer {
@RabbitListener(queues = "q.direct")
public void receive(String message) {
System.out.println("接收消息:" + message);
}
}
重新启动这个工程,然后进行观察网页队列和控制台情况:http://localhost:15672/#/queues
3.2、主题模式
TopicExchange是按规则转发消息,是交换机中最灵活的一个,也是最常用的一个。
3.2.1、消息生产者
com.caochenlei.integrationrabbitmqproducer.config.TopicConfig
- 配置一个api.core的消息队列并绑定在coreExchange交换机上。(交换机的匹配规则为api.core.*)
- 配置一个api.payment的消息队列并绑定在paymentExchange交换机上。(交换机的匹配规则为api.payment.#)
@Configuration
public class TopicConfig {
@Bean
public Queue coreQueue() {
return new Queue("api.core");
}
@Bean
public Queue paymentQueue() {
return new Queue("api.payment");
}
@Bean
public TopicExchange coreExchange() {
return new TopicExchange("coreExchange");
}
@Bean
public TopicExchange paymentExchange() {
return new TopicExchange("paymentExchange");
}
@Bean
public Binding bindingCoreExchange(Queue coreQueue, TopicExchange coreExchange) {
return BindingBuilder.bind(coreQueue).to(coreExchange).with("api.core.*");
}
@Bean
public Binding bindingPaymentExchange(Queue paymentQueue, TopicExchange paymentExchange) {
return BindingBuilder.bind(paymentQueue).to(paymentExchange).with("api.payment.#");
}
}
com.caochenlei.integrationrabbitmqproducer.bean.TopicProducer
- 添加一个user()方法,发送消息至coreExchange交换机且routingKey为api.core.user
- 添加一个userQuery()方法,发送消息至coreExchange交换机且routingKey为api.core.user.query
- 添加一个order()方法,发送消息至paymentExchange交换机且routingKey为api.payment.order
- 添加一个orderQuery()方法,发送消息至paymentExchange交换机且routingKey为api.payment.order.query
@Component
public class TopicProducer {
@Autowired
private AmqpTemplate amqpTemplate;
public void user(String message) {
amqpTemplate.convertAndSend("coreExchange", "api.core.user", message);
}
public void userQuery(String message) {
amqpTemplate.convertAndSend("coreExchange", "api.core.user.query", message);
}
public void order(String message) {
amqpTemplate.convertAndSend("paymentExchange", "api.payment.order", message);
}
public void orderQuery(String message) {
amqpTemplate.convertAndSend("paymentExchange", "api.payment.order.query", message);
}
}
com.caochenlei.integrationrabbitmqproducer.IntegrationRabbitmqProducerApplicationTests
@SpringBootTest
class IntegrationRabbitmqProducerApplicationTests {
@Autowired
private TopicProducer topicProducer;
@Test
void testTopicUserSend() {
topicProducer.user("用户编号:987654321");
}
@Test
void testTopicUserQuerySend() {
topicProducer.userQuery("查询用户:987654321");
}
@Test
void testTopicOrderSend() {
topicProducer.order("订单编号:123456789");
}
@Test
void testTopicOrderQuerySend() {
topicProducer.orderQuery("查询订单:123456789");
}
}
测试testTopicUserSend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
测试testTopicUserQuerySend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
注意:因为在TopicConfig配置类中,我们对api.core队列绑定的交换机规则是api.core.*,而通配符“*”只能向后多匹配一层路径。
测试testTopicOrderSend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
测试testTopicOrderQuerySend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
3.2.2、消息消费者
com.caochenlei.integrationrabbitmqconsumer.bean.TopicConsumer
- 添加user()方法,监听队列名称为api.core的队列消息
- 添加order()方法,监听队列名称为api.payment的队列消息
@Component
public class TopicConsumer {
@RabbitListener(queues = "api.core")
public void user(String message) {
System.out.println(message);
}
@RabbitListener(queues = "api.payment")
public void order(String message) {
System.out.println(message);
}
}
重新启动这个工程,然后进行观察网页队列和控制台情况:http://localhost:15672/#/queues
3.3、广播模式
FanoutExchange交换机是转发消息到所有绑定队列(广播模式,和routingKey没有关系)。
3.3.1、消息生产者
com.caochenlei.integrationrabbitmqproducer.config.FanoutConfig
- 配置一个api.report.payment的消息队列并绑定在reportExchange交换机上。
- 配置一个api.report.refund的消息队列并绑定在reportExchange交换机上。
@Configuration
public class FanoutConfig {
@Bean
public Queue reportPaymentQueue() {
return new Queue("api.report.payment");
}
@Bean
public Queue reportRefundQueue() {
return new Queue("api.report.refund");
}
@Bean
public FanoutExchange reportExchange() {
return new FanoutExchange("reportExchange");
}
@Bean
public Binding bindingReportPaymentExchange(Queue reportPaymentQueue, FanoutExchange reportExchange) {
return BindingBuilder.bind(reportPaymentQueue).to(reportExchange);
}
@Bean
public Binding bindingReportRefundExchange(Queue reportRefundQueue, FanoutExchange reportExchange) {
return BindingBuilder.bind(reportRefundQueue).to(reportExchange);
}
}
com.caochenlei.integrationrabbitmqproducer.bean.TopicProducer
- 添加一个generateReports()方法,发送消息至reportExchange交换机
@Component
public class FanoutProducer {
@Autowired
private AmqpTemplate amqpTemplate;
public void generateReports(String message) {
amqpTemplate.convertAndSend("reportExchange", "api.report", message);
}
}
com.caochenlei.integrationrabbitmqproducer.IntegrationRabbitmqProducerApplicationTests#fanoutProducer
@SpringBootTest
class IntegrationRabbitmqProducerApplicationTests {
@Autowired
private FanoutProducer fanoutProducer;
@Test
void testGenerateReportsSend() {
fanoutProducer.generateReports("开始生成报表!");
}
}
测试testGenerateReportsSend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
3.3.2、消息消费者
com.caochenlei.integrationrabbitmqconsumer.bean.FanoutConsumer
- 添加payment()方法,监听队列名称为api.report.payment的队列消息
- 添加refund()方法,监听队列名称为api.report.refund的队列消息
@Component
public class FanoutConsumer {
@RabbitListener(queues = "api.report.payment")
public void payment(String message) {
System.out.println(message);
}
@RabbitListener(queues = "api.report.refund")
public void refund(String message) {
System.out.println(message);
}
}
重新启动这个工程,然后进行观察网页队列和控制台情况:http://localhost:15672/#/queues
3.4、头部模式
HeadersExchange交换机是根据请求消息中设置的header attribute参数类型来匹配的(和routingKey没有关系)。
3.4.1、消息生产者
com.caochenlei.integrationrabbitmqproducer.config.HeadersConfig
- 配置一个credit.bank的消息队列并绑定在creditBankExchange交换机上。
@Configuration
public class HeadersConfig {
@Bean
public Queue creditBankQueue() {
return new Queue("credit.bank");
}
@Bean
public HeadersExchange creditBankExchange() {
return new HeadersExchange("creditBankExchange");
}
@Bean
public Binding bindingCreditBankExchange(Queue creditBankQueue, HeadersExchange creditBankExchange) {
Map<String, Object> headerValues = new HashMap<>();
headerValues.put("type", "cash");
headerValues.put("aging", "fast");
return BindingBuilder.bind(creditBankQueue).to(creditBankExchange).whereAll(headerValues).match();
}
}
com.caochenlei.integrationrabbitmqproducer.bean.HeadersProducer
- 添加一个creditBank()方法,发送消息至creditBankExchange交换机且routingKey为credit.bank
@Component
public class HeadersProducer {
@Autowired
private AmqpTemplate amqpTemplate;
private Message getMessage(Map<String, Object> head, String message) {
//创建信息属性对象
MessageProperties messageProperties = new MessageProperties();
//循环添加头部信息
Set<Map.Entry<String, Object>> headerEntries = head.entrySet();
for (Map.Entry<String, Object> header : headerEntries) {
String key = header.getKey();
Object value = header.getValue();
messageProperties.setHeader(key, value);
}
//返回指定头部信息对象
return new Message(message.getBytes(), messageProperties);
}
public void creditBank(Map<String, Object> head, String message) {
amqpTemplate.convertAndSend("creditBankExchange", "credit.bank", getMessage(head, message));
}
}
com.caochenlei.integrationrabbitmqproducer.IntegrationRabbitmqProducerApplicationTests
@SpringBootTest
class IntegrationRabbitmqProducerApplicationTests {
@Autowired
private HeadersProducer headersProducer;
@Test
public void testCreditBankSend() {
Map<String, Object> head = new HashMap<>();
head.put("type", "cash");
headersProducer.creditBank(head, "银行授信(部分匹配)");
}
@Test
public void testCreditBankAllSend() {
Map<String, Object> head = new HashMap<>();
head.put("type", "cash");
head.put("aging", "fast");
headersProducer.creditBank(head, "银行授信(全部匹配)");
}
}
测试testCreditBankSend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
注意:部分匹配是无法将消息发送到指定队列中的。
测试testCreditBankAllSend(),测试完毕,打开队列查看当前队列情况:http://localhost:15672/#/queues
3.4.2、消息消费者
com.caochenlei.integrationrabbitmqconsumer.bean.HeadersConsumer
- 添加creditBank()方法,监听队列名称为credit.bank的队列消息
@Component
public class HeadersConsumer {
@RabbitListener(queues = "credit.bank")
public void creditBank(String message) {
System.out.println(message);
}
}
重新启动这个工程,然后进行观察网页队列和控制台情况:http://localhost:15672/#/queues
4、RabbitMQ注解
4.1、@RabbitListener 用法
使用 @RabbitListener 注解标记方法,当监听到队列 debug 中有消息时则会进行接收并处理。
@RabbitListener(queues = "debug")
public void processMessage(Message bytes) {
System.out.println(new String(bytes));
}
消息处理方法参数是由 MessageConverter 转化,默认 Spring 使用的实现是 SimpleRabbitListenerContainerFactory,SimpleMessageConverter 对于要发送的消息体 body 为 byte[] 时不进行处理,如果是 String 则转成字节数组,如果是 Java 对象,则使用 Jdk 序列化将消息转成字节数组,转出来的结果较大,含class类名,类相应方法等信息,因此性能较差。消息的 content_type 属性表示消息 body 数据以什么数据格式存储,接收消息除了使用 Message 对象接收消息(包含消息属性等信息)之外,还可直接使用对应类型接收消息 body 内容,但若方法参数类型不正确会抛异常:
- application/octet-stream:二进制字节数组存储,使用 byte[]
- text/plain:文本数据类型存储,使用 String
- application/x-java-serialized-object:java 对象序列化格式存储,使用 Object、相应类型(反序列化时类型应该同包同名,否者会抛出找不到类异常)
- application/json:JSON 格式,使用 Object、相应类型,需要自己配置 Jackson2JsonMessageConverter
当使用 RabbitMQ 作为中间件时,数据量比较大,此时就要考虑使用 Jackson2JsonMessageConverter 等序列化形式以此提高性能,序列化与反序列化配置:
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 AMQPConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
生产者和消费者都需要配置上边这一段配置,使用了之后,所有序列化和反序列化都将使用 Jackson2JsonMessageConverter ,如果启动报错,请加入依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
通过 @RabbitListener 的 bindings 属性声明 Binding(若 RabbitMQ 中不存在该绑定所需要的 Queue、Exchange、RouteKey 则自动创建,若存在则抛出异常)
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = "topic.exchange",durable = "true",type = "topic"),
value = @Queue(value = "consumer_queue",durable = "true"),
key = "key.#"
))
public void processMessage(Message message) {
System.out.println(message);
}
4.2、@RabbitHandler 用法
@RabbitListener 可以标注在类上面,需配合 @RabbitHandler 注解一起使用,@RabbitListener 标注在类上面表示当有收到消息的时候,就交给 @RabbitHandler 的方法处理,具体使用哪个方法处理,根据 MessageConverter 转换后的参数类型。
@Component
@RabbitListener(queues = "consumer_queue")
public class Receiver {
@RabbitHandler
public void processMessage1(String message) {
System.out.println(message);
}
@RabbitHandler
public void processMessage2(byte[] message) {
System.out.println(new String(message));
}
}
4.3、@Payload 与 @Headers 用法
使用 @Payload 和 @Headers 注解可以消息中的 body 与 headers 信息。
@RabbitListener(queues = "debug")
public void processMessage1(@Payload String body, @Headers Map<String,Object> headers) {
System.out.println("body:"+body);
System.out.println("Headers:"+headers);
}
也可以获取单个 Header 属性。
@RabbitListener(queues = "debug")
public void processMessage1(@Payload String body, @Header String token) {
System.out.println("body:"+body);
System.out.println("token:"+token);
}