RabbitMQ学习
详细学习视频链接:https://www.bilibili.com/video/BV1dX4y1V73G?p=1
或者: https://www.kuangstudy.com/course
一、RabbitMQ介绍
1、什么是中间件
中间件是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台)但在这里中间件中必须要有一个通信中间件,即中间件=平台+通信,这个定义也限定于分布式系统中才能称为中间件,同时还可以把它和实用软件区分开来。
2、为什么使用中间件
中间件屏蔽了底层操作系统的复杂性,使得程序开发人员对一个简单而统一的开发环境,减少程序设计的复杂性,将注意力集中在自己的业务上,不必再为程序在不同操作系统上面的移植而重复工作,从而大大减少了技术上的负担。中间件带给应用系统的,不只是开发的简便,开发周期的缩短,也减少了系统的维护、运行和管理的工作量,还减少了计算机总体费用的投入。
3、中间件的特点
- 满足大量应用的需要
- 运行于多种硬件和OS平台,跨平台
- 支持分布计算,提供跨网络、硬件和OS平台的透明性的应用或服务的交互
- 支持标准的协议
- 支持标准的接口
4、中间件分类
(1)分布式消息中间件
- ActiveMQ
- RabbitMQ
- Kafka
- RocketMQ
(2)负载均衡中间件
- Nginx
- LVS
- KeepAlive
- CDN
(3)缓存中间件
-
MemCache
-
Redis
(4)数据库中间件
- Mycat
- ShardingJDBC
5、单体架构和微服务架构
单体架构是:一个请求发起jvm调度线程,分配线程Thread处理请求直到释放
分布式的系统是:一个请求由多个系统共同完成,jvm和环境可能是独立的。
6、消息中间件
利用可靠的消息传递机制进行系统和系统直接的通讯
通过提供消息传递和消息的排队机制,它可以在分布式系统环境下扩展进程间的通讯。

核心组成部分:
- 消息的协议
- 消息的持久化机制
- 消息的分发策略
- 消息的高可用,高可靠
- 消息的容错机制
7、AMQP协议
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是一个进程间传递异步消息的网络协议。

工作过程
发布者(Publisher)发布消息(Message),经由交换机(Exchange)。
交换机根据路由规则将收到的消息分发给与该交换机绑定的队列(Queue)。
最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。
为什么消息队列不采用http协议?
1、http请求报文头和响应报文头是比较复杂的,包含了很多东西。
2、大部分的http都是短链接,在实际的交互过程中,一个请求响应很有可能会中断,中段以后就不会持久化了。
8、分发策略
MQ消息队列有以下几个角色:
- 生产者
- 存储消息
- 消费者
生产者生成消息后,MQ进行存储,消费者是如何获得消息的?
一般获取的方式无外乎推(push)或者拉(pull)。典型的git就有推拉机制,我们发送的http请求就是一种典型的拉取数据库数据返回的过程。而消息队列是一种推送的过程。
9、高可用、高可靠
集群模式1:Master-Slave 主从共享

集群模式2:Master-Slave 主从同步

集群模式3:多主集群同步

集群模式4:多主集群转发

集群模式5:Master-Slave与reoker-cluster组合的方案

终归三句话:
- 要么消息共享
- 要么消息同步
- 要么元数据共享
高可用:产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力。
当业务量增加时,请求也过大,一台消息中间件服务器会触及硬件的极限,一台消息服务器已经无法满足业务的需求,所以消息中间件必须支持集群部署,来达到高可用的目的。
高可靠:系统可以无故障持续运行,比如一个系统突然崩溃,报错,异常等等并不能影响线上业务的正常运行,出错的几率低,就称之为:高可靠。
如何保证高可靠:
1、消息的传输:通过协议来保证系统间数据解析的正确性
2、消息的存储可靠:通过持久化来保证消息的可靠性
二、RabbitMQ安装
1、erlang 和 rabbitmq 安装
# 查看系统版本号
lsb_release -a
# 安装下载
wget https://packages.erlang-solutions.com/erlang-solutions-2.0-1.noarch.rpm
rpm -Uvh erlang-solutions-2.0-1.noarch.rpm
# 安装成功
yum install -y erlang
# 测试
erl -v
# 安装socat
yum install -y socat
# 安装rabbitmq
rpm -Uvh rabbitmq-server-3.8.14-1.el7.noarch.rpm
yum install rabbitmq-server -y
# 开机启动服务
systemctl enable rabbitmq-server
# 启动服务
systemctl start rabbitmq-sever
# 查看服务状态
systemctl status rabbitmq-sever
# 停止服务
systemctl stop rabbitmq-sever
注:
rabbitmq下载地址:https://www.rabbitmq.com/download.html

