提示:RabbitMQ五种工作模式及应用(官方是7种)
前言
一、RabbitMQ安装(Docker)
-
下载镜像:
docker pull rabbitmq
-
运行容器:
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:latest
账号admin,密码123456 用于登录MQ后台
-
打开访问界面
进入容器: docker exec -it a64bbac6d26f bash 执行: rabbitmq-plugins enable rabbitmq_management 输出: Enabling plugins on node rabbit@a64bbac6d26f: rabbitmq_management The following plugins have been configured: rabbitmq_management rabbitmq_management_agent rabbitmq_prometheus rabbitmq_web_dispatch Applying plugin configuration to rabbit@a64bbac6d26f... The following plugins have been enabled: rabbitmq_management started 1 plugins
-
即可在浏览器中通过 http://127.0.0.1:15672/ 访问,可通过上面设置的账号密码进行登录。
二、MQ中相关概念
KEY | VALUE |
---|---|
Broker | 接收和分发消息的应用 |
Virtual host | 出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等 |
Connection | publisher/consumer 和 broker 之间的 TCP 连接 |
Channel | 如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销 |
Exchange | message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (定向,把消息交给符合指定routing key 的队列), topic (通配符,把消息交给符合routing pattern(路由模式) 的队列) and fanout (广播,将消息交给所有绑定到交换机的队列) |
Queue | 消息最终被送到这里等待 consumer 取走 |
Binding | exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据 |
每个Virtual Hosts相当于一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。 相当于mysql的db。Virtual Name一般以/开头
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
三、工作模式
模式 | 特点 | 场景 | 图解 |
---|---|---|---|
简单 (Simple) | 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机) | 单个生产者和单个消费者(日志记录,订单处理) | ![]() |
工作(Work Queue) | 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机) | 需要多个消费者共同处理任务(异步处理,短信通知) | ![]() |
发布/订阅(Publish/Subscribe) | 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列 | 需要消息被多个消费者同时接收(实时通知,广播) | ![]() |
路由(Routing) | 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routingkey,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列 | 需要根据特定条件匹配路由消息(消息分类处理) | ![]() |
通配符(Topics) | 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列 | 适用于需要根据模式匹配分发消息的场景 | ![]() |
四、Java操作MQ
依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.21.0</version>
</dependency>
工具类:
package com.rabbitmq.utils;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUntils {
public static Connection getConnection() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("admin");
factory.setPassword("123456");
//创建连接
Connection connection = factory.newConnection();
return connection;
}
}
- 简单模式
package com.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.utils.ConnectionUntils;
/**
* 简单模式
* 生产者
*/
public class Producer {
static final String QUEUE_NAME = "simple_name";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUntils.getConnection();
Channel channel = connection.createChannel();
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 发送消息
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
String message = "Hello World!7";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//释放资源
channel.close();
connection.close();
}
}
package com.rabbitmq.simple;
import com.rabbitmq.client.*;
import com.rabbitmq.utils.ConnectionUntils;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws Exception {
Connection conection = ConnectionUntils.getConnection();
//通道
Channel channel = conection.createChannel();
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//接受消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接受消息 处理消息的回调函数
* @param consumerTag 消费者标签 channel.basicConsume() 指定使用什么标签的消费者消费消息
* @param envelope 消息包的信息:消息id routingkey exchange
* @param properties 属性信息
* @param body 消息
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由:" + envelope.getRoutingKey());
System.out.println("交换机:" + envelope.getExchange());
System.out.println("消息ID:" + envelope.getDeliveryTag());
System.out.println("消息" + new String(body, "UTF-8"));
super.handleDelivery(consumerTag, envelope, properties, body);
}
};
/**
* 1.队列名称
* 2.是否确认,true 消息消费后,mq自动回复消息已消费 mq收到回复后删除消息
* 3.消费者对象,消息消费的回调函数
*/
channel.basicConsume(Producer.QUEUE_NAME, true, consumer);
}
}
- 工作模式:在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
package com.rabbitmq.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.utils.ConnectionUntils;
/**
* 工作模式
* 生产者
*/
public class Producer {
static final String QUEUE_NAME = "work_name";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUntils.getConnection();
Channel channel = connection.