RabbitMQ背景介绍
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端是用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
作为老牌的MQ消息队列,我打算在接下来的时间学习一下它。
RabbitMQ应用场景
消息队列的应用场景RabbitMQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。(随便看一下就可以了。知道怎么回事就行啦)
RabbitMQ环境安装
因为我是在Docker里面安装的,安装方法也比较简单。可以参考我之前的文章Docker下载和部署项目
RabbitMQ是带有客户端的消息中间件,我们可以通过网址来访问它(服务器IP:15672,一般默认密码都是 guest/guest)
RabbitMQ常用使用的开发方法
环境也安装好了 ,接下来我们在项目中实际操作一下,看看怎么使用的
先提一个概念
RabbitMQ有很多概念的名词;
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Connection
网络连接,比如一个TCP连接。
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker
表示消息队列服务器实体。
转载链接:https://www.jianshu.com/p/79ca08116d57/
由于这些内容设计的满多的内容,我打算在后续文章中分别介绍一下每一种的概念,这一次主要是介绍Exchange交换器和使用不同的Exchange交换器来做消息的接收和发送。
- DirectExchange
项目搭建
新建一个springboot项目,相关依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>RabbitMQDemo</groupId>
<artifactId>RabbitMQDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
在application中添加RabbitMQ的相关配置
server:
port: 8888
spring:
application:
name: springboot-rabbitmq
rabbitmq:
host: /*这里替换成部署在Docker里面地址,如果部署在本地填写localhost即可*/
port: 5672
username: guest
password: guest
# 发布确认
publisher-confirms: true
virtual-host: /
交换器介绍
RabbitMQ常用的交换器一共有四种,基本上能满足所有的业务需求,交换机的实质其实是通过不同的队名名称进行匹配,接下来我们通过代码来介绍一个常用交换机的用法
- DirectExchange
DirectExchange是RabbitMQ的默认交换机,直接使用routingKey匹配队列。通俗点描述就生产者直接把消息生产到指定到某一个队列上,消费者直接在这个队列上消费即可。**
新建一个队列
@Configuration
public class Rabbitonfig {
/**
* 配置routingKey,名字叫做hello的队列
* @return
*/
@Bean
public Queue helloQueue() {
return new Queue("hello");
}
}
消息发送者
@Component
public class HelloSender {
// rabbitTemplate是springboot 基于rabbitmq 实现的一些基本的队列消息发送功能。
@Autowired
private AmqpTemplate rabbitTemplate;
public void send() {
String context = "hello " + LocalDate.now();
System.out.println("Sender : " + context);
this.rabbitTemplate.convertAndSend("hello", context);
}
}
消息接收者
/**
* rabbit监听已经配置的列
*
* @author koala
* @date 2018/11/17
*/
@Component
public class HelloReceiver {
@RabbitHandler
@RabbitListener(queues = "hello")
public void process(String hello) {
System.out.println("Receiver1 : " + hello);
}
}
接着我们编写一个controller层给外部调用
@GetMapping("/hello")
public void hello(){
helloSender.send();
}
我们通过接口 http://localhost:8888/rabbit/hello 访问之后,消息生产者就会把接收到的消息发送给“hello”这个队列,而我们的消费者的监听hello队列就能处理这个消息。如下图
- TopicExchange
TopicExchange是按规则转发消息,是交换机中最灵活的一个。也是最常用的一个。
TopicExchange交换机支持使用通配符*、#
*号只能向后多匹配一层路径。#号可以向后匹配多层路径。
首先我们定义2个队列:
/**
* @author koala
* @date 2018/11/17
* RabbitTopicExchangeConfig
* topic 是RabbitMQ中最灵活的一种方式,可以根据routing_key自由的绑定不同的队列
*/
@Configuration
public class RabbitTopicExchangeConfig {
// 队列的名字
final static String core = "api.core";
final static String payment = "api.payment";
// 创建2个队列 api.core 和 api.payment
@Bean
public Queue coreQueue(){
return new Queue(RabbitTopicExchangeConfig.core);
}
@Bean
public Queue paymentQueue(){
return new Queue(RabbitTopicExchangeConfig.payment);
}
/**
* 分别创建TopicExchange交换器
**/
@Bean
public TopicExchange coreExchange() {
return new TopicExchange("coreExchange");
}
@Bean
public TopicExchange paymentExchange() {
return new TopicExchange("paymentExchange");
}
/**
* 将两个队列分别部署到两个交换机上,并且设置 routingkey
* * 表示一次词,
* # 表示零个或者多个词
*/
@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.#");
}
这样我们的TopExchange对列就编写好了,接下来 我们编写一个发送端和一个接收端
TopicExchange发送端
/**
* 添加两个消息发送类(生产者)
* 添加一个user()方法,发送消息至coreExchange交换机且routingKey为api.core.user
* 添加一个userQuery()方法,发送消息至coreExchange交换机且routingKey为api.core.user.query
*
* @author koala
* @date 2018/11/17
*/
@Component
public class ApiCoreSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void user(String msg){
System.out.println("api.core.user send message: "+msg);
rabbitTemplate.convertAndSend("coreExchange", "api.core.user", msg);
}
public void userQuery(String msg){
System.out.println("api.core.user.query send message: "+msg);
rabbitTemplate.convertAndSend("coreExchange", "api.core.user.query", msg);
}
}
TopicExchange接收端
/**
* 监听routingKey为api.core的队列消息
*
* @author koala
* @date 2018/11/17
*/
@Component
public class ApiCoreReceive {
@RabbitHandler
@RabbitListener(queues = "api.core")
public void user(String msg) {
System.out.println("ApiCoreReceive得到消息:"+ msg);
}
}
接着我们编写一个controller 看一下效果
/**
* TopicExchangeController层
*
* @author koala
* @date 2019/1/16
*/
@RestController
@RequestMapping("/topic")
public class RabbitTopicExchangeController {
private HelloSender helloSender;
private ApiCoreSender apiCoreSender;
@Autowired
RabbitTopicExchangeController(HelloSender helloSender,ApiCoreSender apiCoreSender){
this.helloSender = helloSender;
this.apiCoreSender = apiCoreSender;
}
/**
* 消息生产者
* @param user
*/
@GetMapping("/user")
public void sendUser(@RequestParam String user) {
this.apiCoreSender.user(user);
}
@GetMapping("/userQuery")
public void sendUserQuert(@RequestParam String param){
this.apiCoreSender.userQuery(param);
}
执行:
http://localhost:8888//topic/user?user=koala
http://localhost:8888//topic/userQuery?param=koala-param
输出:
- 为什么userQuery方法没有接收到消息呢?
这是因为 我们的我们TopicExchange绑定的队列是一个“api.core.”,在topicExchange中, 号只能向后匹配一层路径。而我们userQuery的发送端却是2层路径
所以我们的消息实际上并没有发送成功,所以接收端自然无法收到。