目录
1.RabbitMQ介绍
1.1MQ概述
1.2MQ优势
1.3. MQ的劣势
1.4. 常见的 MQ 产品
1.5. AMQP 和 JMS
1.6. RabbitMQ
2. 安装及配置RabbitMQ
3. RabbitMQ入门
3.1. 编写生产者
3.2. 编写消费者
4. RabbitMQ工作模式
4.1. Work queues工作队列模式
4.1.1. 模式说明
4.1.2. 代码
4.1.3. 测试
4.1.4. 小结
4.2. 订阅模式概述
4.3. Publish/Subscribe发布与订阅模式
4.3.1. 代码
4.3.2. 测试
4.3.3. 小结
4.4. Routing路由模式
4.4.1. 代码
4.4.1. 测试
4.5. Topics通配符模式
4.5.1. 代码
4.5.2. 测试
4.6. 模式总结
5. Spring 整合RabbitMQ
5.1. 搭建生产者工程
5.2. 搭建消费者工程
6. Spring Boot整合RabbitMQ
6.1. 简介
6.2. 搭建生产者工程
6.3搭建消费者工程
7.高级特性
7.1消息的可靠投递
7.1.1 确认模式
7.1.2退回模式
7.2 Consumer Ack
7.3 消费端限流
7.4 TTL
7.5 死信队列
7.6 延迟队列
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统 之间进行通信。
1、应用解耦
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
2、任务异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响 应时间。
一个下单操作耗时:20 + 300 + 300 + 300 = 920ms 用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢!
用户点击完下单按钮后,只需等待25ms就能得到下单响应 (20 + 5 = 25ms)。 提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
3、削峰填谷
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发 量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以 上,这个时候数据库肯定卡死了
消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个消息,这样慢慢 写入数据库,这样就不会卡死数据库了。
但是使用了MQ之后,限制消费消息的速度为1000,但是这样一来,高峰期产生的数据势必会被积压在 MQ中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会 维持在1000QPS,直到消费完积压的消息,这就叫做“填谷”
系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高 可用?
系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。 如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息,如果 B 系统、C 系统处理成功,D 系统处理 失败。如何保证消息数据处理的一致性?
目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq 等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要 结合自身需求及 MQ 产品特征,综合考虑。
RabbitMQ | ActiveMQ | RocketMQ | Kafka | |
公 司/ 社 区 | Rabbit | Apache | 阿里 | Apache |
开 发 语 言 | Erlang | Java | Java | Scala&Java |
协 议 支 持 | AMQP,XMPP,SMTP, STOMP | OpenWire,STOMP, REST,XMPP,AMQP | 自定义 | 自定义协议, 社区封装了 http协议支持 |
客 户 端 支 持 语 言 | 官方支持Erlang,Java, Ruby等,社区产出多种API, 几乎支持所有语言 | Java,C,C++, Python,PHP, Perl,.net等 | Java,C++ (不成熟) | 官方支持J ava,社区产出 多种API,如 PHP,Python 等 |
单 机 吞 吐 量 | 万级(其次) | 万级(最差) | 十万级(最 好) | 十万级(次 之) |
消 息 延 迟 | 微妙级 | 毫秒级 | 毫秒级 | 毫秒以内 |
功 能 特 性 | 并发能力强,性能极其好, 延时低,社区活跃,管理界 面丰富 | 老牌产品,成熟度 高,文档较多 | MQ功能比 较完备,扩 展性佳 | 只支持主要的 MQ功能,毕 竟是为大数据 领域准备的。 |
我主要学习的rabbitmq
实现MQ的大致有两种主流方式:AMQP、JMS。
AMQP
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用 层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,遵 循此协议,不收客户端和中间件产品和开发语言限制。2006年,AMQP 规范发布。类比HTTP。
JMS
JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的API
JMS是JavaEE规范中的一种,类比JDBC
很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ 官方没有提供 JMS 的实现包,但是开源社区有
AMQP 与 JMS 区别
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。RabbitMQ官方地址:http://www.rabbitmq.com/
2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开 发。Erlang 语言专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ 基础架构如下图:
RabbitMQ 中的相关概念:
RabbitMQ提供了6种模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由 模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不作介绍); 官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
正在制作中ing
P:生产者,也就是要发送消息的程序
C:消费者:消息的接受者,会一直等待消息到来
queue:消息队列,图中红色部分。可以缓存消息;生产者向其中投递消息,消费者从其中取出消息
添加依赖
新建一个maven工程,添加amqp-client依赖
com.rabbitmq
amqp-client
5.7.1
编写消息生产者com.wq.rabbitmq.simple.Producer
/**
* @program: com.wq.rabbitmq.simple
* @description: 生产者
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Producer {
public static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
// 创建频道
Channel channel = connection.createChannel();
// 声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 要发送的信息
String message = "你好;RabbitMq11!";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已发送消息:" + message);
// 关闭资源
channel.close();
connection.close();
}
}
在执行上述的消息发送之后;可以登录rabbitMQ的管理控制台,可以发现队列和其消息:
抽取创建connection的工具类com.wq.rabbitmq.util.ConnectionUtil;
/**
* @program: com.wq.rabbitmq.utils
* @description: 连接公共类
* @author: AOQO
* @create: 2022-03-29 17:51
**/
public class ConnectionUtils {
public static Connection getConnection() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.159.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/wqq");
//连接用户名;默认为guest
connectionFactory.setUsername("wq");
//连接密码;默认为guest
connectionFactory.setPassword("wq");
//创建连接
com.rabbitmq.client.Connection connection = connectionFactory.newConnection();
return connection;
}
}
编写消息的消费者com.wq.rabbitmq.simple.Consumer
/**
* @program: com.wq.rabbitmq.simple
* @description: 消费者
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
// 创建频道
Channel channel = connection.createChannel();
// 声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel){
//接收消息的回调
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复
会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.QUEUE_NAME,true,consumer);
//不关闭资源,应该一直监听消息
// channel.close();
// connection.close();
}
}
Work Queues 与入门程序的 简单模式 相比,多了一个或一些消费端,多个消费端共同消费同一个队列 中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
Work Queues 与入门程序的 简单模式 的代码是几乎一样的;可以完全复制,并复制多一个消费者进行 多个消费者同时消费消息的测试。
1)生产者
/**
* @program: com.wq.rabbitmq.work
* @description: 生产者
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Producer {
public static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
// 创建频道
Channel channel = connection.createChannel();
// 声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
for (int i = 1; i < 10 ; i++) {
// 要发送的信息
String message = "你好;RabbitMq!work-queue模式"+i;
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("已发送消息:" + message);
}
// 关闭资源
channel.close();
connection.close();
}
}
2)消费者1
package com.wq.rabbitmq.work;
import com.rabbitmq.client.*;
import com.wq.rabbitmq.utils.ConnectionUtils;
import java.io.IOException;
/**
* @program: com.wq.rabbitmq.work
* @description: 消费者1
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Consumer1 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
// 创建频道
Channel channel = connection.createChannel();
// 声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel){
//接收消息的回调
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复
会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.QUEUE_NAME,true,consumer);
//不关闭资源,应该一直监听消息
// channel.close();
// connection.close();
}
}
3)消费者2
package com.wq.rabbitmq.work;
import com.rabbitmq.client.*;
import com.wq.rabbitmq.utils.ConnectionUtils;
import java.io.IOException;
/**
* @program: com.wq.rabbitmq.work
* @description: 消费者2
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Consumer2 {
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
// 创建频道
Channel channel = connection.createChannel();
// 声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel){
//接收消息的回调
/**
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复
会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.QUEUE_NAME,true,consumer);
//不关闭资源,应该一直监听消息
// channel.close();
// connection.close();
}
}
启动两个消费者,然后再启动生产者发送消息;到IDEA的两个消费者对应的控制台查看是否竞争性的接 收到消息。
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
在订阅模型中,多了一个exchange角色,而且过程略有变化:
Exchange有常见以下3种类型:
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑 定,或者没有符合路由规则的队列,那么消息会丢失!
发布订阅模式: 1、每个消费者监听自己的队列。 2、生产者将消息发给broker,由交换机将消息转发 到绑定此交换机的每个队列,每个绑定交换机的队列都将接收 到消息
1)生产者
package com.wq.rabbitmq.ps;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wq.rabbitmq.utils.ConnectionUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @program: com.wq.rabbitmq.ps
* @description: 生产者-发布订阅模式
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Producer {
public static String FANOUT_EXCHAGE = "fanout_exchage";
public static String FANOUT_QUEUE_1 = "fanout_queue_1";
public static String FANOUT_QUEUE_2 = "fanout_queue_2";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**声明交换机
* 参数1:交换机名称
* 参数2:交换机类型,fanout, topic, direct, headers
*/
channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);
//队列绑定交换机
channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE, "");
for (int i = 1; i <= 10; i++) {
//发送消息
String message = "你好:小兔子~ fanout 模式---" + i;
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(FANOUT_EXCHAGE, "", null, message.getBytes());
System.out.println("已发送消息:" + message);
}
//释放资源
channel.close();
connection.close();
}
}
2)消费者1
package com.wq.rabbitmq.ps;
import com.rabbitmq.client.*;
import com.wq.rabbitmq.utils.ConnectionUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @program: com.wq.rabbitmq.ps
* @description: 消费者1
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.FANOUT_QUEUE_1, Producer.FANOUT_EXCHAGE, "");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.FANOUT_QUEUE_1, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
3)消费者2
package com.wq.rabbitmq.ps;
import com.rabbitmq.client.*;
import com.wq.rabbitmq.utils.ConnectionUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @program: com.wq.rabbitmq.ps
* @description: 消费者
* @author: AOQO
* @create: 2022-03-23 16:31
**/
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.FANOUT_QUEUE_2, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.FANOUT_QUEUE_2, Producer.FANOUT_EXCHAGE, "");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.FANOUT_QUEUE_2, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
启动所有消费者,然后使用生产者发送消息;在每个消费者对应的控制台可以查看到生产者发送的所有 消息;到达广播的效果。 在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges 选项卡,点击 fanout_exchange 的交换机,可以查看到如下的绑定:
交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
发布订阅模式与工作队列模式的区别:
1、工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机。
2、发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使 用默认交换机)。
3、发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将 队列绑 定到默认的交换机 。
路由模式特点:
在编码上与 Publish/Subscribe发布与订阅模式 的区别是交换机的类型为:Direct,还有队列绑定交换 机的时候需要指定routing key。
1)生产者
package com.wq.rabbitmq.routing;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static String DIRECT_EXCHAGE = "direct_exchage";
public static String DIRECT_QUEUE_INSERT = "direct_queue_insert";
public static String DIRECT_QUEUE_UPDATE = "direct_queue_update";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**声明交换机
* 参数1:交换机名称
* 参数2:交换机类型,fanout, topic, direct, headers
*/
channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);
//队列绑定交换机
channel.queueBind(DIRECT_QUEUE_INSERT, DIRECT_EXCHAGE, "insert");
channel.queueBind(DIRECT_QUEUE_UPDATE, DIRECT_EXCHAGE, "update");
//发送消息
String message = "新增商品。 路由模式:routing key insert";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(DIRECT_EXCHAGE, "insert", null, message.getBytes());
System.out.println("已发送消息:" + message);
//发送消息
message = "修改商品。 路由模式:routing key update";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(DIRECT_EXCHAGE, "update", null, message.getBytes());
System.out.println("已发送消息:" + message);
//释放资源
channel.close();
connection.close();
}
}
2)消费者1
package com.wq.rabbitmq.routing;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.DIRECT_EXCHAGE, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHAGE, "insert");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
3)消费者2
package com.wq.rabbitmq.routing;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.DIRECT_QUEUE_UPDATE, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.DIRECT_QUEUE_UPDATE, Producer.DIRECT_EXCHAGE, "update");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.DIRECT_QUEUE_UPDATE, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应 routing key对应队列的消息;到达按照需要接收的效果。
在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges 选项卡,点击 direct_exchange 的交换机,可以查看到如下的绑定:
Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列。
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
# :匹配一个或多个词 * :匹配不多不少恰好1个词
举例: item.# :能够匹配 item.insert.abc 或者 item.insert item.* :只能匹配 item.insert
1)生产者
package com.wq.rabbitmq.topic;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static String TOPIC_EXCHAGE = "topic_exchage";
public static String TOPIC_QUEUE_ALL = "topic_queue_all";
public static String TOPIC_QUEUE_INSERT_UPDATE = "topic_queue_insert_update";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**声明交换机
* 参数1:交换机名称
* 参数2:交换机类型,fanout, topic, direct, headers
*/
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
//发送消息
String message = "新增商品。 topic模式:routing key item.insert";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(TOPIC_EXCHAGE, "item.insert", null, message.getBytes());
System.out.println("已发送消息:" + message);
//发送消息
message = "修改商品。 topic模式:routing key item.update";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(TOPIC_EXCHAGE, "item.update", null, message.getBytes());
System.out.println("已发送消息:" + message);
//发送消息
message = "删除商品。 topic模式:routing key item.delete";
/**
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish(TOPIC_EXCHAGE, "item.delete", null, message.getBytes());
System.out.println("已发送消息:" + message);
//释放资源
channel.close();
connection.close();
}
}
2)消费者1
接收两种类型的消息:更新商品和删除商品
package com.wq.rabbitmq.topic;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_ALL, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.TOPIC_QUEUE_ALL, Producer.TOPIC_EXCHAGE, "item.*");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者1 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.TOPIC_QUEUE_ALL, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
3)消费者2
接收所有类型的消息:新增商品,更新商品和删除商品。
package com.wq.rabbitmq.topic;
import com.wq.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(Producer.TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
//声明(创建)队列
/**
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接,只能有一个Consumer监听这个队列
* 参数4:是否在不使用的时候自动删除队列,当没有Consumer时,自动删除
* 参数5:队列其它参数
*/
channel.queueDeclare(Producer.TOPIC_QUEUE_INSERT_UPDATE, true, false, false, null);
//队列绑定交换机
channel.queueBind(Producer.TOPIC_QUEUE_INSERT_UPDATE, Producer.TOPIC_EXCHAGE, "item.update");
channel.queueBind(Producer.TOPIC_QUEUE_INSERT_UPDATE, Producer.TOPIC_EXCHAGE, "item.insert");
//接收消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 接收到消息执行的的回调
* consumerTag 消息者标签,在channel.basicConsume时候可以指定
* envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
* properties 属性信息
* body 消息
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//路由key
System.out.println("路由key为:" + envelope.getRoutingKey());
//交换机
System.out.println("交换机为:" + envelope.getExchange());
//消息id
System.out.println("消息id为:" + envelope.getDeliveryTag());
//收到的消息
System.out.println("消费者2 - 接收到的消息为:" + new String(body, "utf-8"));
}
};
/**
* 参数1:队列名称
* 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
* 参数3:消息接收到后回调
*/
channel.basicConsume(Producer.TOPIC_QUEUE_INSERT_UPDATE, true, consumer);
//释放资源
// channel.close();
// connection.close();
}
}
启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应 routing key对应队列的消息;到达按照需要接收的效果;并且这些routing key可以使用通配符。
在执行完测试代码后,其实到RabbitMQ的管理后台找到 Exchanges 选项卡,点击 topic_exchange 的交换机,可以查看到如下的绑定:
Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。
RabbitMQ工作模式:
添加依赖
4.0.0
com.wq
spring-rabbitmq-producer
1.0-SNAPSHOT
org.springframework
spring-context
5.1.7.RELEASE
org.springframework.amqp
spring-rabbit
2.1.8.RELEASE
junit
junit
4.12
org.springframework
spring-test
5.1.7.RELEASE
配置整合
1. 创建 spring-rabbitmq-producer\src\main\resources\properties\rabbitmq.properties 连接参数等配置文件:
rabbitmq.host=192.168.159.128
rabbitmq.port=5672
rabbitmq.username=wq
rabbitmq.password=wq
rabbitmq.virtual-host=/wqq
2. 创建 spring-rabbitmq-producer\src\main\resources\spring\spring-rabbitmq.xml 整合 配置文件:
发送消息
创建测试文件 spring-rabbitmq-producer\src\test\java\com\wq\rabbitmq\ProducerTest.java
package com.wq.rabbitmq;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/spring/spring-rabbitmq.xml")
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testQueue() {
rabbitTemplate.convertAndSend("spring_queue", "只发送spring_queue的消息");
}
/**
* 发送广播
* 交换机类型为 fanout
* 绑定到该交换机的所有队列都能够收到消息
*/
@Test
public void fanoutTest(){
/**
* 参数1:交换机名称
* 参数2:路由键名(广播设置为空)
* 参数3:发送的消息内容
*/
rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "发送到spring_fanout_exchange交换机的广播消息");
}
/**
* 通配符
* 交换机类型为 topic
* 匹配路由键的通配符,*表示一个单词,#表示多个单词
* 绑定到该交换机的匹配队列能够收到对应消息
*/
@Test
public void topicTest(){
/**
* 参数1:交换机名称
* 参数2:路由键名
* 参数3:发送的消息内容
*/
rabbitTemplate.convertAndSend("spring_topic_exchange", "wq.bj", "发送到spring_topic_exchange交换机lxs.bj的消息");
rabbitTemplate.convertAndSend("spring_topic_exchange", "wq.bj.1", "发送到spring_topic_exchange交换机lxs.bj.1的消息");
rabbitTemplate.convertAndSend("spring_topic_exchange", "wq.bj.2", "发送到spring_topic_exchange交换机lxs.bj.2的消息");
rabbitTemplate.convertAndSend("spring_topic_exchange", "xzk.cn", "发送到spring_topic_exchange交换机xzk.cn的消息");
}
}
添加依赖
org.springframework
spring-context
5.1.7.RELEASE
org.springframework.amqp
spring-rabbit
2.1.8.RELEASE
junit
junit
4.12
org.springframework
spring-test
5.1.7.RELEASE
配置整合
1. 创建 spring-rabbitmq-consumer\src\main\resources\properties\rabbitmq.properties 连接参数等配置文件:
rabbitmq.host=192.168.159.128
rabbitmq.port=5672
rabbitmq.username=wq
rabbitmq.password=wq
rabbitmq.virtual-host=/wqq
2. 创建 spring-rabbitmq-consumer\src\main\resources\spring\spring-rabbitmq.xml 整合 配置文件:
消息监听器
1)队列监听器
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\SpringQueueListener.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("接收的路由名称为:%s, 路由键:%s, 队列名: %s, 消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2)广播监听器1
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\FanoutListener1.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class FanoutListener1 implements MessageListener {
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("广播监听器1:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3)广播监听器2
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\FanoutListener2.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class FanoutListener2 implements MessageListener {
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("广播监听器2:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4)星号通配符监听器
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\TopicListenerStar.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class TopicListenerStar implements MessageListener {
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("通配符*监听器:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5)井号通配符监听器
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\TopicListenerWell.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class TopicListenerWell implements MessageListener {
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("通配符#监听器:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
6)井号通配符监听器2
创建 spring-rabbitmq-consumer\src\main\java\com\wq\rabbitmq\listener\TopicListenerWell2.java
package com.wq.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class TopicListenerWell2 implements MessageListener {
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("通配符#监听器2:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试
在Spring项目中,可以使用Spring-Rabbit去操作RabbitMQhttps://github.com/spring-projects/spring-amqp
尤其是在spring boot项目中只需要引入对应的amqp启动器依赖即可,方便的使用RabbitTemplate发 送消息,使用注解接收消息
一般在开发过程中:
生产者工程:
消费者工程:
添加依赖
4.0.0
com.wq
springboot-rabbitmq-producer
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
8
8
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-test
启动类
可使用工具自动生成
package com.wq.rabbitmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
配置文件
创建application.yml,内容如下:
spring:
rabbitmq:
host: 192.168.159.128
port: 5672
virtual-host: /wqq
username: wq
password: wq
绑定交换机和队列
创建RabbitMQ队列与交换机绑定的配置类com.wq.rabbitmq.config.RabbitMQConfig
package com.wq.rabbitmq.config;
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 RabbitMQConfig {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "springboot_item_topic_exchange";
//队列名称
public static final String ITEM_QUEUE = "springboot_item_queue";
/**
* 声明交换机
* @return
*/
@Bean
public Exchange itemTopicExchange() {
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
@Bean
public Queue itemQueue() {
return QueueBuilder.durable(ITEM_QUEUE).build();
}
@Bean
public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue, @Qualifier("itemTopicExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
}
测试
在生产者工程springboot-rabbitmq-producer中创建测试类,发送消息:
package com.wq.rabbitmq;
import com.wq.rabbitmq.config.RabbitMQConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMQTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void test(){
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.insert", "商品新增,routing key 为item.insert");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.update", "商品修改,routing key 为item.update");
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "商品删除,routing key 为item.delete");
}
}
先运行上述测试程序(交换机和队列才能先被声明和绑定),然后启动消费者;在消费者工程 springboot-rabbitmq-consumer中控制台查看是否接收到对应消息。
添加依赖
4.0.0
com.wq
springboot-rabbitmq-consumer
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-test
启动类
可自动生成
package com.wq.rabbitmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
配置RabbitMQ
spring:
rabbitmq:
host: 192.168.159.128
port: 5672
virtual-host: /wqq
username: wq
password: wq
消息监听处理类
编写消息监听器com.wq.rabbitmq.listener.MyListener
package com.wq.rabbitmq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @program: com.wq.rabbitmq.listener
* @description:
* @author: wang_777
* @create: 2022-04-17 16:41
**/
@Component
public class MyListener {
@RabbitListener(queues = "springboot_item_queue")
public void myListener1(String message) {
System.out.println("消费者接收到的消息为:" + message);
}
}
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我 们提供了两种方式用来控制消息的投递可靠性模式。
rabbitmq 整个消息投递的路径为:
producer--->rabbitmq broker--->exchange--->queue--->consumer
我们将利用这两个 callback 控制消息的可靠性投递
消息从 producer 到 exchange 则会返回一个 confirmCallback
/**
* 消息可靠性投递
* 确认模式
* 1: 配置publisher-confirms="true"
* 2:rabbitTemplate的ConfirmCallBack回调
*/
@Test
public void testConfirm() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 相关配置信息
* @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
* @param cause 失败原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了。。。");
if (ack) {
System.out.println("成功接收消息:" + cause);
} else {
System.out.println("接收消息失败:" + cause);
//业务处理
}
}
});
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
}
消息从 exchange-->queue 投递失败则会返回一个 returnCallback
/**
* 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
* 步骤:
* 1. 开启回退模式:publisher-returns="true"
* 2. 设置ReturnCallBack
* 3. 设置Exchange处理消息失败的模式:setMandatory
* 1. 如果消息没有路由到Queue,则丢弃消息(默认)
* 2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
*/
@Test
public void testReturn() {
//设置交换机处理失败消息的模式
rabbitTemplate.setMandatory(true);
//2.设置ReturnCallBack
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 消息对象
* @param replyCode 错误码
* @param replyText 错误信息
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("return 执行了....");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
//处理
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
}
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
三种确认方式:
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么 该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手 动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
package com.wq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliverTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.println(new String(message.getBody()));
System.out.println("处理业务逻辑");
// int i = 3/0;//模拟业务处理异常
channel.basicAck(deliverTag, true);
} catch (Exception e) {
e.printStackTrace();
//拒绝签收
//requeue:true:表示重回队列
channel.basicNack(deliverTag, true, true);
// channel.basicReject(deliverTag, true);
}
}
}
记得xml内容
...
...
...
package com.wq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* Consumer 限流机制
* 1. 确保ack机制为手动确认。
* 2. listener-container配置属性
* perfetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
*/
@Component
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
//1.获取消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
//3. 签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
Time To Live,消息过期时间设置
管控台中设置队列TTL
代码实现
代码
/**
* TTL:过期时间
* 1. 队列统一过期
*
* 2. 消息单独过期
*
*
* 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
* 队列过期后,会将队列所有消息全部移除。
* 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
*
*/
@Test
public void testTtl() {
// for (int i = 0; i < 10; i++) {
// // 发送消息
// rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
// }
// // 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
message.getMessageProperties().setExpiration("5000");//消息的过期时间
//2.返回该消息
return message;
}
};
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....", messagePostProcessor);
// for (int i = 0; i < 10; i++) {
// if(i == 5){
// //消息单独过期
// rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
// }else{
// //不过期的消息
// rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
//
// }
//
// }
}
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
代码实现
生产端测试
/**
* 发送测试死信消息:
* 1. 过期时间
* 2. 长度限制
* 3. 消息拒收
*/
@Test
public void testDlx(){
//1. 测试过期时间,死信消息
// rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
//2. 测试长度限制后,消息死信
// for (int i = 0; i < 20; i++) {
// rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
// }
//3. 测试消息拒收
rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.haha","我是一条消息,我会死吗?");
}
消费端监听
package com.wq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
需求:
实现方式:
很可惜,在RabbitMQ中并未提供延迟队列功能。但是可以使用:TTL+死信队列 组合实现延迟队列的效果。
代码实现
生产端测试
@Test
public void testDelay() throws InterruptedException {
//1.发送订单消息。 将来是在订单系统中,下单成功后,发送消息
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
rabbitTemplate.convertAndSend("order_exchange","order.msg","订单信息:id=1,time=" + sdf.format(new Date()));
/*//2.打印倒计时10秒
for (int i = 10; i > 0 ; i--) {
System.out.println(i+"...");
Thread.sleep(1000);
}*/
}
消费端监听
package com.wq.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class OrderListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
System.out.println("根据订单id查询其状态...");
System.out.println("判断状态是否为支付成功");
System.out.println("取消订单,回滚库存....");
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现异常,拒绝接受");
//4.拒绝签收,不重回队列 requeue=false
channel.basicNack(deliveryTag,true,false);
}
}
}