第一章 安装RabbitMQ
RabbitMQ是一个消息中间件,遵循AMQP协议 (Advanced Message Queuing Protocol)。由Erlang语言开发,所以安装rabbitmq之前先安装Erlang环境。
Erlang与rabbitmq的版本必须匹配,版本匹配信息可以查看rabbit官网
官网 https://www.rabbitmq.com/which-erlang.html
1.1 安装Erlang
网址:https://www.erlang.org/downloads
右边是对应的版本信息,中间是选择下载的系统环境。
下载成功,点击安装,配置环境。
打开电脑的环境配置,变量值是erlang的安装路径。
修改Path的值,点击新建 复制 %ERLANG_HOME%\bin
然后保存,这里的作用是为了在cmd窗口能够执行erlang的命令。
测试,win + r 打开cmd窗口,输入 erl -v
出现版本号表示安装成功。
1.2 安装 RabbitMQ
下载地址 https://www.rabbitmq.com/install-windows.html
往下滑,选择下载,这个下载资源来自GitHub,比较慢,可以找其他渠道下载。
下载安装成功之后,进入安装目录。
点击上方的目录,输入cmd 加一个空格 然后回车。
执行如下命令,安装管理插件 rabbitmq-plugins enable rabbitmq_management
执行命令 查看安装状态 rabbitmqctl status
点击运行脚本,运行服务器。
访问 后台管理地址 http://localhost:15672/#/
登录账号与密码都是 guest
登录成功如下
到此安装rabbitmq安装成功。
1.3 RabbitMQ后台管理系统
1.3.1 概述
由于当前没有进行任何配置,所以有效内容没有数据。
Totals:显示消息在单位时间内的数据情况。
Node:显示电脑的磁盘、内存等信息,选择表单的最右边加减号图标即可增加或者减少要识别的内容。
对连接,信道,队列流失统计
端口与上下文信息
配置文件导入与导出。
1.3.2 连接
信息的生产者与消费者的连接状态都在这里,由于没有创建,所以,没有任何信息。
1.3.3 信道
1.3.4 交换机
1.3.4 队列
1.3.4 admin
用于添加用户与虚拟主机。
1.4 配置用户与虚拟主机
为什么要配置用户与虚拟主机呢?
如果你是一个人开发,就你一个人那就没有问题,如果有很多人开发,他们都要查看rabbitmq时,只有一个账号是不行的,所以,就要配置一个用户,那虚拟机呢,主要是隔离每一个队列的配置。如,一个用户把自己的所有消息服务配置在一个虚拟主机当中,很容易管理各个的配置信息。
1.4.1 配置用户
如果不使用guest,我们也可以自己创建一个用户
1、 超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、 监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、 策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息。
4、 普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、 其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
添加成功如下,点击用户名配置权限。
1.4.2 新增虚拟主机
主机的名称必须是 /
开头
添加成功如下
设置虚拟主机的访问权限,如哪些用户能访问这个虚拟主机。
配置好之后,退出登录,使用刚刚配置好的用户登录虚拟主机。
退出登录
查看地址栏并登录,显示登录成功。
第二章 RabbitMQ 配置
2.1 rabbitmq 架构
组成部分说明:
- Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
- Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
- Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的
- Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
- Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
生产者发送消息流程:
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
消费者接收消息流程:
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
6、ack回复
点击Tutorials进入官网教程
进入之后,有7中消息模式,下面的语言是对应的案例实现。
2.2 web后台创建消息队列
2.2.1 基本消息模式 (交换机类型:direct)
这种模式没有交换机,直接生产消息,直接消费。
创建成功,如果没有出现队列,则查看当前页面的虚拟主机属性
配置队列,点击队列名称。
发送消息,其他默认
发布成功后,概述出现一条消息
获取消息
查看概述,消息已被消费
2.2.2 work queue 消息模式
work模式属于第一种的模式升级版,可以多个消费队列去消费生产者发布的消息,而且消息只能被一个消费者消费,消费者队列属于竞争关系,该模式需要用代码实现,web后台管理暂时不实现。
2.2.3 Publish/subscribe 模式(交换机类型:Fanout)
发现虚拟主机下已经默认配置了常用类型的交换机,我们就不创建了,直接点击交换机名称,进入交换机配置。
进入 amq.direct
交换机
绑定队列
通过交换机发送消息到queue-01
进入queue-01 队列获取消息
消息获取成功。
2.2.4 Routing 路由模型(交换机类型:direct)
该模式下,所有的队列的key是确定的,只有指定的key与消费者队列的key相匹配,消息才能被消费。
routing路由模式,只要指定的路由key匹配,该队列才会收到消息。
创建两个队列。
分别设置rout-queue-01 rout-queue-02的队列key,设置的这个队列key会与交换机的路由key相匹配,如果匹配成功,则对应的队列消费该消息。
进入交换机查看
发送消息
查看key所在的队列,是否有该消息。
点击rout-queue-01队列查看。
获取消息。
2.2.5 Topics 通配符模式(交换机类型:topics)
该模式是上个模式的升级版,可以对路由key进行通配符操作,满足通配符匹配的队列才能消费消息。
Routingkey一般都是有一个或者多个单词组成,多个单词之间以“.”分割,例如:inform.sms
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
案例:
AA.# : AA.AA AA.AA.BB
#.AA : AA.AA AA.BB.AA
#.AA.# : CC.BB.AA.BB.CC
BB.* : BB.B
*.BB : AA.BB
结合
.#.A..# : B.B.B.A.B.C.C
创建队列
配置每一个队列的路由key
配置完成后,进入交换机查看 amq.top 交换机查看绑定的数据。
发送消息,这个消息的key能发送到三个topic队列。
三个队列全部收到消息
进入队列查看消息。
发送一个消息只有topic-01 能收到。
topic-01.topic.topic-01.topic-01
将匹配到 *.topic.#
发送成功如下
消息内容
总结
通过web后台管理系统,实现了4种模式,分别是基本消息模式、发布与订阅模式、路由模式,主题模式,还剩三种,留到代码中去实践,先分析已经实践过的4中。
第一种基本消息模式:不需要交换机,直接是点对点的发布信息与消费消息(默认是有交换机的,只不过不需要我们配置)。
第二种发布与订阅模式:需要用到交换机,还有配置消费队列与指定的交换机相匹配,生产者通过交换机把消息发送到指定的消费队列中。
第三种Routing模式:需要用到交换机,需要配置消费队列与交换机绑定,还有设置交换机路由,交换机路由负责跟所有绑定了交换机的消费队列进行路由匹配,只有路由匹配成功的,才能消费到消息。
第四种Topic模式:需要用到交换机,需要配置消费队列与交换机绑定,还有设置交换机路由,交换机的路由配置改成了通配符,使用更加灵活。
剩余 work queue、 rpc远程调用、 Publisher Confirms模式在代码中说明。
第三章 代码配置
3.1 创建项目
创建一个maven项目
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
定义一个连接rabbitmq的工具类
public class ConnectionUtil {
/**
* 建立与RabbitMQ的连接
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址 如果使用的guest账号,则这里要设置成localhost,或者 127.0.0.1
factory.setHost("127.0.0.1");
//端口 默认的端口,不能修改
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
//设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq
factory.setVirtualHost("/mq");
factory.setUsername("mqtest");
factory.setPassword("123456");
// 通过工厂获取连接
return factory.newConnection();
}
}
3.2 基本消息模式
P:生产者,也就是要发送消息的程序
C:消费者:消息的接受者,会一直等待消息到来。
queue:消息队列,图中红色部分。可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
生产者
public class Producer {
private final static String QUEUE_NAME = "java_queue";
public static void main(String[] argv) throws Exception {
// 1、获取到连接
Connection connection = ConnectionUtil.getConnection();
// 2、从连接中创建信道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
// 3、声明(创建)队列
//参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 参数明细
* 1、queue 队列名称
* 2、durable 是否持久化,如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
* 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
* 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4、消息内容
String message = "Hello RabbitMQ!";
// 向指定的队列中发送消息
//参数:String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 参数明细:
* 1、exchange,交换机,如果不指定将使用mq的默认交换机(设置为"")
* 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
* 3、props,消息的属性
* 4、body,消息内容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
//关闭通道和连接(资源关闭最好用try-catch-finally语句处理)
// channel.close();
// connection.close();
}
}
代码后面我们没有关闭工厂与信道连接,所以,在web后台管理能看到连接信息,及队列信息。
消费者
public class Consumer {
private final static String QUEUE_NAME = "java_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
/**
* 当接收到消息后此方法将被调用
*
* @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume
* @param envelope 信封,通过envelope
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
String exchange = envelope.getExchange();
// System.out.println("交换机:"+exchange);
//消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
long deliveryTag = envelope.getDeliveryTag();
// System.out.println("信息id:"+deliveryTag);
// body 即消息体
String msg = new String(body, "utf-8");
System.out.println(" [x] received : " + msg + "!");
}
};
// 监听队列,第二个参数:是否自动进行消息确认。
//参数:String queue, boolean autoAck, Consumer callback
/**
* 参数明细:
* 1、queue 队列名称
* 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
* 3、callback,消费方法,当消费者接收到消息要执行的方法
*/
channel.basicConsume(QUEUE_NAME,true,consumer);
//关闭信道连接
// channel.close();
//关闭连接
//connection.close();
}
}
消息生产者与消费者已创建完成,显运行生产者生产消息,在运行消费者消费消息。
3.3 消息确认机制(ACK)
自动ACK:消息一旦被接收,消费者自动发送ACK
手动ACK:消息接收后,不会发送ACK,需要手动调用
两者的区别:
自动ACK机制:只要消费端收到消息,就回复消息被消费,然后删除该消息,出现异常,或者出现其他原因,只要消费者收到就回复确认,不管这个消息有没有处理完成。
手动ACK:需要手动确认消息已经被消费,如果消息消费了但是没有回复,消息会一直存在队列中,必须手动的回复才会消失在队列中。
消费者该为手动确认。
public class Consumer {
private final static String QUEUE_NAME = "java_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
/**
* 当接收到消息后此方法将被调用
*
* @param consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume
* @param envelope 信封,通过envelope
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
String exchange = envelope.getExchange();
// System.out.println("交换机:"+exchange);
//消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
long deliveryTag = envelope.getDeliveryTag();
System.out.println("信息id:"+deliveryTag);
// body 即消息体
String msg = new String(body, "utf-8");
System.out.println(" [x] received : " + msg + "!");
/*
* void basicAck(long deliveryTag, boolean multiple) throws IOException;
* deliveryTag:用来标识消息的id
* multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息。
*/
channel.basicAck(envelope.getDeliveryTag(),true);
}
};
// 监听队列,第二个参数:是否自动进行消息确认。
//参数:String queue, boolean autoAck, Consumer callback
/**
* 参数明细:
* 1、queue 队列名称
* 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
* 3、callback,消费方法,当消费者接收到消息要执行的方法
*/
channel.basicConsume(QUEUE_NAME,false,consumer);
//关闭信道连接
// channel.close();
//关闭连接
//connection.close();
}
}
3.4 work queue模式
work queues与基本消息模式相比,多了一个消费端,两个消费端共同消费同一个队列中的消息,但是一个消息只能被一个消费者获取。
接下来我们来模拟这个流程:
P:生产者:任务的发布者
C1:消费者1:领取任务并且完成任务,假设完成速度较慢(模拟耗时)
C2:消费者2:领取任务并且完成任务,假设完成速度较快
通过 BasicQos 方法设置prefetchCount = 1。这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理1个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。相反,它会将其分派给不是仍然忙碌的下一个Consumer。
值得注意的是:prefetchCount在手动ack的情况下才生效,自动ack不生效。
生产者
public class ProducerWork {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 1、获取到连接
Connection connection = ConnectionUtil.getConnection();
// 2、从连接中创建信道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
for (int i=0;i<30;i++){
// 4、消息内容
String message = "Hello RabbitMQ:"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" ["+i+"] Sent '" + message + "'");
Thread.sleep(i * 2);
}
channel.close();
connection.close();
}
}
消费者01
public class ConsumerWork01 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.basicQos(1);
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println(" [x] received : " + msg + "!");
channel.basicAck(envelope.getDeliveryTag(),true);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
//关闭信道连接
// channel.close();
//关闭连接
//connection.close();
}
}
消费者02
public class ConsumerWork02 {
private final static String QUEUE_NAME = "work_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println(" [x] received : " + msg + "!");
channel.basicAck(envelope.getDeliveryTag(),true);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
//关闭信道连接
// channel.close();
//关闭连接
//connection.close();
}
}
总结:如果一个消费者消费消息很慢,则在它的队列中加入 channel.basicQos(1);
3.5 订阅模式分类
订阅模型分类
1、一个生产者多个消费者
2、每个消费者都有一个自己的队列
3、生产者没有将消息直接发送给队列,而是发送给exchange(交换机、转发器)
4、每个队列都需要绑定到交换机上
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者消费
Exchange类型有以下几种:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Header:header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。
3.5.1 Publish/subscribe
(交换机类型:Fanout,也称为广播 )
生产者
创建交换机 通过交换机把消息发送到队列中
public class ProducerPublish {
// 交换机名称
private final static String EXCHANGE_NAME = "publish_exchange";
public static void main(String[] argv) throws Exception {
// 1、获取到连接
Connection connection = ConnectionUtil.getConnection();
// 2、从连接中创建信道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
// 声明一个交换机,类型为FANOUT
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 4、消息内容
String message = "Hello RabbitMQ:";
//EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
channel.basicPublish( EXCHANGE_NAME, "",null, message.getBytes());
System.out.println(" Sent '" + message + "'");
channel.close();
connection.close();
}
}
消费者01
消费交换机发来的消息
public class ConsumerPublish01 {
//队列
private final static String QUEUE_NAME = "publish01_queue";
//交换机
private final static String EXCHANGE_NAME = "publish_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
消费者02
消费交换机发来的消息
public class ConsumerPublish02 {
//队列
private final static String QUEUE_NAME = "publish02_queue";
//交换机
private final static String EXCHANGE_NAME = "publish_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
1、publish/subscribe与work queues有什么区别。
区别:
1)work queues不用定义交换机,而publish/subscribe需要定义交换机。
2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实际上work queues会将队列绑定到默认的交换机 。
相同点:
所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
2、实际工作用 publish/subscribe还是work queues。
建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大(也可以做到同一队列竞争),并且发布订阅模式可以指定自己专用的交换机。
3.5.2 Routing
路由模式:表示交换机的路由key与队列的路由key一致,才会消费到消息。
生产者
public class ProducerRouting {
// 交换机名称
private final static String EXCHANGE_NAME = "routing_exchange";
public static void main(String[] argv) throws Exception {
// 1、获取到连接
Connection connection = ConnectionUtil.getConnection();
// 2、从连接中创建信道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
// 声明一个交换机,类型为FANOUT
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 4、消息内容
String message = "Hello routing01:";
//发送到 routing02队列
String message02 = "Hello routing02:";
//EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
channel.basicPublish( EXCHANGE_NAME, "routing01",null, message.getBytes());
channel.basicPublish( EXCHANGE_NAME, "routing02",null, message02.getBytes());
System.out.println(" Send '" + message + "'");
System.out.println(" Send '" + message02 + "'");
channel.close();
connection.close();
}
}
消费者01
public class ConsumerRouting01 {
//队列
private final static String QUEUE_NAME = "routing01_queue";
//交换机
private final static String EXCHANGE_NAME = "routing_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机 String queue, String exchange, String routingKey
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routing01");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
消费者02
public class ConsumerRouting02 {
//队列
private final static String QUEUE_NAME = "routing02_queue";
//交换机
private final static String EXCHANGE_NAME = "routing_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机 String queue, String exchange, String routingKey
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"routing02");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
3.5.3 Topics
通配符模式(交换机类型:topics)
Routingkey一般都是有一个或者多个单词组成,多个单词之间以“.”分割,例如:inform.sms
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
案例:
AA.# : AA.AA AA.AA.BB
#.AA : AA.AA AA.BB.AA
#.AA.# : CC.BB.AA.BB.CC
BB.* : BB.B
*.BB : AA.BB
结合
.#.A..# : B.B.B.A.B.C.C
生产者
public class ProducerTopic {
// 交换机名称
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
// 1、获取到连接
Connection connection = ConnectionUtil.getConnection();
// 2、从连接中创建信道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
// 声明一个交换机,类型为FANOUT
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 4、消息内容
String message = "Hello topic01:";
//发送到 routing02队列
String message02 = "Hello topic02:";
//EXCHANGE_NAME:交换机名称, null:路由key,null:属性, message:消息体
channel.basicPublish( EXCHANGE_NAME, "topic.topic.01",null, message.getBytes());
channel.basicPublish( EXCHANGE_NAME, "01-topic.topic",null, message02.getBytes());
channel.basicPublish( EXCHANGE_NAME, "02-topic.topic",null, message.getBytes());
channel.basicPublish( EXCHANGE_NAME, "02-topic.topic.02",null, message02.getBytes());
System.out.println(" Send '" + message + "'");
System.out.println(" Send '" + message02 + "'");
channel.close();
connection.close();
}
}
消费者01
public class ConsumerTopic01 {
//队列
private final static String QUEUE_NAME = "topic01_queue";
//交换机
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机 String queue, String exchange, String routingKey
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.*");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
消费者02
public class ConsumerTopic02 {
//队列
private final static String QUEUE_NAME = "topic02_queue";
//交换机
private final static String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = ConnectionUtil.getConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 绑定到交换机 String queue, String exchange, String routingKey
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.*");
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"#.topic.#");
//实现消费方法
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, "utf-8");
System.out.println("received : " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
3.5.3 RPC (远程调用)
RPC模型示意图:
基本概念:
Callback queue 回调队列,客户端向服务器发送请求,服务器端处理请求后,将其处理结果保存在一个存储体中。而客户端为了获得处理结果,那么客户在向服务器发送请求时,同时发送一个回调队列地址reply_to。
Correlation id 关联标识,客户端可能会发送多个请求给服务器,当服务器处理完后,客户端无法辨别在回调队列中的响应具体和那个请求时对应的。为了处理这种情况,客户端在发送每个请求时,同时会附带一个独有correlation_id属性,这样客户端在回调队列中根据correlation_id字段的值就可以分辨此响应属于哪个请求。
流程说明:
当客户端启动的时候,它创建一个匿名独享的回调队列。
在 RPC 请求中,客户端发送带有两个属性的消息:一个是设置回调队列的 reply_to 属性,另一个是设置唯一值的 correlation_id 属性。
将请求发送到一个 rpc_queue 队列中。
服务器等待请求发送到这个队列中来。当请求出现的时候,它执行他的工作并且将带有执行结果的消息发送给 reply_to 字段指定的队列。
客户端等待回调队列里的数据。当有消息出现的时候,它会检查 correlation_id 属性。如果此属性的值与请求匹配,将它返回给应用。
3.6 持久化
3.6.1 队列持久化
队列持久化:在信道中声明队列是对durable
设置为true。
/**
* 参数明细
* 1、queue 队列名称
* 2、durable 是否持久化,如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
* 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
* 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
3.6.2 交换机持久化
durable
:设置为true,表示持久化。
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT,true);
3.6.3 消息持久化
在发布消息的方法中声明一个MessageProperties.PERSISTENT_TEXT_PLAIN
的参数就能实现持久化。
channel.basicPublish( EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
3.7 死信队列
消息成为死信的三种情况:
- 原队列存在消息过期设置,消息到达超时时间未被消费;
- 队列消息长度到达限制;
- 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3.7.1 超过过期时间
正常生产者(正常交换机)
public class ProducerNormal {
private final static String EXCHANGE_NORMAL = "normal_Exchange";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//创建正常交换机
channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);
// 设置消息过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
//创建正常消息
for (int i = 0 ;i < 10; i++) {
String normalMessage = "send new"+i+" message to normal_exchange!";
channel.basicPublish(EXCHANGE_NORMAL, "normal.topic", properties, normalMessage.getBytes());
System.out.println("message:"+normalMessage);
}
channel.close();
connection.close();
}
}
正常消费者(正常队列)
public class ConsumerNormal {
private final static String EXCHANGE_NORMAL = "normal_Exchange";
private final static String NORMAL_QUEUE = "normal_queue";
private final static String EXCHANGE_DEAL = "deal_Exchange";
private final static String DEAL_QUEUE = "deal_queue";
public static void main(String[] args) {
try {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明死信交换机
channel.exchangeDeclare(EXCHANGE_DEAL, BuiltinExchangeType.TOPIC);
//声明正常交换机
channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);
// 创建死信队列
channel.queueDeclare(DEAL_QUEUE,false,false,false,null);
// 死信队列与死信交换机绑定
channel.queueBind(DEAL_QUEUE,EXCHANGE_DEAL,"*.deal_topic");
//正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
params.put("x-dead-letter-exchange", EXCHANGE_DEAL);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put("x-dead-letter-routing-key", "deal.deal_topic");
// 声明正常队列
channel.queueDeclare(NORMAL_QUEUE,false,false,false,params);
//正常队列与正常交换机绑定
channel.queueBind(NORMAL_QUEUE,EXCHANGE_NORMAL,"*.topic");
channel.basicConsume(NORMAL_QUEUE,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, StandardCharsets.UTF_8);
System.out.println("from normal_exchange"+message);
}
});
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
死信消费者(死信队列)
public class ConsumerDeal {
private final static String DEAL_QUEUE = "deal_queue";
public static void main(String[] args) {
try {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.basicConsume(DEAL_QUEUE,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, StandardCharsets.UTF_8);
System.out.println("from deal_exchange"+message);
}
});
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
测试
1 查看队列里面没有消息;再运行正常消费者创建交换机与队列。
2 运行生产者,发现死信队列有10条消息,这是因为我们没有运行正常的消费者队列,时间超过了设置的过期时间,消息就全部发送到死信队列了。
3 运行正常消费者;发现消息还在死信队列中。
4 运行死信队列,消息被消费。
测试 :运行正常消费者队列,再运行正常生产者;发现消息被正常消费者队列消费。
3.7.2 队列达到最大长度
在正常消费者(正常队列)中加入最大消息数
params.put("x-max-length",6);
3.7.3 消费者拒绝消费消息
删除队列
再运行消费者队列。这样做的原因是:如果不删除,队列名称一样,加了参数跟注释了参数不会同步到队列上。
修改正常消费者队列,把自动确认消息该为手动。
channel.basicConsume(NORMAL_QUEUE,false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, StandardCharsets.UTF_8);
//
channel.basicReject(envelope.getDeliveryTag(),false);
// 确认消息
channel.basicAck(envelope.getDeliveryTag(),true);
// System.out.println("from normal_exchange"+message);
}
});
3.8 TTL队列
time to live,即生存时间
RabbitMQ 支持消息的过期时间,可以在发消息是指定
RabbitMQ 支持队列的过期时间,从消息入队开始计算,只要超过了队列的超时时间配置,那么消息会自动清除。
public class TTLDueue{
/**
* ttl队列/消息
*/
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
String exchangeName = "test_ttl_exchange";
String routingKey = "ttl.abc";
String queueName = "test_ttl_queue";
// 声明exchange
channel.exchangeDeclare(exchangeName, "topic");
// 声明 queue
channel.queueDeclare(queueName, true, false, false, null);
Map<String, Object> arguments = new HashMap<>();
// 队列ttl,设置为8s
arguments.put("message-ttl", 8000);
channel.queueBind(queueName, exchangeName, routingKey, arguments);
for (int i = 0; i < 3; i++) {
// expiration("10000") 设置消息8s过期,消息的ttl
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().deliveryMode(2).contentEncoding("UTF-8")
.expiration("8000").build();
String msg = "这是第" + i + "条ack消息";
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicPublish(exchangeName, routingKey, false, props, msg.getBytes(StandardCharsets.UTF_8));
}
channel.close();
connection.close();
}
}
3.9 延迟队列
延迟队列:
所谓 “延时消息” 是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
在RabbitMQ中并没有支持延时队列的功能,延时队列可以通过 【过期时间 + 死信队列】 来实现
代码实现:思路是把死信队列中的消费消息的代码去掉。
public class ConsumerNormal {
private final static String EXCHANGE_NORMAL = "normal_Exchange";
private final static String NORMAL_QUEUE = "normal_queue";
private final static String EXCHANGE_DEAL = "deal_Exchange";
private final static String DEAL_QUEUE = "deal_queue";
public static void main(String[] args) {
try {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_DEAL, BuiltinExchangeType.TOPIC);
channel.exchangeDeclare(EXCHANGE_NORMAL, BuiltinExchangeType.TOPIC);
channel.queueDeclare(DEAL_QUEUE,false,false,false,null);
channel.queueBind(DEAL_QUEUE,EXCHANGE_DEAL,"*.deal_topic"); //正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
params.put("x-dead-letter-exchange", EXCHANGE_DEAL);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put("x-dead-letter-routing-key", "deal.deal_topic");
// params.put("x-max-length",6);
channel.queueDeclare(NORMAL_QUEUE,false,false,false,params);
channel.queueBind(NORMAL_QUEUE,EXCHANGE_NORMAL,"#.topic");
// channel.basicConsume(NORMAL_QUEUE,false,new DefaultConsumer(channel){
// @Override
// public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// String message = new String(body, StandardCharsets.UTF_8);
// //
// channel.basicReject(envelope.getDeliveryTag(),false);
// // 确认消息
channel.basicAck(envelope.getDeliveryTag(),true);
// // System.out.println("from normal_exchange"+message);
// }
// });
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
第四章 与spring整合
1 导入依赖
<dependencies>
<!-- 与spring整合-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- 日志-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
4.1 topic模式整合
外部属性
rabbit.hosts=127.0.0.1
rabbit.username=mqtest
rabbit.password=123456
rabbit.port=5672
rabbit.virtualHost=/mq
rabbit.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.7.xsd">
<context:property-placeholder location="rabbitmqdata.properties"/>
<bean id="rabbitConnectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="${rabbit.hosts}"/>
<property name="username" value="${rabbit.username}"/>
<property name="password" value="${rabbit.password}"/>
<property name="port" value="${rabbit.port}"/>
<property name="virtualHost" value="${rabbit.virtualHost}"/>
</bean>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<!-- autoDelete:是否自动删除 durable:持久化 -->
<rabbit:queue name="pro_topic_queue" durable="false"/>
<rabbit:queue name="con_topic_queue" durable="false"/>
<!-- topic主题 -->
<rabbit:topic-exchange id="topicExchange" name="topic.exchange" durable="false">
<rabbit:bindings>
<rabbit:binding queue="con_topic_queue" pattern="*.topic" />
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- 创建rabbitTemplate 消息模板类 -->
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory"/>
<bean name="queueListenter" class="com.example.mq.spring.TopicConsumer"/>
<rabbit:listener-container>
<!-- 消费者监听对象绑定消费者队列,如果消费者队列里面有消息就消费-->
<rabbit:listener ref="queueListenter" queue-names="con_topic_queue"/>
</rabbit:listener-container>
</beans>
生产者
public class TopicProducer {
public static void main(String[] args){
AbstractApplicationContext context = new ClassPathXmlApplicationContext("rabbit.xml");
RabbitTemplate template = context.getBean(RabbitTemplate.class);
// 设置构建消息属性
MessagePropertiesBuilder builder = MessagePropertiesBuilder.newInstance();
builder.setContentEncoding("utf-8"); // 设置内容响应编码
builder.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN); // 设置内容响应格式
try {
// 生成Message对象
Message message = MessageBuilder.withBody("你好,世界".getBytes("UTF-8")).andProperties(builder.build()).build();
template.send("topic.exchange", "rabbit.topic",message);
System.out.println("消息发送成功!" + message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费者
public class TopicConsumer implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.print("receive msg: "+new String(message.getBody(), StandardCharsets.UTF_8));
}
}
第五章 与springboot整合
5.1 topic模式整合
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
配置
# 应用名称
spring.application.name=demo
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=mqtest
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/mq
#每次从队列中取一个,轮询分发,默认是公平分发
spring.rabbitmq.listener.simple.prefetch=1
#失败后重试
spring.rabbitmq.listener.simple.default-requeue-rejected=true
生产者
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicConfig {
public static final String QUEUE_EMAIL = "queue_email";//email队列
public static final String QUEUE_SMS = "queue_sms";//sms队列
public static final String EXCHANGE_NAME="topic.exchange";//topics类型交换机
public static final String ROUTINGKEY_EMAIL="topic.#.email.#";
public static final String ROUTINGKEY_SMS="topic.#.sms.#";
//声明交换机
@Bean(EXCHANGE_NAME)
public Exchange exchange(){
//durable(true) 持久化,mq重启之后交换机还在
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//声明email队列
/*
* new Queue(QUEUE_EMAIL,true,false,false)
* durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
* auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
* exclusive 表示该消息队列是否只在当前connection生效,默认是false
*/
@Bean(QUEUE_EMAIL)
public Queue emailQueue(){
return new Queue(QUEUE_EMAIL);
}
//声明sms队列
@Bean(QUEUE_SMS)
public Queue smsQueue(){
return new Queue(QUEUE_SMS);
}
//ROUTINGKEY_EMAIL队列绑定交换机,指定routingKey
@Bean
public Binding bindingEmail(@Qualifier(QUEUE_EMAIL) Queue queue,
@Qualifier(EXCHANGE_NAME) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_EMAIL).noargs();
}
//ROUTINGKEY_SMS队列绑定交换机,指定routingKey
@Bean
public Binding bindingSMS(@Qualifier(QUEUE_SMS) Queue queue,
@Qualifier(EXCHANGE_NAME) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY_SMS).noargs();
}
}
发布消息
@SpringBootTest
@RunWith(SpringRunner.class)
class DemoApplicationTests {
@Test
void contextLoads() {
}
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void sendMsgByTopics() {
/**
* 参数:
* 1、交换机名称
* 2、routingKey
* 3、消息内容
*/
for (int i = 0; i < 5; i++) {
String message = "恭喜您,注册成功!userid=" + i;
rabbitTemplate.convertAndSend(TopicConfig.EXCHANGE_NAME, "topic.sms.email", message);
System.out.println(" [x] Sent '" + message + "'");
}
}
}
运行测试方法,查看web后台
消费者
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
//监听邮件队列
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "queue_email", durable = "true"),
exchange = @Exchange(
value = "topic.exchange",
ignoreDeclarationExceptions = "true",
type = ExchangeTypes.TOPIC
),
key = {"topic.#.email.#","email.*"}))
public void rece_email(String msg){
System.out.println(" [邮件服务] received : " + msg + "!");
}
//监听短信队列
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "queue_sms", durable = "true"),
exchange = @Exchange(
value = "topic.exchange",
ignoreDeclarationExceptions = "true",
type = ExchangeTypes.TOPIC
),
key = {"topic.#.sms.#"}))
public void rece_sms(String msg){
System.out.println(" [短信服务] received : " + msg + "!");
}
}
再次运行测试,消息被消费。