RabbitMq
一、docker安装rabbitmq
# 拉取镜像
docker pull rabbitmq
# 启动rabbitmq
docker run -d --name rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq
# 进入容器
docker exec -it rabbitmq /bin/bash
# 安装插件
rabbitmq-plugins enable rabbitmq_management
# 退出
ctrl + p + q
# 访问 用户名和密码默认都是guest
http://****:15672/
二、消息队列
场景一 : 简单队列
- P : 生产者
- **C **: 消费者
- 中间红色 : 队列,队列只受主机内存和磁盘的限制,本质是一个大型消息缓冲区
# pom
<!--amqp协议-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.5.3</version>
</dependency>
生产者
Send.java
/**
* Created with IntelliJ IDEA.
*
* @author: L-JiaHui
* @date: 2021/07/20/14:41
* @description: * 简单的队列----producing---生产者
* 1.RabbitMQ的核心就是基于通道(Channel)进行开展的
* 2.通道的概念非常常见,例如:NIO的三大核心其中有一项就是通道
*/
@Slf4j
public class Send {
/**
* 队列名称
*/
private static final String QUEUE_NAME = "554_queue";
public static void main(String[] args) {
try {
//获取RabbitMQ连接
Connection connection = ConnectionUtil.getConnection();
//获取管道
Channel channel = connection.createChannel();
/*
* @param_one : 队列名称
* 其余参数后续场景会依次开启
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/*
* channel.basicPublish() --- 进行发送消息
* @param_two:队列名称
* @param_four: 存储的数据(字节形式)
* 可通过循环发送信息到队列中
*/
for (int i = 0; i < 1; i++) {
String msg = "Hell Word,this is queue" + (i + 1);
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
}
//消息发送成功
log.info("message:{}", "send msg success");
} catch (Exception e) {
e.printStackTrace();
}
}
}
ConnectionUtil.java
/**
* Rabbitmq的工具类
* method_one:连接
* method_two关闭链接
*
* @author l-jiahui
*/
public class ConnectionUtil {
/**
* amqp访问地址
*/
private static final String HOST = "*****";
/**
* 用户名
*/
private static final String USER_NAME = "guest";
/**
* 密码
*/
private static final String PWD = "guest";
/**
* 虚拟主机
*/
private static final String VIRTUAL_HOST = "/";
/**
* amqp协议端口
*/
private static final Integer PORT = 5672;
/**
* 获取连接对象
*
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
//Rabbitmq连接工厂
ConnectionFactory factory = new ConnectionFactory();
//主机
factory.setHost(HOST);
//用户名
factory.setUsername(USER_NAME);
//pwd
factory.setPassword(PWD);
factory.setVirtualHost(VIRTUAL_HOST);
factory.setPort(PORT);
return factory.newConnection();
}
/**
* 关闭连接
*
* @param con
* @param channel
*/
public static void closeConnection(Connection con, Channel channel) {
try {
if (channel != null) {
channel.close();
}
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费者
/**
* 1. 简单的队列---Recv----消费者
* 2. 1.消费者无非就是获取数据/使用数据
*
* @author l-jiahui
*/
@Slf4j
public class Recv {
/**
* 队列名称---在某个队列中读取数据
*/
private static final String QUEUE_NAME = "554_queue";
public static void main(String[] args) {
try {
//连接RabbitMQ
Connection connection = ConnectionUtil.getConnection();
//创建队列
Channel channel = connection.createChannel();
/*
* 创建队列---和生产者代码一致
* 注意:生产者或消费者程序不存在优先级关系,所以在此声明队列的原因,是保证队列存在
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/*
* 内部静态类 --实现方法①
* Lambda表达式--实现方法②
* lambda实现方式前提:必须是函数式接口(一个接口中只有一个方法)
*/
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
//delivery.getBody()返回的是二进制数组-字节数组
String returnMsg = new String(delivery.getBody(), StandardCharsets.UTF_8);
log.info("returnMessage:{}", returnMsg);
}
};
/*
* 消费者
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumer -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
场景二: 工作队列/任务队列
Send.java
/**
* 工作队列(任务队列)---生产者
* ①工作任务:背后思想避免立即执行密集资源型任务,并不得不等待它完成
* ②工作任务:用于在多个工作者/消费者之间分配耗时任务
*/
public class Send {
private static final String QUEUE_NAME = "554_queue"; //队列名称
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//获取核心管道/通道
Channel channel = connection.createChannel();
//创建队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//正常发送信息
String mgs = "vritual msg";
for (int i = 0; i < 30; i++) {
//发布
channel.basicPublish("", QUEUE_NAME, null, (mgs+i).getBytes());
//模拟资源紧张
Thread.sleep(500);
}
System.out.println("send msg success");
//关闭连接
ConnectionUtil.closeConnection(connection,channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Recv.java
/**
* 工作队列(任务队列)---消费者
* 该Recv类进行通用,启动两个,消费者一sleep时间间隔为1秒,而消费者二的sleep时间间隔为2秒。
*/
public class Recv {
private static final String QUEUE_NAME = "554_queue"; //队列名称
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//获取管道
Channel channel = connection.createChannel();
//创建队列--生产者或消费者不存在优先级顺序,生产者发送消息,消费者监听信息并获取信息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
String msg = new String(delivery.getBody());
System.out.println("接收的消息========" + msg);
try {
//模仿处理任务所耗时长
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//消费
channel.basicConsume(QUEUE_NAME, callback, consuer -> {
});
System.out.println("消费者1启动成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试总结
消费者1处理任务快,但执行的任务数量却和消费者2一致,这就是RabbitMQ默认情况下的轮询分发,如果队列中的数据总数是奇数的话,那么最后一条数据会发给最先获取数据的消费者
消息确认
正常情况下,RabbitMQ将数据传递给消费者,同时也会将这条数据标记为删除,然后消费者拿到数据进行处理,但如果是非正常情况,例如:RabbitMQ将信息传递给消费者,但传递过程中,消费者挂掉或者连接丢失了,这就会导致这条数据丢失,并且无法还原,针对这一点我们可以将autoAck=true 的标识关闭,将它改为false,这样RabbitMQ在传递消息给消费者时不会将该消息标记为删除,而是等待着消费者接收到消息之后的ack响应(消息确认),RabbitMQ成功接收到消费者的ack响应之后才会将该数据标记为删除,而不是消息一传递出去就标记删除
Recv.java
/**
* 工作队列(任务队列)---消费者
* 该Recv类进行通用,启动两次,消费者一sleep时间间隔为1秒,而消费者二的sleep时间间隔为2秒。
*/
public class Recv {
private static final String QUEUE_NAME = "554_queue"; //队列名称
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//获取管道
final Channel channel = connection.createChannel();
//创建队列--生产者或消费者不存在优先级顺序,生产者发送消息,消费者监听信息并获取信息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
String msg = new String(delivery.getBody());
System.out.println("接收的消息========" + msg);
try {
//模仿处理任务所耗时长
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//新增finally块
finally {
//消息获取成功后ack响应
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
};
//消费
boolean autoAck = false; //关闭
channel.basicConsume(QUEUE_NAME, autoAck, callback, consuer -> {
});
System.out.println("消费者1启动成功");
} catch (
Exception e) {
e.printStackTrace();
}
}
}
消息持久性
消息确认解决了消费者即使没有拿到数据,也可以保证数据不丢失的隐患,但如果RabbitMQ在使用的中途中挂掉了、服务器崩溃了、网络异常或RabbitMQ重启了等这些情况,还是会使数据丢失,所以我们在通过生产者发送信息的给RabbitMQ的时候就要告知RabbitMQ该系列数据是需要标记持久状态的
# 注意一:不能在原有的队列中进行消息持久性设置,否则会报错,解决方法:①删除原有的队列。②重新创建一个不存在的队列。
# 注意二:此场景下生产者创建队列,消费者就无需创建队列了。
Send.java
/**
* 工作队列(任务队列)---生产者
* ①工作任务:背后思想避免立即执行密集资源型任务,并不得不等待它完成
* ②工作任务:用于在多个工作者/消费者之间分配耗时任务
*/
public class Send {
private static final String QUEUE_NAME = "554_queue"; //队列名称
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//获取核心管道/通道
Channel channel = connection.createChannel();
//创建队列
boolean durable = true;//持久性开启--解决RabbitMQ重启不会丢失数据
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
//正常发送信息
String mgs = "vritual msg";
for (int i = 0; i < 3; i++) {
//MessageProperties.PERSISTENT_TEXT_PLAIN---解决了数据持久化,类似zk存储节点的持久化设置
//发布
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, (mgs + i).getBytes());
}
System.out.println("send msg success");
//关闭连接
ConnectionUtil.closeConnection(connection, channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
官方的原话:消息标记为持久性并不能一定保证数据的持久性,虽然它告诉RabbitMQ将消息存储到磁盘上,但当RabbitMQ已经接收到一条消息却还没有保存的时候,仍然有丢失的可能性,RabbitMQ不会对每条消息都执行fsync(2)—它可能只是保存到缓存中,而不是真正的写到磁盘中。
fsync(2):把内存中的数据存储到磁盘中。
公平调度
轮询分发的弊端:处理消息快的工作者会很闲,而处理消息慢的工作者会很忙,这种情况RabbitMQ自身是不知道的,它只知道要将信息均匀的分摊,但是并不是所有场景都适合轮询分发,为了解决此问题,我们可以使用prefetchCount = 1设置basicQos()方法,告诉RabbitMQ一次不要给一个消费者发多个消息,而是当消费者处理完上一条消息之后,在传递下一条消息,这种模式的话完美的解决了轮询分发的弊端,并且会出现多个消费者之间抢占资源情况,哪个消费者处理数据快,哪个消费者就会接收到的消息多。
生产者先启动,随后在启动消费者,因为设置成了消息持久化,只能一个程序创建队列,多个程序创建同一个队列的话会报错。
Recv.java
**
* 工作队列(任务队列)---消费者
* 该Recv类进行通用,启动两次,消费者一sleep时间间隔为1秒,而消费者二的sleep时间间隔为2秒。
*/
public class Recv {
private static final String QUEUE_NAME = "yunkai_queue"; //队列名称
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//获取管道
final Channel channel = connection.createChannel();
//模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
int prefetchCount = 1;
channel.basicQos(prefetchCount); //公平调度
DeliverCallback callback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
String msg = new String(delivery.getBody());
System.out.println("接收的消息========" + msg);
try {
//模仿处理任务所耗时长
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//新增finally块
finally {
//消息获取成功后ack响应
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
};
//消费
boolean autoAck = false; //关闭
channel.basicConsume(QUEUE_NAME, autoAck, callback, consuer -> {
});
System.out.println("消费者1启动成功");
} catch (
Exception e) {
e.printStackTrace();
}
}
}
场景三:订阅者(publish)/发布者(Subscribe)
生产者将消息发送给交易所,交易所将接收到的消息在发送给已绑定的队列上,最终消费者会接收队列传递的消息
临时队列/绑定
# 什么是临时队列?用完即丢,拿来即用,一旦与消费者断开连接的话,队列就会自动删除。
//自动生成成队列名称,非持久、独占、自动删除队列。
String queueName = channel.queueDeclare().getQueue();
持久队列
# (即使将队列重启后,队列也依旧存在)
独占:(仅由一个连接使用,该链接关闭时候队列将被删除)
自动删除:(当最后一个消费者取消订阅时,删除至少有一个消费者的队列)
ExSend.java
/**
* exchange(交换)
* <p>
* RabbitMQ的核心模型概念:生产者永远不会直接向队列发送任何消息。实际上,
* 很多时候,生产者甚至根本不知道消息是否会被传递到任何队列
*
* @author l-jiahui
*/
@Slf4j
public class ExSend {
/**
* 交换机名称
*/
private static String EXCHANGE_NAME = "exchange_name1";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//管道/通道
Channel channel = connection.createChannel();
/*
* 交换机
* @param_one:交换机名称
* @param_two:交换类型(扇出)
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//消息
String msg = "发布者/订阅者以及它的使用方式";
/*
* 发布
* @param_one:交换机名称 @
* param_two:原队列名称,现已不需要填写
* @param_three:配置
* @param_four:消息
*/
channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
log.info("provider===消息发送成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv1.java
/**
* 消费者1
*
* @author l-jiahui
*/
@Slf4j
public class ExRecv1 {
/**
* 交换机名称
*/
private static String EXCHANGE_NAME = "exchange_name1";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//通道
Channel channel = connection.createChannel();
/*
* 创建交换机--如果存在则免创建
* @param_one:交换机名称
* @param_two:交换类型(扇出)
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//非持久、独占式、自动删除的队列
String queue = channel.queueDeclare().getQueue();
/*
* 绑定队列
* @param_one:队列名称
* @param_two:交换机名称
*/
channel.queueBind(queue, EXCHANGE_NAME, "");
//接收信息
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info("consumer1=====" + new String(delivery.getBody()));
}
};
/*
* 消费信息-到指定的队列中获取信息
* @param_one:到指定队列中获取信息
* @param_three:回调
*/
channel.basicConsume(queue, true, deliverCallback, isempty -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv2.java
/**
* 消费者2
* @author l-jiahui
*/
@Slf4j
public class ExRecv2 {
/**
* 交换机名称
*/
private static String EXCHANGE_NAME = "exchange_name1";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//通道
Channel channel = connection.createChannel();
/*
* 创建交换机--如果存在则免创建
* param_one:交换机名称
* @param_two:交换类型(扇出)
* */
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//非持久、独占式、自动删除的队列
String queue = channel.queueDeclare().getQueue();
/*
* 绑定队列
* param_one:队列名称
* @param_two:交换机名称
* */
channel.queueBind(queue, EXCHANGE_NAME, "");
//接收信息
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info("consumer2=====" + new String(delivery.getBody()));
}
};
/*
* 消费信息-到指定的队列中获取信息
* param_one:到指定队列中获取信息
* @param_three:回调
* */
channel.basicConsume(queue, true, deliverCallback, isempty -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
场景四:路由(Routing)
路由
什么是路由?个人理解:路由很抽象,在每个场景都有不同的介绍,例如:在消息队列中,路由负责过滤消息,如果你发送的消息符合条件则放行,如果不符合则拒之门外,而在Http请求中,路由负责将你提前配置好的虚拟请求转发到实际请求上,每个场景都有不同的用法,可能实现方式上有一定的差别,但在概念上却都是一致的。
为什么需要路由?如果细看的话,会发现路由的模型图和发布/订阅场景的模型图类似,不同点在于交换机的类型变了,以及routingkey的参数也进行了设置,其实原因很简单,RabbitMQ对消息的存储方式为二种:磁盘上、内存上,而如果我们不想将所有的消息进行存储,只想将某些特定的消息或重要的消息进行存储,那怎么办呢?这种情况,通过路由则可以完美解决。
路由模型解析
直接交换: 在发布/订阅场景下的时候,交换机设置的类型是扇出(fanout),但并没有一点灵活性,只是一味的无脑传递消息,所以在路由场景下,交换机的类型会设置为直接(direct),这样的话生产者在发送消息时可指定该消息的类型,而消费者在接收消息时,如果不满足生产者指定的类型的话,则无法获取消息。
多重绑定: 概念如上,但可以实现多绑定,接收多种类型消息。
直接交换与多重绑定
ExSend.java
/**
* routing:
* 筛选,接收指定的格式信息
* routing:只接收符合条件的消息,不会接收多余的消息,会将不符合条件的消息拒之门外
*
* @author l-jiahui
*/
@Slf4j
public class ExSendTwo {
/**
* 交换机名称
*/
private static String EXCHANGE_NAME = "ex_queuechange";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//管道
Channel channel = connection.createChannel();
//交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//路由键数组
String[] routingArray = new String[]{"lion", "Elephant", "frog"};
//重复发送
for (String s : routingArray) {
/*
* 发布
* @param_one:交换机
* @param_two:路由键
* @param_four:发送的消息
*/
channel.basicPublish(EXCHANGE_NAME, s, null, (s + ":可以收到我的消息吗").getBytes());
log.info("消息发送成功");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv3.java
/**
* 消费者 --大象 青蛙 狮子
*
* @author l-jiahui
*/
@Slf4j
public class ExRecv3 {
/**
* 交换机名称
*/
private static final String EXCHANGE_NAME = "ex_queuechange";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//管道
Channel channel = connection.createChannel();
//交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//随机队列接收
String queue = channel.queueDeclare().getQueue();
/*
* 绑定队列--多重绑定
* @param_one:绑定的队列名称
* @param_two:交换机中获取数据
* @param_three:路由键
*/
channel.queueBind(queue, EXCHANGE_NAME, "frog");
channel.queueBind(queue, EXCHANGE_NAME, "Elephant");
channel.queueBind(queue, EXCHANGE_NAME, "lion");
//获取数据
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info("接收的信息为:{}" + new String(delivery.getBody()));
}
};
//消费
channel.basicConsume(queue, true, deliverCallback, comeback -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv4.java
/**
* 消费者2 --大象
* @author l-jiahui
*/
@Slf4j
public class ExRecv4 {
/**
* 交换机名称
*/
private static final String EXCHANGE_NAME = "ex_queuechange";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//管道
Channel channel = connection.createChannel();
//交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//随机队列接收
String queue = channel.queueDeclare().getQueue();
/*
* 绑定队列--多重绑定
* @param_one:绑定的队列名称
* @param_two:交换机中获取数据
* @param_three:路由键
*/
channel.queueBind(queue, EXCHANGE_NAME, "Elephant");
//获取数据
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info("接收的信息为======" + new String(delivery.getBody()));
}
};
//消费
channel.basicConsume(queue, true, deliverCallback, comeback -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv5.java
/**
* 消费者2 --大象
* @author l-jiahui
*/
@Slf4j
public class ExRecv5 {
/**
* 交换机名称
*/
private static final String EXCHANGE_NAME = "ex_queuechange";
public static void main(String[] args) {
try {
//连接
Connection connection = ConnectionUtil.getConnection();
//管道
Channel channel = connection.createChannel();
//交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//随机队列接收
String queue = channel.queueDeclare().getQueue();
/*
* 绑定队列--多重绑定
* @param_one:绑定的队列名称
* @param_two:交换机中获取数据
* @param_three:路由键
*/
channel.queueBind(queue, EXCHANGE_NAME, "lion");
//获取数据
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info("接收的信息为======" + new String(delivery.getBody()));
}
};
//消费
channel.basicConsume(queue, true, deliverCallback, comeback -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
多重绑定
直接绑定
直接绑定
场景五:主题(topic)
Exchange类型描述
扇出(fanout):发布/订阅场景中使用,将消息无条件推送给已绑定的队列。
直接(direct):路由场景中使用,将消息推送给满足条件已绑定的队列,用于精准匹配。
主题(topic):路由场景中使用,将消息推送给满足条件已绑定的队列,用于模糊匹配。
注意:交换机类型在某个场景下都可以任意使用,不存在固定场景固定类型。
主题场景模型解析
在发布/订阅场景下的时候,交换机设置的类型是扇出(fanout),但并没有一点灵活性,只是一味的无脑传递消息,在路由场景下的时候,交换机设置的类型是直接(direct),虽然做到了对消息的限制,但是还是存在局限性-----不能基于多个标准进行路由,所以在路由的基础之上,我们再次改变交换机的类型,实现主题场景
主题场景
*:星号代表一个词或一个匹配项。
#:井号代表n个匹配项。
①当为“#”符号的时候,就类似于交换机的扇出(fanout)类型。
②当为“*”符号的时候,就类似于交换机的直接(direct)类型。
③当一个路由键中满足多个类型的时候,是无法与多个类型匹配的,只会匹配第一个类型。
④路由键可以为任意字,最大长度为255个字节,当路由键不在跟符号“*”或“#”符号的话,那么它的匹配方式就会和路由完全一致。
ExSendThree.java
/**
* //第五个场景:主题
* 生产者
*
* @author l-jiahui
*/
@Slf4j
public class ExSendThree {
/**
* exchangeName
*/
private static final String EX_CHANGE_NAME = "ex_name1";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
/* 3.exchange
* @param_one:exchangeName
* @param_two:exchangeType ---->topic主题
*/
channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
//sendType
String[] sendType = new String[]{"frog.consumer1", "Elephant.consumer1", "frog", "lion", "lion.consumer1", "lion.frog.Elephant.frog"};
for (String s : sendType) {
channel.basicPublish(EX_CHANGE_NAME, s, null, (s + "=====msg send suc").getBytes());
}
log.info("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv6.java
/**
* topic
* 青蛙系列消费者
* @author l-jiahui
*/
@Slf4j
public class ExRecv6 {
/**
* exchangeName
*/
private static final String EX_CHANGE_NAME = "ex_name1";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
/* 3.exchange
* @param_one:exchangeName
* @param_two:exchangeType ---->topic主题
*/
channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
//4.queue
String queue = channel.queueDeclare().getQueue();
//5.binding
channel.queueBind(queue, EX_CHANGE_NAME, "frog.#");
//6.get msg
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info(new String(delivery.getBody()));
}
};
//7.consumer
channel.basicConsume(queue, true, deliverCallback, cancel -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv7.java
/**
* topic
* 青蛙系列消费者
* @author l-jiahui
*/
@Slf4j
public class ExRecv7 {
/**
* exchangeName
*/
private static final String EX_CHANGE_NAME = "ex_name1";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
/* 3.exchange
* @param_one:exchangeName
* @param_two:exchangeType ---->topic主题
*/
channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
//4.queue
String queue = channel.queueDeclare().getQueue();
//5.binding
channel.queueBind(queue, EX_CHANGE_NAME, "Elephant.#");
//6.get msg
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info(new String(delivery.getBody()));
}
};
//7.consumer
channel.basicConsume(queue, true, deliverCallback, cancel -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExRecv8.java
/**
* topic
* 青蛙系列消费者
*
* @author l-jiahui
*/
@Slf4j
public class ExRecv8 {
/**
* exchangeName
*/
private static final String EX_CHANGE_NAME = "ex_name1";
public static void main(String[] args) {
try {
//1.connection
Connection connection = ConnectionUtil.getConnection();
//2.channel
Channel channel = connection.createChannel();
/* 3.exchange
* @param_one:exchangeName
* @param_two:exchangeType ---->topic主题
*/
channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
//4.queue
String queue = channel.queueDeclare().getQueue();
//5.binding
channel.queueBind(queue, EX_CHANGE_NAME, "lion.#");
//6.get msg
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) {
log.info(new String(delivery.getBody()));
}
};
//7.consumer
channel.basicConsume(queue, true, deliverCallback, cancel -> {
});
} catch (Exception e) {
e.printStackTrace();
}
}
}