2、管理界面安装
# 安装rabbitmq管理界面
rabbitmq-plugins enable rabbitmq_management
# 重启服务器
systemctl restart rabbitmq-server
# 在浏览器访问
http://ip:15672
# 默认账号和密码 guest 需要开放端口
# 查看防火墙状态
firewall-cmd --state
# 如果上一步处于关闭状态
systemctl start firewalld.service
# 开启 15672 和 5672 端口
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --zone=public --add-port=5672/tcp --permanent
# 重启防火墙
systemctl restart firewalld.service
# 输入命令重新载入配置
firewall-cmd --reload
3、授权账号和密码
# 新增用户
rabbitmqctl add_user admin admin
# 设置用户分配操作权限
rabbitmqctl set_user_tags admin administrator
# 为用户添加资源权限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
# 修改密码
rabbitmqctl change_password admin admin
用户级别:
- administrator 可以登录控制台、查看所有信息、可以对rabbitmq进行管理
- monitoring 监控者 登录控制台,查看所有信息
- policymaker 策略制定者 登录控制台,指定策略
- management 普通管理员 登录控制台
4、docker 安装 rabbitmq
# 获取rabbitmq镜像
docker pull rabbitmq:management
# 创建并运行容器
docker run -di --name=myrabbit -p 15672:15672 rabbitmq:management
--hostname 指定容器主机名称
--name 指定容器名称
-p 将mq端口号映射到本地
# 运行时设置用户和密码
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
# 查看日志
docker logs -f myrabbit
额外的linux相关命令
more xxx.log # 查看日志
netstat -naop | grep 5672 # 查看端口是否被占用
ps -ef | grep 5672 # 查看进程
systemctl stop 服务
三、RabbitMQ角色分类
1、none:
- 不能访问management plugi
2、management: 查看自己相关节点信息
- 列出自己可以通过AMQP登入的虚拟机
- 查看自己的虚拟机节点 virtual hosts 的queues exchanges 和bindings信息
- 查看有关自己的虚拟机节点 virtual hosts 的统计信息,包括其他用户在这个节点virtual hosts中的活动信息
3、Policymaker:
- 包含management所有权限
- 查看和创建和删除自己的virtual hosts 所属的policies 和 parameters信息
4、Monitoring:
- 包含management所有权限
- 罗列出所有的virtual hosts 包括不能登录的virtual hosts
- 查看其他用户的connections和channels信息
- 查看节点级别的数据如:clustering和memory使用情况
- 查看所有的virtual hosts的全局统计信息
5、Administrator:
- 最高权限
- 可以创建和删除virtual hosts
- 可以查看,创建和删除users
- 查看创建permissions
- 关闭所有用户的connections
四、RabbitMQ入门案例
1、实现步骤
- 环境jdk1.8
- 构建一个maven工程
- 导入rabbitmq的maven依赖
- 启动rabbitmq-sever服务
- 定义生产者
- 定义消费者
- 观察消息在rabbitmq-srever服务中的过程
2、导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
3、创建对应的包
4、编写生产者和消费者
package com.lk.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.243.140");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
try {
// 2.创建连接connection
conn = connectionFactory.newConnection("生产者");
// 3。通过连接获取channel
channel = conn.createChannel();
// 4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
String queueName = "queue1";
channel.queueDeclare(queueName,false,false,false,null);
// 5.准备消息内容
String msg = "hello,rabbitmq!";
// 6.发送消息给队列
channel.basicPublish("",queueName,null,msg.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭连接
if(channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 8.关闭通道
if(conn != null && conn.isOpen()){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package com.lk.rabbitmq.simple;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.243.140");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
try {
// 2.创建连接connection
conn = connectionFactory.newConnection("生产者");
// 3。通过连接获取channel
channel = conn.createChannel();
// 4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
channel.basicConsume("queue1", true, new DeliverCallback() {
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("接收失败了!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭连接
if(channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8.关闭通道
if(conn != null && conn.isOpen()){
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
五、RabbitMQ组件和架构

1、核心概念:
-
Server:又称Broker,接受客户端的连接,实现AMQP实体服务,安装rabbitmq-server。
-
Connection:连接,应用程序和Broker的网络连接 ,TCP/IP /三次握手和四次挥手。
-
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立多对Channel,每个Channel代表一个会话任务。
-
Message:消息,服务与应用程序之间传送的数据,由Properties和body组成,properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,body则是消息体的内容。
-
Virtual Host 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机路由可以有若干个Exhange和Queue 同一个虚拟主机里面不能有相同的Exchange。
-
Exchange:交换机,接收消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)。
-
Bindings:Exchange和queue 之间的虚拟连接,binding中可以保护多个routingkey。
-
Routing key: 是一个路由规则,虚拟机可以用它来确定给特定路由一个特定消息。
-
Queue: 队列:也称为 MessageQueue消息队列,保存消息并将它们发给消费者。
2、运行流程:

六、RabbitMQ消息模式
1、简单模式

2、工作模式

- 特点:分发机制
3、发布订阅模式-fanout

- 类型:fanout
- Fanout-发布与订阅模式,是一种光比机制,它是没有路由key的模式
4、路由模式-direct

- 类型:direct
- 特点:有routing-key的匹配模式
5、主题模式-topic

- 类型:topic
- 特点:模糊的routing-key的匹配模式
6、参数模式-headers
- 类型:headers
- 特点:参数匹配模式
七、RabbitMQ其他模式案例
1、fanout
package com.lk.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) {
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.243.140");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
try {
// 2.创建连接connection
conn = connectionFactory.newConnection("生产者");
// 3。通过连接获取channel
channel = conn.createChannel();
// 4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
String queueName = "queue1";
channel.queueDeclare(queueName,false,false,false,null);
// 5.准备消息内容
String msg = "hello,rabbitmq!";
// 6.准备交换机
String exchangeName = "lk.fanout";
String routingKey = "";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭连接
if(channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 8.关闭通道
if(conn != null && conn.isOpen()){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
package com.lk.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static Runnable runnable = new Runnable() {
public void run() {
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.243.140");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection conn = null;
Channel channel = null;
try {
// 2.创建连接connection
conn = connectionFactory.newConnection("生产者");
// 3。通过连接获取channel
channel = conn.createChannel();
// 4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
channel.basicConsume(Thread.currentThread().getName(), true, new DeliverCallback() {
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("接收失败了!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭连接
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8.关闭通道
if (conn != null && conn.isOpen()) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
public static void main(String[] args) {
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
}
2、direct
// 指定交换机
String exchangeName = "lk.direct";
// 指定路由key
String routingKey = "email";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
3、topic
// 指定交换机
String exchangeName = "lk.topic";
// 指定路由key
String routingKey = "lk.lk";
channel.basicPublish(exchangeName,routingKey,null,msg.getBytes());
4、在代码中绑定关系
// 准备交换机
String exchangeName = "lk.direct";
String exchangeType = "direct";
channel.exchangeDeclare(exchangeName,exchangeType,true);
// 声明队列
channel.queueDeclare("queue5",true,false,false,null);
channel.queueDeclare("queue6",true,false,false,null);
channel.queueDeclare("queue7",true,false,false,null);
// 绑定关系
channel.queueBind("queue5",exchangeName,"email");
channel.queueBind("queue6",exchangeName,"phone");
channel.queueBind("queue7",exchangeName,"name");
5、work模式

使用默认交换机,给一个队列发送消息,用两个消费者接收。
(1)轮询
// autoAck 为 true
channel.basicConsume(Thread.currentThread().getName(), true, new DeliverCallback() {
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("接收失败了!");
}
});
(2)公平
// 指定basicQos
channel.basicQos(1);
final Channel finalChannel = channel;
// autoAck 为 false
finalChannel.basicConsume(Thread.currentThread().getName(), false, new DeliverCallback() {
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是:" + new String(message.getBody(), "UTF-8"));
// 改成手动应答
finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, new CancelCallback() {
public void handle(String s) throws IOException {
System.out.println("接收失败了!");
}
});
八、SpringBoot整合
1、生产者
(1)导入依赖

(2)编辑配置文件 application.yml
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.243.140
port: 5672
(3)编写服务
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void makeOrder(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
}
}
(4)编写配置类
@Configuration
public class RabbitMQConfuguration {
// 声明注册fanout模式的交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueue(){
return new Queue("email_fanout_queue",true);
}
@Bean
public Queue phoneQueue(){
return new Queue("phone_fanout_queue",true);
}
@Bean
public Queue addrQueue(){
return new Queue("addr_fanout_queue",true);
}
// 绑定关系
@Bean
public Binding emailBinding(){
return BindingBuilder.bind(eamilQueue()).to(fanoutExchange());
}
@Bean
public Binding phoneBinding(){
return BindingBuilder.bind(phoneQueue()).to(fanoutExchange());
}
@Bean
public Binding addrBinding(){
return BindingBuilder.bind(addrQueue()).to(fanoutExchange());
}
}
(5)测试
@SpringBootTest
class RabbitmqProducerApplicationTests {
@Autowired
OrderService orderService;
@Test
void contextLoads() {
orderService.makeOrder("1","1",5);
}
}
交换机和队列创建成功,且关系绑定正确
2、消费者
(1)导入依赖

(2)编辑配置文件 application.yml
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.243.140
port: 5672
(3)编写服务
@RabbitListener(queues = {"addr_fanout_queue"})
@Service
public class FanoutAddrConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("addr fanout ----接收到了订单信息是:"+ message);
}
}
@RabbitListener(queues = {"email_fanout_queue"})
@Service
public class FanoutEmailConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("email fanout ----接收到了订单信息是:"+ message);
}
}
@RabbitListener(queues = {"phone_fanout_queue"})
@Service
public class FanoutPhoneConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("phone fanout ----接收到了订单信息是:"+ message);
}
}
(4)运行

3、direct 模式
(1)生产者
@Configuration
public class DirectRabbitMQConfuguration {
// 声明注册fanout模式的交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("direct_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueueDirect(){
return new Queue("email_direct_queue",true);
}
@Bean
public Queue phoneQueueDirect(){
return new Queue("phone_direct_queue",true);
}
@Bean
public Queue addrQueueDirect(){
return new Queue("addr_direct_queue",true);
}
// 绑定关系
@Bean
public Binding emailBindingDirect(){
return BindingBuilder.bind(eamilQueueDirect()).to(directExchange()).with("email");
}
@Bean
public Binding phoneBindingDirect(){
return BindingBuilder.bind(phoneQueueDirect()).to(directExchange()).with("phone");
}
@Bean
public Binding addrBindingDirect(){
return BindingBuilder.bind(addrQueueDirect()).to(directExchange()).with("addr");
}
}
public void makeOrderDirect(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "direct_order_exchange";
rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
rabbitTemplate.convertAndSend(exchangeName,"addr",orderId);
}
@Test
void contextLoadsDirect() {
orderService.makeOrderDirect("1","1",5);
}
(2)消费者
@RabbitListener(queues = {"addr_direct_queue"})
@Service
public class DirectAddrConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("addr direct ----接收到了订单信息是:"+ message);
}
}
@RabbitListener(queues = {"email_direct_queue"})
@Service
public class DirectEmailConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("email direct ----接收到了订单信息是:"+ message);
}
}
@RabbitListener(queues = {"phone_direct_queue"})
@Service
public class DirectPhoneConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("phone direct ----接收到了订单信息是:"+ message);
}
}

4、topic 模式
(1)生产者
@Configuration
public class TopicRabbitMQConfuguration {
// 声明注册fanout模式的交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topic_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueueTopic(){
return new Queue("email_topic_queue",true);
}
@Bean
public Queue phoneQueueTopic(){
return new Queue("phone_topic_queue",true);
}
@Bean
public Queue addrQueueTopic(){
return new Queue("addr_topic_queue",true);
}
// 绑定关系
@Bean
public Binding emailBindingTopic(){
return BindingBuilder.bind(eamilQueueTopic()).to(topicExchange()).with("#.email.#");
}
@Bean
public Binding phoneBindingTopic(){
return BindingBuilder.bind(phoneQueueTopic()).to(topicExchange()).with("phone.*");
}
@Bean
public Binding addrBindingTopic(){
return BindingBuilder.bind(addrQueueTopic()).to(topicExchange()).with("#.addr");
}
}
public void makeOrderTopic(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "topic_order_exchange";
rabbitTemplate.convertAndSend(exchangeName,"lk.addr.email",orderId);
}
@Test
void contextLoadsTopic() {
orderService.makeOrderTopic("1","1",5);
}
(2)消费者
配置文件可以放在生产者上,也可以放在消费者里,也可以用注释绑定。
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "addr_topic_queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "#.addr"
))
public class TopicAddrConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("addr topic ----接收到了订单信息是:"+ message);
}
}
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email_topic_queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "#.email.#"
))
public class TopicEmailConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("email topic ----接收到了订单信息是:"+ message);
}
}
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "phone_topic_queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "phone.*"
))
public class TopicPhoneConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("phone topic ----接收到了订单信息是:"+ message);
}
}
九、RabbitMQ ttl和死信队列
1、设置ttl队列过期时间
(1)TTLRabbitMQConfiguration
@Configuration
public class TTLRabbitMQConfuguration {
// 声明注册fanout模式的交换机
@Bean
public DirectExchange ttlExchange(){
return new DirectExchange("ttl_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueueTtl(){
// 设置过期时间
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000);
return new Queue("email_ttl_queue",true,false,false,args);
}
// 绑定关系
@Bean
public Binding emailBindingTtl(){
return BindingBuilder.bind(eamilQueueTtl()).to(ttlExchange()).with("ttl");
}
}
(2)service
public void makeOrderTtl(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "ttl_order_exchange";
rabbitTemplate.convertAndSend(exchangeName,"ttl",orderId);
}
(3)test
@Test
void contextLoadsTtl() {
orderService.makeOrderTtl("1","1",5);
}
2、设置ttl消息过期时间
public void makeOrderTtlMsg(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "ttl_order_exchange";
// 给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(exchangeName,"ttl",orderId);
}
3、死信队列
DLX 全称为:Dead-Letter-Exchange,可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要使用死信队列,只需要在定义队列的时候设置队列参数:x-dead-letter-exchange 指定交换机即可。
// 死信交换机和死信队列的配置
@Configuration
public class DeadRabbitMQConfuguration {
// 声明注册fanout模式的交换机
@Bean
public DirectExchange deadExchange(){
return new DirectExchange("dead_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueueDead(){
return new Queue("email_dead_queue",true);
}
// 绑定关系
@Bean
public Binding emailBindingDead(){
return BindingBuilder.bind(eamilQueueDead()).to(deadExchange()).with("dead");
}
}
// 其他队列绑定死信交换机
@Configuration
public class TTLRabbitMQConfuguration {
// 声明注册direct模式的交换机
@Bean
public DirectExchange ttlExchange(){
return new DirectExchange("ttl_order_exchange",true,false);
}
// 声明队列
@Bean
public Queue eamilQueueTtl(){
// 设置过期时间
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000);
args.put("x-dead-letter-exchange","dead_order_exchange");
args.put("x-dead-letter-routing-key","dead"); // 如果是fanout模式则不需要设置routing-key
return new Queue("email_ttl_queue",true,false,false,args);
}
// 绑定关系
@Bean
public Binding emailBindingTtl(){
return BindingBuilder.bind(eamilQueueTtl()).to(ttlExchange()).with("ttl");
}
}
// service
public void makeOrderTtl(String userid,String productid, int num){
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功:"+orderId);
String exchangeName = "ttl_order_exchange";
rabbitTemplate.convertAndSend(exchangeName,"ttl",orderId);
}
// Test
@Test
void contextLoadsTtl() {
orderService.makeOrderTtl("1","1",5);
}
效果如下(5s后ttl队列中的消息过期,该消息就会自动发送到死信交换机下绑定的死信队列):


十、分布式事务案例

1、可靠生产者

创建一个消息冗余DB,在生产者发送消息给MQ后,判断MQ返回的消息是接收成功还是失败,如果成功,就把消息冗余DB中的状态标志设为1,如果失败就设置为0。然后创建一个定时任务,定时检测消息冗余DB中是否存在状态为0的数据,如果有,就重新发送到MQ。
2、可靠消费者

如果在消费过程中,遇到错误,可以使用重发来解决,主要有以下几个方式:
- 控制重发的次数
- try+catch + 手动ack
- try+catch + 手动ack + 死信队列
其他具体详情可以观看视频教程:https://www.bilibili.com/video/BV1dX4y1V73G?p=1

浙公网安备 33010602011771号