AMQP简介
AMQP是什么?
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是进程之间传递异步消息的网络协议。
AMQP工作过程
发布者(Publisher)发布消息(Message),经过交换机(Exchange),交换机根据路由规则将收到消息分发给交换机绑定的队列(Queue),最后AMQP代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
队列
队列是数据结构中概念。数据存储在一个队列中,数据是有顺序的,先进的先出,后进后出。其中一侧负责进数据,另一侧负责出数据。
MQ(消息队列)很多功能都是基于此队列结构实现的!
RabbitMQ
RabbitMQ介绍
RabbitMQ是由Erlang(二郎神)语言编写的基于AMQP的消息中间件。而消息中间件作为分布式系统重要组件之一,可以解决应用耦合,异步消息,流量削峰等问题。
RabbitMQ适用场景
通过队列的特性可以实现排队算法、秒杀活动
通过异步消息的特性可以实现消息分发、异步处理、数据同步、处理耗时任务、流量销峰
RabbitMQ原理
RabbitMQ会根据发布的消息通过路由分发给相应的交换器,然后交换器会根据绑定的规则分发给相应的队列,最终消费者会从相应的队列中拿到对应的消息。
1. Message
消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列可选属性组成,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(消息递送的模式,是否需要持久化等)等。
2. Publisher
消息的发送者(发送消息的客户端程序)。
3. Consumer
消息的消费者(通过信道获取消息的客户端程序)。
4. Exchange
交换器,接受消息发送者的消息,并把消息分发给对应的消息队列,发送给对应消息队列的规则和接受消息的路由键取决于交换器的类型。常用的交换器有direct(直接)、fanout(广播)、topic(主题)。
5.Binding
绑定。用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
6.Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面直到消费者连接到这个队列将其取走。
7.Routing-key
路由键。RabbitMQ决定消息该投递到哪个队列的规则。队列通过路由键绑定到交换器。消息发送到MQ服务器时,消息将拥有一个路由键,即便是空的,RabbitMQ也会将其和绑定使用的路由键进行匹配。如果相匹配,消息将会投递到该队列。如果不匹配,消息将无法投递。
8.Connection
指rabbit服务器和服务建立的TCP连接。
9.Channel
信道,是TCP里面的虚拟连接接。负责发布消息、接收消息、订阅队列。
10.Virtual Host
虚拟主机。表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个虚拟主机本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。虚拟主机是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是**/**
11.Borker
消息队列服务器实体。
RabbitMQ安装
由于目前我的电脑无法运行虚拟机,所有先只提供windows平台的安装,linux的安装步骤会在后续进行补充。
Windows 安装
正如我们上面所说的,RabbitMQ是基于Erlang语言开发的,所以安装时需要先安装Erlang运行环境(我们称它为二郎神 )。
RabbitMQ最新版本的下载地址为https://github.com/rabbitmq/rabbitmq-server/releases,目前最新的版本为v3.10.7。下载完成后根据对应的指引安装即可(安装步骤很简单,一直点下一步即可)。
安装完成后,运行RabbitMQ Server\rabbitmq_server-3.10.7\sbin
目录下的rabbitmq-server.bat
即可。
tips:如果运行失败,可查看日志,通常是RabbitMQ服务已经运行,在任务管理器中找到这个服务并关闭,重新执行rabbitmq-server.bat命令即可。
启动成功后可直接访问:http://127.0.0.1:15672,如果访问成功则说明安装是成功的。这里默认的用户名密码为(root/root)。
RabbitMQ在Java中的使用
默认api(Exchange为direct的使用):
发送:
ConnectionFactory factory = new ConnectionFactory();
// 主机地址
factory.setHost("127.0.0.1");
// 端口(同默认可以没有)
// factory.setPort(5672);
// 用户名和密码
factory.setUsername("root");
factory.setPassword("root");
// 虚拟机(同默认可以没有)
// factory.setVirtualHost("/");
// 创建连接
Connection conn = factory.newConnection();
// 创建渠道
Channel ch = conn.createChannel();
// 定义队列
ch.queueDeclare("myQueue", true, false, false, null);
// 发送消息
for (int i = 0; i < 10; i++) {
ch.basicPublish("", "myQueue", null, ("发送消息" + i).getBytes());
}
ch.close();
conn.close();
接受(单次):
// 省略创建渠道代码
GetResponse res = ch.basicGet("myQueue", true);
if (res != null) {
System.out.println(new String(res.getBody()));
}
接收(监听信道):
// 省略创建渠道代码
// 消费消息
ch.basicConsume("myQueue", new DefaultConsumer(ch) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
// 应答
ch.basicAck(envelope.getDeliveryTag(), false);
// 路由
String routingKey = envelope.getRoutingKey();
System.out.println(routingKey + ":" + new String(body));
}
});
默认的api需要自己建立和释放连接。而且需要自定义信道和消费处理。当你使用的是springboot项目时,我们有另一种方式可以集成rabbitmq。具体使用方法如下:
在springboot中使用rabbitmq
配置:
application.yml
spring:
rabbitmq:
host: 127.0.0.1
username: root
password: root
引入依赖(pom.xml):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
发送启动类:
package org.example;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import javax.annotation.Resource;
@SpringBootApplication
public class RabbitMQSenderApp {
@Bean
public Queue queue(){
Queue queue = new Queue("myQueue");
return queue;
}
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(RabbitMQSenderApp.class, args);
AmqpTemplate amqpTemplate = ctx.getBean(AmqpTemplate.class);
for (int i = 1; i < 11; i++) {
amqpTemplate.convertAndSend("myQueue", "这是内容" + i);
}
}
}
从上面的代码可以知道,springboot项目中通过AmqpTemplate对象就可以直接进行消息发送。
接收启动类:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RabbitMQReceiverApp {
@RabbitListener(queues = "myQueue")
public void demo(String msg) {
System.out.println("获取到的消息1111:" + msg);
}
@RabbitListener(queues = "myQueue")
public void demo2(String msg) {
System.out.println("获取到的消息2222:" + msg);
}
public static void main(String[] args) {
SpringApplication.run(RabbitMQReceiverApp.class, args);
}
}
同样的,通过RabbitListener注解配置后,即可直接得到消息进行处理,省略了中间的操作。
springboot接收到的结果为:
获取到的消息2222:这是内容1
获取到的消息1111:这是内容2
获取到的消息2222:这是内容3
获取到的消息1111:这是内容4
获取到的消息2222:这是内容5
获取到的消息1111:这是内容6
获取到的消息2222:这是内容7
获取到的消息1111:这是内容8
获取到的消息2222:这是内容9
获取到的消息1111:这是内容10
上面的结果可以看出,两个处理器是交替消费消息的。
tips:在上述代码执行时,我们可以在rabbitmq的控制台上看到消息发送和消费的具体情况。
其他交换器
direct作为默认的交换器,可以不进行声明绑定,队列默认绑定的就是direct交换器。
下面依然是以springboot为例子,讲解下剩下的两种交换器的使用。
fanout
这个交换器的作用是广播消息,往该交换器发送的详细,会以广播的形式转发给所有他绑定的队列。
发送配置:
// 队列fanout1
@Bean
protected Queue fanoutQueue1(){
return new Queue("fanout1");
}
// 队列fanout2
@Bean
protected Queue fanoutQueue2(){
return new Queue("fanout2");
}
// 交换器定义(fanout,可以从管理界面的Exchanges清单中看到)
@Bean
protected FanoutExchange fanoutExchange(){
return new FanoutExchange("amq.fanout");
}
// 绑定队列top1
@Bean
protected Binding fanoutBinding(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
// 绑定队列top2
@Bean
protected Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
发送消息代码片段:
for (int i = 0; i < 10; i++) {
amqpTemplate.convertAndSend("amq.fanout","asdfadsf", "fanout message" + i);
}
System.out.println("发送成功");
上述的代码回发送10条消息给fanout1和fanout2队列。两个队列都会收到相同的消息(可以从管理界面验证)。对于接受,同direct的接受一致,只需要把队列名称改为fanout相应的队列即可。
topic
订阅的方式发送消息,这里需要注意的是,订阅的话有一个路由key,路由key可以固定,也可以为表达式。以下为key的几种表示:
路由key | 表示 |
---|---|
a.b | a.b。 |
a.b.# | a.b.c/a.b.d等,表示a.b后面可以有一位,所以这个不能表示a.b.c.d。 |
a.b.* | 路由key以a.b开头,后面无限制,例如:可以为a.b.c.d。 |
topic的配置与fanout类似,只是在绑定时需要指定路由key: |
@Bean
protected Queue topQueue1(){
return new Queue("top1");
}
@Bean
protected Queue topQueue2(){
return new Queue("top2");
}
@Bean
protected TopicExchange topExchange(){
return new TopicExchange("amq.topic");
}
@Bean
protected Binding topicBinding(Queue topQueue1, TopicExchange topExchange){
return BindingBuilder.bind(topQueue1).to(topExchange).with("com.buss.#");
}
@Bean
protected Binding topicBinding2(Queue topQueue2,TopicExchange topExchange){
return BindingBuilder.bind(topQueue2).to(topExchange).with("com.buss.*");
}
发送消息:
amqpTemplate.convertAndSend("amq.topic","com.buss.c", "topic message1");
amqpTemplate.convertAndSend("amq.topic","com.buss.a.b.c", "topic message2");
上文可以发送message给队列top1一条消息。同时,回发送给top2两条消息。