上篇文章:
目录
[1 AMQP协议](#1 AMQP协议)
[2 工作流程](#2 工作流程)
[3 流程演示](#3 流程演示)
[3.1 引入依赖](#3.1 引入依赖)
[3.2 生产者](#3.2 生产者)
[3.3 消费者](#3.3 消费者)
1 AMQP协议
AMQP(Advanced Message Queuing Protocol)协议是一种高级消息队列协议,它定义了消息交换功能 ,包含****交换机(Exchange)和队列(Queue)****组件,生产者的消息首先传输到交换机,由交换机进行路由分发到指定的队列中并等待消费者接收。
除此以外,它还定义了网络通信的协议,允许生产者和消费者遵循该协议和消息代理来与实现了AMQP协议的模型进行通信。
因此,RabbitMQ就是实现了AMQP协议的一种产品,其内部结构也包含交换机和队列。
2 工作流程
RabbitMQ遵循生产者消费者模型,由生产者Producer生产消息,RabbitMQ作为消息中间件暂时存储消息到队列中,消费者Consumer从队列中获取消息(不需要知道是谁生产的)。同时,生产者和消费者也是RabbitMQ的客户端。

**Connection:**Producer和RabbitMQ、Consumer和RabbitMQ基于TCP来建立连接,一个连接包含多个通道channel(逻辑上各通道之间相互独立,用于实现连接复用,减少频繁建立/关闭TCP连接的开销),发送消息和接收消息都是基于channel。
**Virtual Host:**逻辑上相互独立的虚拟机,不同于安装不同系统的那个虚拟机。类似于MySQL的多个databases,各个database相互隔离,提供不同的数据存储服务给不同的业务。Virtual Host也是相互隔离,提供不同的消息交换服务给不同的用户(使用方)。
**Exchange:**交换机,生产者的消息进入RabbitMQ后首先进入Exchange,由Exchange进行路由分发(根据消息类型、规则和标签等等),把消息分发给指定的Queue。如果找不到Queue,就会丢弃消息/返回给生产者(根据生产者配置策略)。
**Queue:**队列,存储消息。Queue和Consumer之间是多对多关系,即一个Queue可以被多个Consumer订阅;一个Consumer可以订阅多个不同的Queue。
完整的流程:
1.Producer生产了一条消息(通常带有标签来方便路由);
2.Producer和RabbitMQ建立Connection并开启一个通道channel;
3.Producer声明Exchange和Queue分别是谁;
4.Producer发送消息给RabbitMQ;
5.Exchange根据消息类型、标签等信息把消息路由给Queue;
6.Consumer根据订阅的Queue以及消息推送的不同策略来消费消息。
3 流程演示
3.1 引入依赖
XML
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.20.0</version>
</dependency>
3.2 生产者
java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitProducer {
public static void main(String[] args) throws Exception {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.217.150");//ip
factory.setPort(5672); //端口号
factory.setVirtualHost("testVirtual");//虚拟机名称,默认为/
factory.setUsername("admin");//user,默认guest
factory.setPassword("admin");//password,默认guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4.创建channel通道
Channel channel = connection.createChannel();
//5.声明队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments)
1.queue:队列名称
2.durable:是否持久化
3.exclusive:是否独占,只能有一个消费者监听队列,当Connection关闭时,是否删除队列
4.autoDelete: 是否自动删除,当没有Consumer时,自动删除掉
5.arguments: 一些其它参数
*/
//如果没有队列,会自动创建,如果有,则不创建
channel.queueDeclare("queue1", true, false, false, null);
//6. 通过channel发送消息到队列中
/*
basicPublish(String exchange, String routingKey, AMQP.BasicProperties
props, byte[] body)
1.exchange:交换机名称,简单模式下,交换机会使用默认的""
2.routingKey:路由名称,就是路由到哪个队列
3.props:配置信息
4.body:消息内容
*/
String msg = "Hello RabbitMQ";
//使用的是内置交换机,使用内置交换机时,routingKey要和队列名称⼀样,才可以路由到对应的队列上去
channel.basicPublish("", "queue1", null, msg.getBytes());
//7.释放资源
System.out.println(msg + "消息发送成功");
channel.close();
connection.close();
}
}

3.3 消费者
java
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class RabbitmqConsumer {
public static void main(String[] args) throws Exception {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("192.168.217.150");//ip
factory.setPort(5672); //端口号
factory.setVirtualHost("testVirtual");//虚拟机名称,默认为/
factory.setUsername("admin");//user,默认guest
factory.setPassword("admin");//password,默认guest
//3.创建连接Connection
Connection connection = factory.newConnection();
//4. 创建channel通道
Channel channel = connection.createChannel();
//5. 声明队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments)
1.queue:队列名称
2.durable:是否持久化
3.exclusive:是否独占,只能有一个消费者监听队列,当Connection关闭时,是否删除队列
4.autoDelete: 是否自动删除,当没有Consumer时,自动删除掉
5.arguments: 一些其它参数
*/
//如果没有队列,会自动创建,如果有,则不创建
channel.queueDeclare("queue1", true, false, false, null);
//6. 接收消息,并消费
/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
1.queue:队列名称
2.autoAck:是否自动确认,消费者收到消息之后,自动确认(MQ认为消费者一定收到,内部自动删除消息)/手动确认(消费者手动告诉MQ收到消息)
3.callback:回调对象(消费者收到消息后需要执行的逻辑)
*/
// DefaultConsumer是Consumer的接口实现
DefaultConsumer consumer = new DefaultConsumer(channel) {
/*
回调方法:当收到消息后,会自动执行该方法
1. consumerTag:标识
2. envelope:获取一些信息比如:交换机、路由key(队列名称)
3. properties:配置信息
4. body:数据
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息: " + new String(body));
}
};
channel.basicConsume("queue1", true, consumer);
//等待回调函数执行完毕之后(防止消息没有接收完毕就立即执行关闭),再关闭资源
TimeUnit.SECONDS.sleep(5);
//7.释放资源,消费者相当于是一个监听程序,不需要关闭资源
//注意:顺序不可改变(要么只关闭连接,通道也会关闭;但是如果先关闭连接,通道已经关闭了再调用通道就报错)
channel.close();
connection.close();
}
}

注意:为什么消费者也要声明队列?因为在实际中,生产者和消费者往往是两个不同的系统,双方什么时候运行都不一定一致。消费者如果不声明队列就直接订阅,可能生产者还未声明队列,不存在队列就会报错,因此消费者也需要声明队列。
如果生产者提前运行,并且明确消息队列是什么,消费者也可以不用声明队列。
下篇文章: