RabbitMQ学习笔记
RabbitMQ介绍
RabbitMQ是什么?
- 主要是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,它主要是使用 Erlang 语言进行编写的,并且是基于 AMQP 协议。
- 优点:与 Spring AMQP 完美结合,拥有丰富的 API。集群模式相当丰富,提供表达式配置,HA模式,镜像队列模型。
使用场景
- 在我们秒杀抢购商品的时候,系统会提醒我们稍等排队中,而不是像几年前一样页面卡死或报错给用户。
- 像这种排队结算就用到了消息队列机制,放入通道里面一个一个结算处理,而不是某个时间断突然涌入大批量的查询新增把数据库给搞宕机,所以RabbitMQ本质上起到的作用就是削峰填谷,为业务保驾护航。
AMQP
Advanced Message Queuing Protocol 高级消息协议。
AMQP协议模型
核心概念
- Server:服务端(broker)
- Virtual Host:虚拟主机。作用是做消息的逻辑隔离。
- ConnectionFactory:连接管理器。应用程序与 Rabbit 之间建立连接的管理器,程序代码中使用。
- Connection:连接。主要是应用于服务器之间的连接。
- Channel:信道。主要是进行消息的读写操作,每个客户端都可以建立多个Channel,每个 Channel 代表一个会话任务。
- Message:由 Properties 和 Body 两部分组成,前者主要是对消息的处理,后者是消息主体。
- Exchange:交换机。用于接收、分配消息。
- Queue:用于存储生产者的消息。
- Binding:绑定。主要是讲 Exchange 中的信息与 Message Queue 中的队列进行绑定。
- Routing Key:路由键,用于把生产者的数据分配到交换机上。
- BindingKey:绑定键,用于把交换机的消息绑定到队列上。
RabbitMQ工作原理
RabbitMQ架构图
RabbitMQ消息流转
- 连接(Connection):消费者或者生产者与消息中间件建立的tcp连接;
- 频道(Channel):也叫信道,tcp连接建立之后,必须先在连接上开频道,才能进行其他操作;
- 登录(Login):建立频道之后,要登录到特定的虚拟机,一组虚拟机持有一组交换机和队列,其他虚拟机用户无法访问当前用户对应的虚拟机中的交换机和队列;
- 交换机(Exchange):在 RabbitMQ 消息中间件启动时就会创建一个默认的交换机(当然也可以人为创建),与连接无关,负责整个消息中间件中消息的投递;交换机不会存储消息,如果没有任何队列与之绑定,那么交换机会丢弃收到的消息;
- 队列(Queue):用来存储交换机投递过来的消息,通过路由键与交换机绑定,进行消息的持久化存储;
队列由消费者或者生产者连上消息中间件后自行创建,人为指定队列名称,如果当前创建的队列 RabbitMQ 上已经存在,RabbitMQ 不会重复创建; - 路由键(RoutingKey):交换机和队列进行消息投递的识别码,人为指定。
RabbitMQ安装和使用
安装
- 官网地址:https://www.rabbitmq.com/
安装包安装
- 安装版本:rabbitmq-server-3.8.5
- 卸载:
/sbin/service rabbitmq-server stop
yum list | grep rabbitmq
yum -y remove rabbitmq-server.noarch
yum list | grep erlang
yum -y remove erlang-*
yum remove erlang.x86_64
rm -rf /usr/lib64/erlang
rm -rf /var/lib/rabbitmq
- yum安装最新版本的Erlang:
wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
sudo rpm -Uvh erlang-solutions-1.0-1.noarch.rpm --force --nodeps
sudo yum install erlang
- 手动安装指定版本的Erlang:下载地址:https://dl.bintray.com/rabbitmq-erlang/rpm/erlang/
- 下载后上传服务器,执行安装命令
rpm -ivh erlang-23.0.2-1.el8.x86_64.rpm
# 验证是否安装成功
erl -v
- 下载RabbitMQ安装包:
- 将安装包下载上传到服务器,执行安装命令
yum install rabbitmq-server-3.8.5-1.el7.noarch.rpm
快速安装脚本
- 使用 PackageCloud 安装:
- RabbitMQ 服务器安装脚本:
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash
- Erlang 安装脚本:
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
查看下载的仓库
ll /etc/yum.repos.d/
cat /etc/yum.repos.d/rabbitmq_erlang.repo
cat /etc/yum.repos.d/rabbitmq_rabbitmq-server.repo
执行安装
- 安装 erlang:
yum install erlang
- 安装 rabbitmq-server:
yum install rabbitmq-server
启动与访问
- 启动服务、停止服务和查看服务是否运行:
/sbin/service rabbitmq-server start & # & 表示后台启动
/sbin/service rabbitmq-server stop
ps -ef | grep rabbitmq
lsof -i:5672
- 开启后台管理插件:如果你想要进入后台管理页面,一定要记得先开启该插件
rabbitmq-plugins enable rabbitmq_management
- 修改配置文件:目的主要是配置管理后台的登录名和密码,修改为:{loopback_users, [guest]}
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.15/ebin/rabbit.app
- rabbitmq server 端口:5672
- rabbitmq登录控制台端口:15672
- 访问后台管理页面:http://192.168.254.106:15672,用户名和密码都是 guest
- 首页:http://192.168.254.106:15672/
使用
命令行基本操作
- 启动应用:rabbitmqctl start_app
- 停止应用:rabbitmqctl stop_app
- 查看状态:rabbitmqctl status
- 添加用户:rabbitmqctl add_user username password
- 用户列表:rabbitmqctl list_users
- 移除用户:rabbitmqctl delete_user username
- 设置用户权限:rabbitmqctl set_permissions -p vhostpath username
- 移除用户权限:rabbitmqctl clear_permissions -p vhostpath username
- 查看用户权限:rabbitmqctl list_user_permissions username
- 重置密码:rabbitmqctl change_password username newpassword
- 创建虚拟主机:rabbitmqctl add_vhost vhostpath
- 查看所有虚拟主机:rabbitmqctl list_vhosts
- 列出虚拟主机上所有权限:rabbitmqctl list_permissions -p vhostpath
- 删除虚拟主机:rabbitmqctl delete_vhost vhostpath
- 查看所有队列:rabbitmqctl list_queues
- 清除队列里的消息:rabbitmqctl -p vhostpath purge_queue blue
命令行进阶操作
- 移除所有数据:rabbitmqctl reset 【要在 rabbitmqctl stop_app 之后使用】
- 组成集群命令:rabbitmqctl join_cluster [–ram]
- 查看集群状态:rabbitmqctl cluster_status
- 修改集群节点的存储形式:rabbitmqctl change_cluster_node_type disc | ram
- 忘记(摘除)节点:rabbitmqctl forget_cluster_node [–offline]
- 修改节点名称:rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2…]
工作台操作
也就是在 rabbitmq 的后台管理页面对**连接(Connections)、信道(Channels)、交换机(Exchanges)、队列(Queues)和用户(Admin)**进行管理操作。
RabbitMQ原生Java API操作
思路
- 获取连接工厂 ConnectionFactory
- 通过连接工厂,获取一个 Connection 连接对象
- 声明一个 Exchange 交换机(新版本)
- 通过 Connection,获取信道 Channel,主要用于发送和接收消息
- 将消息存储到 Message Queue 队列中
- 两个角色:生产者 Producer 和 消费者 Consumer
代码实现
- 创建工程:新建一个 Spring Initializr 工程,命名为 01-rabbitmq-helloworld。
- 添加依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
- 基础信息:
public interface BaseInfo {
String RABBITMQ_HOST_IP = "192.168.254.106";
int RABBITMQ_HOST_PORT = 5672;
String RABBITMQ_VIRTUAL_HOST = "/";
String RABBITMQ_FIRST_EXCHANGE = "first-exchange";
String RABBITMQ_FIRST_ROUTING_KEY = "first-msg";
String RABBITMQ_FIRST_QUEUE_NAME = "first-queue";
String RABBITMQ_SECOND_EXCHANGE = "second-exchange";
String RABBITMQ_SECOND_ROUTING_KEY = "second-msg";
}
实现一
- 新建 Producer 生产者类:
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.concurrent.TimeUnit;
import static com.yw.rabbitmq.common.BaseInfo.*;
public class FirstProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
// 参数一:指定交换机名称
// 参数二:BuiltinExchangeType.DIRECT : 交换机类型是直连
// 参数三:指明是否消息是否要持久化到队列上
channel.exchangeDeclare(RABBITMQ_FIRST_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 通过 Channel 发送消息
String hello = "Hello World";
for (int i = 0; i < 100; i++) {
String msg = hello + i;
channel.basicPublish(RABBITMQ_FIRST_EXCHANGE, RABBITMQ_FIRST_ROUTING_KEY, null, msg.getBytes());
System.out.println(msg);
TimeUnit.MILLISECONDS.sleep(100);
}
// 6. 关闭资源
channel.close();
conn.close();
}
}
- 新建 Consumer 消费者类:
import com.rabbitmq.client.*;
import static com.yw.rabbitmq.common.BaseInfo.*;
import static java.nio.charset.StandardCharsets.UTF_8;
public class FirstConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_FIRST_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 声明一个队列
// 参数一:队列名
// 参数二:指明是否消息要持久化到队列上,关机后消息也不会丢
// 参数三:是否独占,类似加了一把锁,只有一个 Channel 能监听,保证了消费顺序
// 参数四:是否自动删除,如果队列跟交换机没有绑定关系,是否自动删除
// 参数五:扩展参数
channel.queueDeclare(RABBITMQ_FIRST_QUEUE_NAME, true, false, false, null);
// 6. 队列绑定
channel.queueBind(RABBITMQ_FIRST_QUEUE_NAME, RABBITMQ_FIRST_EXCHANGE, RABBITMQ_FIRST_ROUTING_KEY);
// 7. 异步获取投递消息
// 就相当于根据 路由key 获取信道中的数据
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), UTF_8);
System.out.println(" [x] Received '" + message + "'");
try {
System.out.println(message);
} finally {
System.out.println(" [x] Done!");
}
};
boolean autoAck = true;
channel.basicConsume(RABBITMQ_FIRST_QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {
});
// // 8. 关闭资源
// channel.close();
// conn.close();
}
}
实现二
- 新建 Producer 生产者类:
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.concurrent.TimeUnit;
import static com.yw.rabbitmq.common.BaseInfo.*;
public class SecondProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_SECOND_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 通过 Channel 发送消息
String hello = "==>> Hello World";
for (int i = 0; i < 100; i++) {
String msg = hello + i;
channel.basicPublish(RABBITMQ_SECOND_EXCHANGE, RABBITMQ_SECOND_ROUTING_KEY, null, msg.getBytes());
System.out.println(msg);
TimeUnit.MILLISECONDS.sleep(100);
}
// 6. 关闭资源
channel.close();
conn.close();
}
}
- 新建 Consumer 消费者类:
import com.rabbitmq.client.*;
import java.io.IOException;
import static com.yw.rabbitmq.common.BaseInfo.*;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SecondConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_SECOND_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 声明一个队列
String queueName = channel.queueDeclare().getQueue();
// 6. 队列绑定
channel.queueBind(queueName, RABBITMQ_SECOND_EXCHANGE, RABBITMQ_SECOND_ROUTING_KEY);
// 7. 消费消息
while (true) {
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
System.out.println("消费的路由键:" + routingKey + " 消费的内容类型:" + contentType);
long deliveryTag = envelope.getDeliveryTag();
// 确认消息
channel.basicAck(deliveryTag, false);
System.out.println("消费的消息体:" + new String(body, UTF_8));
}
});
}
}
}
RabbitMQ核心概念
Exchange交换机
- 作用:接收消息,并根据路由键转发消息所绑定的队列
图示
- 蓝色框:生产消息,经过交换机,到达队列
- 绿色框:消费者,从队列中获取消息进行消费
- 红色框:RabbitMQ Server
- 黄色框:交换机绑定队列
交换机属性
Type属性
- direct:直连
- topic:主题
- fanout:广播
- headers:头消息匹配(不常用)
常见属性
- Durability:是否持久化【true、false】
- Auto delete:当 Exchange 上所有队列都删除后,它也将自动被删除。拓展:在队列上,找不到关联的交换机,队列也要被清除。
- Internal:当前 Exchange 是否只在 RabbitMQ 内部使用,一般保持默认值 false【较少使用】。除非熟悉 Erlang 语言,可自定义扩展插件。
- Arguments:扩展参数,用于扩展 AMQP 协议定制使用。
Direct Exchange直连交换机
- 作用:发送到 Direct Exchange 的消息,都会被转发到 RouteKey 中指定的 Queue 中。就是一对一的作用。
- 注意:Direct 模式可以使用 RabbitMQ 自带的 Exchange:default Exchange,所以不需要将 Exchange 进行任何绑定(binding)操作,消息传递时,RouteKey 必须完全匹配才会被队列接收,否则该消息会被抛弃。
- 图示:
案例
- 生产者Producer:
public class DirectProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 通过 Channel 发送消息
String msg = "hello, direct-exchange";
channel.basicPublish(RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY, null, msg.getBytes());
// // 6. 关闭资源
// channel.close();
// conn.close();
}
}
- 消费者Consumer:
public class DirectConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明交换机
channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 声明&绑定队列
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY);
// 6. 消费消息
while (true) {
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取 routingKey & contentType
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
// 获取传送标签
long deliveryTag = envelope.getDeliveryTag();
// 确认消息
channel.basicAck(deliveryTag, false);
System.out.println("消费的 Body:");
String bodyMsg = new String(body, UTF_8);
System.out.println(bodyMsg);
}
});
}
}
}
- BaseInfo.java:
public interface BaseInfo {
String RABBITMQ_HOST_IP = "192.168.254.106";
int RABBITMQ_HOST_PORT = 5672;
String RABBITMQ_VIRTUAL_HOST = "/";
String RABBITMQ_DIRECT_EXCHANGE = "direct-exchange";
String RABBITMQ_DIRECT_ROUTING_KEY = "direct-msg";
}
Topic Exchange主题交换机
- 作用:发送到 Topic Exchange(主题交换机) 上的消息,会被指定给主题相关的 Queue(队列)上。主要是将 RouteKey 和设置的 Topic 进行模糊匹配。
- 注意:可以使用通配符进行模糊匹配。符号 # 匹配一个或多个词,如:hello.# → hello.girl.cuihua。符号 * 匹配一个词,如:hello.* → hello.cuihua
- 图示:
案例
- 生产者Producer:
public class TopicProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC, true);
// 5. 发布消息
String msg = "hello, topic-exchange";
channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY1, null, msg.getBytes());
channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY2, null, msg.getBytes());
channel.basicPublish(RABBITMQ_TOPIC_EXCHANGE, RABBITMQ_TOPIC_ROUTING_KEY3, null, msg.getBytes());
}
}
- 消费者Consumer:
public class TopicConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明交换机
channel.exchangeDeclare(RABBITMQ_TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC, true);
// 5. 声明&绑定队列
// String routingKey = "topic-msg.*";
String routingKey = "topic-msg.#";
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, RABBITMQ_TOPIC_EXCHANGE, routingKey);
// 6. 消费消息
while (true) {
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取 routingKey & contentType
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
// 获取传送标签
long deliveryTag = envelope.getDeliveryTag();
// 确认消息
channel.basicAck(deliveryTag, false);
System.out.println("消费的 Body:");
String bodyMsg = new String(body, UTF_8);
System.out.println(bodyMsg);
}
});
}
}
}
- 修改BaseInfo.java:
Fanout Exchange广播交换机
- 作用:直接广播,不走路由键,直接将队列绑定到交换机上。发送到交换机的消息,全都会被转发到与该交换机绑定的队列上。转发消息是最快的。
- 图示:
案例
- 生产者Producer:
public class FanoutProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT, true);
// 5. 发布消息
for (int i = 0; i < 10; i++) {
String msg = "hello, fanout-exchange" + i;
// 不设置路由键,或者随便设置
String routingKey = "";
channel.basicPublish(RABBITMQ_FANOUT_EXCHANGE, routingKey, null, msg.getBytes());
}
}
}
- 消费者Consumer:
public class FanoutConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明交换机
channel.exchangeDeclare(RABBITMQ_FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT, true);
// 5. 声明&绑定队列
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, RABBITMQ_FANOUT_EXCHANGE, RABBITMQ_FANOUT_ROUTING_KEY);
// 6. 消费消息
while (true) {
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取 routingKey & contentType
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
// 获取传送标签
long deliveryTag = envelope.getDeliveryTag();
// 确认消息
channel.basicAck(deliveryTag, false);
System.out.println("消费的 Body:");
String bodyMsg = new String(body, UTF_8);
System.out.println(bodyMsg);
}
});
}
}
}
- 修改BaseInfo.java:
Binding绑定
- Exchange 和 Exchange、Queue 之间的连接关系
- Binding 中可以包含 RoutingKey 或者参数
Queue消息队列
- 消息队列:存储消息数据
- Durability 是否持久化:Durable 是、Transient 否
- Auto delete:如果选 yes,代表当最后一个监听被移除之后,该 Queue 会自动被删除
Message消息
- 服务器和应用程序之间,进行传送的数据
- 就是一段数据,由 Properties 和 Payload(Body)组成
常用属性
- delivery mode 消息送达模式:持久化、非内存级别的非持久化
- headers(自定义属性)
其它属性
- content_type:消息内容的类型
- content_encoding:消息内容的编码格式
- priority:消息的优先级
- correlation_id:关联id
- reply_to:用于指定回复的队列的名称
- expiration:消息的失效时间
- message_id:消息id
- timestamp:消息的时间戳
- type:类型
- user_id:用户id
- app_id:应用程序id
- cluster_id:集群id
案例
- 生产者Producer:
public class MessageProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明交换机
channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 添加headers信息
HashMap<String, Object> headers = new HashMap<>();
headers.put("msg-01", "hello");
headers.put("msg-02", "world");
// 6. 添加额外的属性信息
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化消息
.contentEncoding(String.valueOf(UTF_8))
.expiration("10000")
.headers(headers)
.build();
// 5. 通过 Channel 发送消息
String hello = "Hello, message";
for (int i = 0; i < 100; i++) {
String msg = hello + i;
channel.basicPublish(RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY, props, msg.getBytes());
System.out.println(msg);
TimeUnit.MILLISECONDS.sleep(100);
}
}
}
- 消费者Consumer:
public class MessageConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个 ConnectionFactory 工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(RABBITMQ_HOST_IP);
factory.setPort(RABBITMQ_HOST_PORT);
factory.setVirtualHost(RABBITMQ_VIRTUAL_HOST);
// 2. 创建一个 Connection 连接
Connection conn = factory.newConnection();
// 3. 获取一个 Channel 信道
Channel channel = conn.createChannel();
// 4. 声明一个交换机
channel.exchangeDeclare(RABBITMQ_DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 5. 声明&绑定队列
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, RABBITMQ_DIRECT_EXCHANGE, RABBITMQ_DIRECT_ROUTING_KEY);
// 6. 消费消息
while (true) {
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取 routingKey & contentType
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
Map<String, Object> headers = properties.getHeaders();
System.out.println("消费的 Routing Key:" + routingKey + " \n消费的 Content Type:" + contentType);
System.out.println("消费的 headers:" + JSON.toJSONString(headers));
// 获取传送标签
long deliveryTag = envelope.getDeliveryTag();
// 确认消息
channel.basicAck(deliveryTag, false);
System.out.println("消费的 Body:");
String bodyMsg = new String(body, UTF_8);
System.out.println(bodyMsg);
}
});
}
}
}
Virtual Host虚拟机
- 虚拟地址,用于进行逻辑隔离,最上层的消息路由
- 一个 Virtual Host 里面可以有若干个 Exchange 和 Queue
- 同一个 Virtual Host 里面不能有相同名称的 Exchange 或 Queue