MQ概述
MQ是消息队列的简称,消息队列是一种在分布式系统中用于处理和管理数据交换的技术,它充当应用程序或系统之间的中间件,以可靠地传递消息。消息队列的主要作用包括解耦、异步处理、缓冲、路由和保证消息传递。消息可以是非常简单的文本字符串,也可以包含更复杂的数据结构,如嵌入对象。在消息队列中,消息由生产者发送到队列中,而消费者则从队列中接收和处理这些消息。消息队列广泛应用于处理大量请求、异步任务、事件驱动等场景。此外,消息队列还支持先进先出(FIFO)的队列操作,确保了消息的顺序性。在某些情况下,消息队列还支持优先级处理,以应对紧急或高优先级的消息。
MQ分类
市面上目前比较流行的MQ主要包含四种。RabbitMQ
、Kafka
、RocketMQ
、ActiveMQ
。
特性 | RabbitMQ | Kafka | RocketMQ | ActiveMQ |
---|---|---|---|---|
单机吞吐量 | 万级,同ActiveMQ | 10万级以上,甚至有文献称,可以达到单机百万级TPS | 10 万级,支撑高吞吐 | 万级,相对其他MQ较低 |
topic 数量对吞吐量的影响 | topic 数量增多,吞吐量会下降 | topic从几十到几百个时候,吞吐量会大幅度下降,所以请不要给Kafka设计过多的topic,需要更多的机器资源支撑大规模的 topic | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 数量增多,吞吐量会下降 |
时效性 | 微秒级,延迟最低RabbitMQ 的一大特点 | 延迟在 ms 级以内 | ms 级 | ms 级 |
可用性 | 高,基于主从架构实现高可用 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | 非常高,分布式架构 | 高,基于主从架构实现高可用 |
消息可靠性 | 基本不丢 | 同 RocketMQ。支持事务 | 经过参数优化配置,可以做到 0 丢失。支持事务 | 有较低的概率丢失数据 |
消息顺序性 | 队列的消息有序 | 队列消息有序,topic不保证 | 分区内消息有序 | 分区内消息有序 |
消息延时 | 插件支持 | 插件支持 | 5.0开始支持,定时消息 | 支持,Scheduled Message |
功能支持 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | MQ 功能较为完善,还是分布式的,扩展性好 | MQ 领域的功能极其完备 |
开发语言 | Erlang | Scala+Java | Java | Java |
支持协议 | AMQP | 自定义(基于TCP) | 自定义 | OpenWire、STOMP、REST、XMPP、AMQP |
集群方式 | 支持简单集群,'复制’模式,对高级集群模式支持不好 | 天然的‘Leader-Slave’无状态集群,每台服务器既是Master也是Slave | 常用多对’Master-Slave’ 模式,开源版本需手动切换Slave变成Master | 支持简单集群模式,比如’主-备’,对高级集群模式支持不好 |
消息存储 | 内存、磁盘。支持少量堆积 | 内存、磁盘、数据库。支持大量堆积 | 磁盘。支持大量堆积 | |
系统场景 | 网站通知系统 任务队列系统 微服务通信系统 | 大数据处理平台(如 Hadoop、Spark) 流处理平台(如 Flink、Storm) 日志收集系统(如 ELK) | 电商系统,金融系统,物流系统 | 传统企业应用(如 ERP、CRM) JMS 兼容系统 |
RabbitMQ
RabbitMQ官网:https://www.rabbitmq.com/
特典
遵从AMQP协议
丰富的消息模型极
消息可靠性高但是吞吐量不高
AMQP
一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户中间件不同产品,不同的开发语言等条件的限制。
丰富的消息模型
官网已经列出RabbitMQ具有多种消息模型
虽然官网列出的是七种类型,但实际上只有五种类型,图中的3、4、5属于同一种类型,只是路由方式不一样。
简单模型
工作模型
发布订阅模型
RPC模型
发布确认模型
现在常说的RabbitMQ的五种消息模型其实是简单模型、工作模型、广播模型、路由模型、主题模型,也逐渐被确定为常识,所以生活中常说的五种模型基本上都是上述的五种,已经是普遍认可的。(也有人称之为消息模式,其实都是一个意思)
消息可靠性高但是吞吐量不高
RabbitMQ 提供了多种机制来确保消息的可靠性,包括持久化、消息确认、发布确认等。这些机制确保消息不会丢失,并且能够在各种情况下处理消息传递失败。但是由于存在这些用于保证消息可靠性的机制,所以吞吐量并不高。
Kafka
分布式架构,大数据领域的消息传输,百万级的TPS吞吐量,时效性毫秒级,一个数据多个副本,不会丢失数据,消息有序,通过控制可以让消息被消费一次,主要日志采集和大数据,消费失败不支持重试(可能导致消息丢失)。
RocketMQ
单机吞吐量十万级,高可用、分布式架构,消息可以做到0丢失,支持十亿级别的消息堆积(仅支持Java和C++,C++不成熟),高吞吐量和低延迟。
ActiveMQ
单机吞吐量万级,毫秒级时效性,高可用,基于主从架构实现的高可用,消息丢失的概率极小。
MQ应用场景
1、流量削峰:秒杀、抢购活动等
2、异步处理:发邮件、日志等等
3、应用解耦:订单库存的解耦等
RabbitMQ的优势
1、ActiveMQ,性能不是很好,因此在高并发的场景下,直接被pass掉了。它的Api很完善,在中小型互联网公司可以去使用。而且现如今基本上都没什么企业使用ActiveMQ了,其实这个产品基本可以弃用掉了。
2、kafka,主要强调高性能,如果对业务需要可靠性消息的投递的时候。那么就不能够选择kafka了。但是如果做一些日志收集呢,kafka还是很好的,因为kafka的性能是十分好的。
3、RocketMQ,它的特点非常好。它高性能、满足可靠性、分布式事物、支持水平扩展、上亿级别的消息堆积、主从之间的切换等等。MQ的所有优点它基本都满足。但是它最大的缺点:商业版收费。因此它有许多功能是不对外提供的。
4.它的单机吞吐量也是万级,对于需要支持特别高的并发的情况,它是无法担当重任的,但是,在高可用上,它使用的是镜像集群模式,可以保证高可用;在消息可靠性上,它是可以保证数据不丢失的,这也是它的一大优点;同时它也支持一些消息中间件的高级功能,如:消息重试、死信队列等。并且掌握成本也是很低的。
Windows系统的RabbitMQ安装
由于RabbitMQ是基于Erlang语言开发的,所以需要先安装Erlang,再安装RabbitMQ,而且需要两者的版本进行对应,官网对版本有详细的说明:https://www.rabbitmq.com/docs/which-erlang。
过往的版本也可以进行查阅:
RabbitMQ下载地址:https://github.com/rabbitmq/rabbitmq-server/releases
github上都有每个版本对应的Erlang版本可控范围
安装了虚拟机的可以在Linux上安装,本次采用最简便的Windows版本进行安装
Erlang下载地址:https://www.erlang.org/downloads
先安装Erlang,再安装RabbitMQ
安装RabbitMQ的时候最后一步记得勾选上添加到服务,这样可以灵活设置是否开机自启、手动开启RabbitMQ服务,比较灵活方便。
如果没有勾选也可以手动添加到服务,以管理员身份打开命令窗口,进入RabbitMQ安装目录的sbin目录后执行.\rabbitmq-service.bat install
命令即可。此时如果成功可在服务列表中看到:
Windows+R打开输入框输入services.msc即可看到RabbitMQ成功添加到服务中:
开始安装RabbitMQ的Web界面,先进入安装路径下的sbin目录,然后运行命令:rabbitmq-plugins enable rabbitmq_management
,如下则安装插件成功:
RabbitMQ的端口默认是15672,浏览器输入https://localhost:15672即可访问,默认账户和密码均为guest:
RabbitMQ组件
RabbitMQ的组件可以抽象为图例:
- Broker(消息代理)
- Producer(生产者)
- Consumer(消费者)
- Exchange(交换机)
- Queue(队列)
- Binding(绑定)
- Connection(连接)
- Channel(信道)
生产者发送消息流程:
1、生产者和Broker建立TCP连接
2、生产者和Broker建立通道
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发
4、Exchange将消息转发到指定的Queue(队列)
消费者接收消息流程:
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者
5、消费者接收到消息
6、ack回复
Broker(消息代理)
以服务的形式运行在系统中,并通过AMQP协议实现消息的路由和传递。Broker负责存储、路由和转发消息,并扮演着
消息代理
的中央角色,可以理解为一个MQ节点即可。AMQP 中的消息代理是消息传递的核心组件。它负责接收、存储和传递消息,并将消息路由到正确的目的地。消息代理可以有多个,形成一个消息代理集群,用于分布式和高可用的消息传递。
Producer(生产者)
生产者是消息的发送者,它负责创建并发送消息到消息代理。生产者不需要关心消息的具体路由,只需将消息发送到指定的交换器即可。
Consumer(消费者)
消费者是消息的接收者,它订阅感兴趣的消息,从消息代理中接收并处理消息。消费者可以订阅一个或多个队列,接收符合条件的消息。
Exchange(交换器)
交换器是消息的路由器,它接收从生产者发送的消息,并根据消息的路由键将消息路由到一个或多个队列中。交换器根据不同的路由策略将消息发送到不同的队列。
Queue(队列)
队列是消息的存储位置,它保存待被消费的消息。消息代理将消息发送到队列后,等待消费者从队列中取出消息进行处理。
Binding(绑定)
绑定是交换器和队列之间的关联关系。通过绑定,交换器将消息路由到队列中,使得生产者发送的消息能够被消费者接收。
Connection(连接)
连接是客户端和消息代理之间的物理连接。客户端使用连接与消息代理进行通信,发送和接收消息。
Channel(信道)
信道是 AMQP 连接内的一个虚拟连接,用于在客户端和消息代理之间进行通信。通过信道,客户端可以创建和使用交换器、队列、绑定,发送和接收消息,而无需在每次通信时都创建新的 TCP 连接。
RabbitMQ的工作模式
简单模式
简而言之就是一个生产者一个消费者
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>msg-queue-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<target>8</target>
<source>8</source>
</configuration>
</plugin>
</plugins>
</build>
</project>
连接工具类
package com.ssy.www;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public final class ConnectionUtil {
private static Connection connection;
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
factory.setConnectionTimeout(5 * 1000);
factory.setHandshakeTimeout(30 * 1000);
factory.setPort(5672);
factory.setHost("127.0.0.1");
connection = factory.newConnection();
return connection;
}
public static void colse() throws IOException {
if(connection!=null){
connection.close();
connection = null;
}
}
}
生产者
package com.ssy.www;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
/**
* 简单模式,一个生产者对应一个消费者
*/
public class SimpleSendMode {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(SimpleSendMode.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/**
* 队列名称
* 队列是否持久化(磁盘)
* 是否消息共享(排他性):1、是否删除,2、是否排外(仅当前连接的当前通道可用),true:删除+排他,false:不删除+其他通道可用
* 自动删除
* 其他参数
*/
channel.queueDeclare("simple_send_mode",true,false,false,null);
String msg = "你好,RabbitMQ";
/**
* 交换机
* 路由键
* mandatory
* immediate
* 消息头参数设置
* 消息
*/
channel.basicPublish("","simple_send_mode",false,false, null, msg.getBytes(StandardCharsets.UTF_8));
log.info("消息发送完毕");
channel.close();
ConnectionUtil.colse();
}
}
简单模式发送消息交换机默认使用的是MQ自带的默认交换机,所以指定交换机的时候指定为空字符串即可,路由键指定队列名称即可
mandatory 和 immediate 都是 basicPublish 方法中的两个参数,它们都有当消息传递过程中不可达目的地时将消息返回给生产者的功能。
manadotory:
参数设置为true时,交换器无法根据自身的类型和路由键找到一个符合条件的队列,那么RabbitMQ会调用basicReturn命令将消息返回给生产者,生产者就需要对该笔消息 重新处理或则自定义处理逻辑。当设置为false出现上述情况,则直接丢弃。生产者可以通过channel.addReturnListener方法监听返回给生产者的消息。
immediate:
参数设置为true时,如果交换器在将消息发送到队列时发现队列上并不存在任何消费者,那么这条消息不会存入队列;当与路由键匹配的所有队列队列都没有消费者时,通过basicReturn返回给生产者,生产者自定义处理逻辑;而immediate设置为false时,出现异常情况服务端会直接将消息扔掉。
简而言之,mandatory标志告诉服务器至少将该消息路由到一个队列中,否则将消息返还给生产者;immediate标志告诉服务器如果该消息关联的queue上有消费者,则马上将消息投递给它,如果所有queue都没有消费者,直接把消息返还给生产者,不用将消息入队列等待消费者了。
此外,在RabbitMQ3.0以后的版本里,去掉了immediate参数支持,对此RabbitMQ官方解释是:这个关键字违背了生产者和消费者之间解耦的特性,因为生产者不关心消息是否被消费者消费掉
消费者
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class SimpleGetMode {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(SimpleGetMode.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info("消费消息:【{}】",new String(body, StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("simple_send_mode",true,deliverCallback,cancelCallback);
}
}
1、先启动生产者:
通过RabbitMQ的Web界面可以成功获取到已经创建了队列并且队列中成功被投递都消息:
点击队列名称可以查询到队列的具体信息以及绑定情况:
2、再启动消费者进行消费:
通过Web界面可以很清晰的看到消息已经被消费成功了
模拟mandatory为true的情况:
/**
* 只需要修改此处的路由键和mandatory模拟未路由到队列的情况
*/
channel.basicPublish("","simple_send_mode123",true,false, null, msg.getBytes(StandardCharsets.UTF_8));
mandatory为true的时候,如果没有路由到队列,消息直接会被抛弃掉,如果进行添加监听处理,那么可以自定义其他操作业务逻辑:
/**
* 增加mandatory为true时候的监听器
*/
channel.addReturnListener(returnMessage -> {
log.info("监听由交换机{}通过路由键{}未投递到队列的消息:{}",returnMessage.getExchange(),returnMessage.getRoutingKey(),new String(returnMessage.getBody(),StandardCharsets.UTF_8));
});
注意此处模拟mandatory不要关闭连接和信道
由于RabbitMQ 3.0版本开始去掉了对于immediate参数的支持,所以此处不再进行immediate的样例
工作模式
简而言之就是一个生产者多个消费者,生产者发送的一个消息只能被处理一次,而不是被处理N多次
生产者
package com.ssy.www;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 工作模式,生产者发送的一个消息只能被处理一次,而不是被处理N多次
*/
public class WorkSendMode {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(WorkSendMode.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/**
* 队列名称
* 队列是否持久化(磁盘)
* 是否消息共享(排他性):1、是否删除,2、是否排外(仅当前连接的当前通道可用),true:删除+排他,false:不删除+其他通道可用
* 自动删除
* 其他参数
*/
channel.queueDeclare("work_send_mode",true,false,false,null);
for (int i = 1; i <= 10; i++) {
String msg = "工作模式下的RabbitMQ" + i;
/**
* MQ自带的默认交换机
*/
channel.basicPublish("","work_send_mode",false,false, null, msg.getBytes(StandardCharsets.UTF_8));
log.info(i + "号消息发送完毕");
}
channel.close();
ConnectionUtil.colse();
}
}
消费者1号
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class WorkGetMode1 {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(WorkGetMode1.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info("消费者1号消费消息:【{}】", new String(body, StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("work_send_mode",true,deliverCallback,cancelCallback);
}
}
消费者2号
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class WorkGetMode2 {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(WorkGetMode1.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info("消费者2号消费消息:【{}】", new String(body, StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("work_send_mode",true,deliverCallback,cancelCallback);
}
}
默认是以轮询的方式进行消费的,不会出现重复消费的情况
采用多线程的方式进行消费
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
public class WorkGetMode3 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->consume("消费者3号"));
Thread t2 = new Thread(()->consume("消费者4号"));
t1.start();
t2.start();
System.in.read();
}
private static void consume(String name){
try{
Logger log = LoggerFactory.getLogger(WorkGetMode3.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info(name + "消费消息:【{}】", new String(body, StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("work_send_mode",true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
发布订阅模式
Sending messages to many consumers at once
根据官方提供的交换机类型:fafnout(扇形交换机)、direct(直连交换机)、topic(主题交换机)、headers(头交换机,此交换机类型并没有列在官方的说明中,但实际上也是属于发布订阅模式)组成发布订阅模式
fanout扇形交换机
在日常生活中,其实fanout类型的交换机被大众化为发布订阅模式。,因为这种类型的交换机对于官方给出的Sending messages to many consumers at once
十分契合。
官方说明:fanout类型的交换机不处理路由键,只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。类似于广播,隶属于这个广播的范围中的所有人都可以听到。fanout交换机转发消息是最快的。所以,fanout 不匹配路由键, 只要消息被发送到交换机上,那么绑定这个交换机上的所有的队列都有收到消息,也就是说交换机上所有的队列都会存储消息。
生产者
package com.ssy.www;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* fanout类型的交换机其实指定路由键与不指定路由键都是一样的效果,类似于只要放出广播,所有人都可以听到
*/
public class FanoutTypeSendDemo {
public static void main(String[] args) {
int num = 1;
try{
Logger log = LoggerFactory.getLogger(FanoutTypeSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("fanout_queue_one",true,false,false,null);
channel.queueDeclare("fanout_queue_two",true,false,false,null);
channel.exchangeDeclare("fanout_ex","fanout",true,false,false,null);
channel.queueBind("fanout_queue_one","fanout_ex","fanout_one",null);
channel.queueBind("fanout_queue_two","fanout_ex","fanout_two",null);
Scanner scanner = new Scanner(System.in);
while(true){
String msg = scanner.next();
log.info("生产者发送了一条消息");
if(num%2==1){
channel.basicPublish("fanout_ex","fanout_one",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
} else {
channel.basicPublish("fanout_ex","fanout_two",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
}
num++;
}
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class FanoutTypeGetDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->consume("消费者1号","fanout_queue_one"));
Thread t2 = new Thread(()->consume("消费者2号","fanout_queue_two"));
t1.start();
t2.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String name,String queueName){
try{
Logger log = LoggerFactory.getLogger(FanoutTypeGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
log.info("{}消费消息【{}】",name,new String(message.getBody(),StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("{}消费消息失败或者中断",name);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
direct直连交换机
很多情况下,direct类型的交换机的场景被称为路由模式,因为这种类型的交换机与官方的说明Receiving messages selectively
匹配度最高。
direct直连交换机完全匹配路由键,官方说明:处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “ssy”,则只有被标记为“ssy”的消息才被转发,不会转发"ssy.aaa",也不会转发其他的如"ssy.12345",只会转发"ssy"。
生产者
package com.ssy.www;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class DirectTypeSendDemo {
public static void main(String[] args) {
int num = 1;
try{
Logger log = LoggerFactory.getLogger(DirectTypeSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("direct_queue_one",true,false,false,null);
channel.queueDeclare("direct_queue_two",true,false,false,null);
channel.queueDeclare("direct_queue_three",true,false,false,null);
channel.exchangeDeclare("direct_ex","direct",true,false,false,null);
channel.queueBind("direct_queue_one","direct_ex","direct_one",null);
channel.queueBind("direct_queue_two","direct_ex","direct_two",null);
channel.queueBind("direct_queue_three","direct_ex","direct_three",null);
Scanner scanner = new Scanner(System.in);
while(true){
String msg = scanner.next();
log.info("生产者发送了一条消息");
if(num%3==0){
channel.basicPublish("direct_ex","direct_three",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
} else {
if(num%2==0){
channel.basicPublish("direct_ex","direct_two",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
} else {
channel.basicPublish("direct_ex","direct_one",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
}
}
num++;
}
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DirectTypeGetDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->consume("direct_queue_one","direct_one"));
Thread t2 = new Thread(()->consume("direct_queue_two","direct_two"));
Thread t3 = new Thread(()->consume("direct_queue_three","direct_three"));
t1.start();
t2.start();
t3.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String queueName,String routingKey){
try{
Logger log = LoggerFactory.getLogger(FanoutTypeGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
log.info("消费队列【{}】路由键为【{}】消息【{}】",queueName,routingKey,new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("队列【{}】路由键为【{}】消费消息失败或者中断",queueName,routingKey);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
topic主题交换机
Receiving messages based on a pattern (topics),很多情况下,topic类型的交换机的场景被称为主题模式
官方说明:将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号#匹配零个或一个或多个词
,符号*匹配不多不少一个词
。因此audit.#能够匹配到audit或者audit.irs或者audit.irs.corporate,但是audit.*只会匹配到audit.irs。
生产者
package com.ssy.www;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* * 代表1个
* # 代表0个活多个
*/
public class TopicTypeSendDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4};
Map<String,String> map = new HashMap<>();
map.put(String.valueOf(arr[0]),"com.ssy.demo");
map.put(String.valueOf(arr[1]),"com.ssy.demo.work");
map.put(String.valueOf(arr[2]),"www.ssy.demo.work");
map.put(String.valueOf(arr[3]),"www.ssy.demo.age");
int num = 0;
try{
Logger log = LoggerFactory.getLogger(TopicTypeSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("topic_queue_one",true,false,false,null);
channel.queueDeclare("topic_queue_two",true,false,false,null);
channel.queueDeclare("topic_queue_three",true,false,false,null);
channel.exchangeDeclare("topic_ex","topic",true,false,false,null);
channel.queueBind("topic_queue_one","topic_ex","com.ssy.demo.#",null);
channel.queueBind("topic_queue_two","topic_ex","*.ssy.demo.*",null);
channel.queueBind("topic_queue_three","topic_ex","com.#.work",null);
Scanner scanner = new Scanner(System.in);
while(true){
num = num==arr.length ? 0 : num;
String msg = scanner.next();
log.info("生产者发送了一条消息,指定的具体路由键是【{}】",map.get(String.valueOf(arr[num])));
channel.basicPublish("topic_ex",map.get(String.valueOf(arr[num])),false,false, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
num = num+1;
}
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TopicTypeGetDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->consume("topic_queue_one","com.ssy.demo.#"));
Thread t2 = new Thread(()->consume("topic_queue_two","*.ssy.many.*"));
Thread t3 = new Thread(()->consume("topic_queue_three","com.#.work"));
t1.start();
t2.start();
t3.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String queueName,String routingKey){
try{
Logger log = LoggerFactory.getLogger(TopicTypeGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
log.info("消费队列【{}】通过主题模式配置的路由键【{}】 | 实际上的路由键是【{}】消息【{}】",queueName,routingKey,message.getEnvelope().getRoutingKey(),new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("队列【{}】路由键为【{}】消费消息失败或者中断",queueName,routingKey);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
headers头交换机
头部交换机(Headers Exchange)是RabbitMQ中的一种交换机类型,与其他类型的交换机(直连交换机、主题交换机、扇出交换机)不同,头部交换机使用消息的头信息(Headers)而不是路由键来进行消息的路由。头部交换机提供更灵活的路由方式,允许根据消息头的键值对进行匹配。headers交换机与路由键无关
。
对于headers交换机,如果队列绑定交换机的时候中的x-match为all,那么消息头中的设置必须包含绑定的除了x-match所有的key-value键值对,x-match为any,只需要和绑定除了x-match参数的任何key-value匹配即可。
生产者
package com.ssy.www;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 头交换机与路由键无关
*/
public class HeadersTypeSendDemo {
public static void main(String[] args) {
int num = 1;
Logger log = LoggerFactory.getLogger(HeadersTypeSendDemo.class);
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("headers_queue_one",true,false,false,null);
channel.queueDeclare("headers_queue_two",true,false,false,null);
channel.queueDeclare("headers_queue_three",true,false,false,null);
channel.exchangeDeclare("headers_ex","headers",true,false,false,null);
Map<String,Object> map1 = new HashMap<>();
map1.put("x-match","all");
map1.put("username","root");
map1.put("password","12345");
channel.queueBind("headers_queue_one","headers_ex","com.ssy.www",map1);
channel.queueBind("headers_queue_two","headers_ex","com.ssy.ok",map1);
Map<String,Object> map2 = new HashMap<>();
map2.put("x-match","any");
map2.put("name","ssy");
map2.put("age","24");
channel.queueBind("headers_queue_three","headers_ex","com.ssy.com",map2);
Map<String,Object> map3 = new HashMap<>();
map3.put("username","root");
map3.put("password","12345");
AMQP.BasicProperties props1 = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.headers(map3)
.build();
Map<String,Object> map4 = new HashMap<>();
map4.put("username","root");
map4.put("password","12345");
map4.put("job","java engineer");
AMQP.BasicProperties props2 = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.headers(map4)
.build();
Map<String,Object> map5 = new HashMap<>();
map5.put("sex","boy");
map5.put("name","ssy");
map5.put("job","java engineer");
AMQP.BasicProperties props3 = new AMQP.BasicProperties().builder()
.deliveryMode(2)
.headers(map5)
.build();
Scanner scanner = new Scanner(System.in);
while (true) {
String msg = scanner.next();
log.info("生产者发送了一条消息");
if(num%3==0){
channel.basicPublish("headers_ex","three",props3,msg.getBytes(StandardCharsets.UTF_8));
} else {
if(num%2==1){
channel.basicPublish("headers_ex","one",props1,msg.getBytes(StandardCharsets.UTF_8));
} else {
channel.basicPublish("headers_ex","two",props2,msg.getBytes(StandardCharsets.UTF_8));
}
}
num++;
}
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class HeadersTypeGetDemo {
public static void main(String[] args) {
Thread t1 = new Thread(()->consume("headers_queue_one","com.ssy.www"));
Thread t2 = new Thread(()->consume("headers_queue_two","com.ssy.ok"));
Thread t3 = new Thread(()->consume("headers_queue_three","com.ssy.com"));
t1.start();
t2.start();
t3.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String queueName,String routingKey){
try{
Logger log = LoggerFactory.getLogger(TopicTypeGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
log.info("消费队列【{}】的路由键【{}】包含的头部信息【{}】消费消息【{}】",queueName,message.getEnvelope().getRoutingKey(),message.getProperties().getHeaders(),new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("队列【{}】路由键为【{}】消费消息失败或者中断",queueName,routingKey);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
先启动消费者
再启动消费者
虽然headers交换机在日常生过中使用并不算多,但是头部交换机适用于需要根据消息的多个属性进行复杂匹配的场景,提供了更高度定制化的消息路由能力。
场景:
如果有一个消息头包含"content-type"为"application/json",并且"priority"为"high",一个队列可以通过绑定这两个条件到头部交换机来接收符合这两个条件的消息。
另一个队列可以通过绑定"content-type"为"application/xml"的条件来接收不同类型的消息。
RPC模式(远程过程调用)
Request/reply pattern example
RPC是指远程过程调用(Remote Procedure Call),是一种计算机通信协议,用于将一个计算机程序的执行过程转移至另一台计算机上,但对用户而言,它就像是在本地运行一样,RPC通常用于分布式系统或组件化架构中,可以隐藏分布式系统背后的复杂性。
客户端©声明一个排他队列自己订阅,然后发送消息到RPC队列同时也把这个排他队列名也在消息里传进去,服务端监听RPC队列,处理完业务后把处理结果发送到这个排他队列,然后客户端收到结果,继续处理自己的逻辑。
RPC的处理流程:
- 当客户端启动时,创建一个匿名的回调队列
- 客户端为RPC请求设置2个属性:①replyTo:设置回调队列名字;②correlationId:标记request
- 请求被发送到rpc_queue队列中
- RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列
- 客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了
服务端
package com.ssy.www.rpc;
import java.io.IOException;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import com.rabbitmq.client.AMQP.BasicProperties;
public class RPCServer {
public static void main(String[] args) throws Exception {
ConnectionFactory f = new ConnectionFactory();
f.setHost("127.0.0.1");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection c = f.newConnection();
Channel ch = c.createChannel();
/**
* 定义队列 rpc_queue, 将从它接收请求信息
*
* 参数:
* 1. queue, 对列名
* 2. durable, 持久化
* 3. exclusive, 排他
* 4. autoDelete, 自动删除
* 5. arguments, 其他参数属性
*/
ch.queueDeclare("rpc_queue",false,false,false,null);
ch.queuePurge("rpc_queue");//清除队列中的内容
ch.basicQos(1);//一次只接收一条消息
//收到请求消息后的回调对象
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//处理收到的数据(要求第几个斐波那契数)
String msg = new String(message.getBody(), "UTF-8");
int n = Integer.parseInt(msg);
//求出第n个斐波那契数
int r = fbnq(n);
String response = String.valueOf(r);
//设置发回响应的id, 与请求id一致, 这样客户端可以把该响应与它的请求进行对应
BasicProperties props = new BasicProperties.Builder()
.correlationId(message.getProperties().getCorrelationId())
.build();
/*
* 发送响应消息
* 1. 默认交换机
* 2. 由客户端指定的,用来传递响应消息的队列名
* 3. 参数(关联id)
* 4. 发回的响应消息
*/
ch.basicPublish("",message.getProperties().getReplyTo(), props, response.getBytes("UTF-8"));
//发送确认消息
ch.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
//中断或者失败
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//消费者开始接收消息, 等待从 rpc_queue接收请求消息, 不自动确认
ch.basicConsume("rpc_queue", false, deliverCallback, cancelCallback);
}
protected static int fbnq(int n) {
if(n == 1 || n == 2) return 1;
return fbnq(n-1)+fbnq(n-2);
}
}
客户端
package com.ssy.www.rpc;
import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import com.rabbitmq.client.AMQP.BasicProperties;
public class RPCClient {
Connection con;
Channel ch;
public RPCClient() throws Exception {
ConnectionFactory f = new ConnectionFactory();
f.setHost("127.0.0.1");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
con = f.newConnection();
ch = con.createChannel();
}
public String call(String msg) throws Exception {
//自动生成对列名,非持久,独占,自动删除
String replyQueueName = ch.queueDeclare().getQueue();
//生成关联id
String corrId = UUID.randomUUID().toString();
//设置两个参数:
//1. 请求和响应的关联id
//2. 传递响应数据的queue
BasicProperties props = new BasicProperties.Builder()
.correlationId(corrId)
.replyTo(replyQueueName)
.build();
//向 rpc_queue 队列发送请求数据, 请求第n个斐波那契数
ch.basicPublish("", "rpc_queue", props, msg.getBytes("UTF-8"));
//用来保存结果的阻塞集合,取数据时,没有数据会暂停等待
BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
//接收响应数据的回调对象
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery message) throws IOException {
//如果响应消息的关联id,与请求的关联id相同,我们来处理这个响应数据
if (message.getProperties().getCorrelationId().contentEquals(corrId)) {
//把收到的响应数据,放入阻塞集合
response.offer(new String(message.getBody(), "UTF-8"));
}
}
};
CancelCallback cancelCallback = new CancelCallback() {
@Override
public void handle(String consumerTag) throws IOException {
}
};
//开始从队列接收响应数据
ch.basicConsume(replyQueueName, true, deliverCallback, cancelCallback);
//返回保存在集合中的响应数据
return response.take();
}
public static void main(String[] args) throws Exception {
RPCClient client = new RPCClient();
while (true) {
System.out.print("求第几个斐波那契数:");
int n = new Scanner(System.in).nextInt();
String r = client.call(""+n);
System.out.println(r);
}
}
}
先启动服务端,再启动客户端:
利用RabbitMQ作为RPC的弊大于利:
- RabbitMQ周边支持不完善,如序列化问题,同步异步选择,服务治理,都需要开发者自行设计与编码
- 调用方和被调用方多对多时,结果集可能乱序,需开发者为每个结果寻找其请求线程并返回值
- 高度依赖MQ集群的可用性和稳定性,一旦MQ故障,RPC即无法进行
和真正的RPC框架比起来,优势不足,缺陷太多,主要是需要开发者进行大量代码编写才能实现较完善的RPC功能,与其如此,不如直接使用现成的RPC框架了,所以虽然RabbitMQ 官方提供了 RPC 模式,但使用者寥寥。除非是少量的异步调用,或跨语言问题无法解决,否则笔者也是不建议在生产环境上选用RabbitMQ 作为 RPC 框架。
发布确认模式
Reliable publishing with publisher confirms
在RabbitMQ中,消息的发送端确认消息是否成功到达服务器称为发布确认(publisher confirm)
当消息成功到达服务器时,RabbitMQ会向发送方回传一个确认
如果消息未能到达目标队列,RabbitMQ也会回传一个否定确认
发布确认模式有三种情况:
- 同步单条发布确认
- 同步批量发布确认
- 异步发布确认
其中前两者都是属于同步操作
对于同步发布确认操作,开启发布确认:channel.confirmSelect(),消息发送完毕之后channel.basicPublish,然后等待确认:channel.waitForConfirms()
对于异步发布确认操作,增加监听器即可,channel.addConfirmListener(ConfirmCallback success, ConfirmCallback fail)
同步单条发布确认
private static void singleton(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
//单个确认
boolean flag = channel.waitForConfirms();
if(!flag){
System.out.println("第" + i + "条消息确认失败");
}
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息单独确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
同步批量发布确认
private static void multi(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
//批量确认
if(i%100==0){
boolean success = channel.waitForConfirms();
if(!success){
System.out.println("第" + (i/100) + "个100条消息批量确认失败");
}
}
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息批量确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
异步发布确认
private static void aysnc(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
/**
* 线程有序安全的hash表,适用于高并发的场景
* 1.轻松的将序号与消息进行关联
* 2.两个线程之间传递消息
* 3.轻松批量删除
* 4.高并发、多线程
*/
ConcurrentSkipListMap<Long,String> outContainer = new ConcurrentSkipListMap<>();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
//成功确认回调
ConfirmCallback success = (deliveryTag, multiple) -> {
//删除已经确认的消息
if(multiple){
ConcurrentNavigableMap<Long, String> yes = outContainer.headMap(deliveryTag);
yes.clear();
}else {
outContainer.remove(deliveryTag);
}
};
//失败确认回调?如何处理?重新发送?保存起来后续再发送?
ConfirmCallback fail = (deliveryTag, multiple) -> {
String msg = outContainer.get(deliveryTag);//未确认的消息
System.out.println("未确认的消息标志:" + deliveryTag);
System.out.println("未确认的消息:" + msg);
};
//监听器监听发送成功和失败的数据
channel.addConfirmListener(success,fail);
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
outContainer.put(channel.getNextPublishSeqNo(),msg);
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息异步确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
完整代码如下:
package com.ssy.www;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
/**
* 确认模式:单个、批量、异步
*/
public class PublishConfirmMode {
public static void main(String[] args) {
//单个
FutureTask<Void> task1 = new FutureTask<Void>(() -> {
singleton();
return null;
});
//批量
FutureTask<Void> task2 = new FutureTask<Void>(() -> {
multi();
return null;
});
//异步
FutureTask<Void> task3 = new FutureTask<Void>(() -> {
aysnc();
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(task1);
pool.submit(task2);
pool.submit(task3);
}
private static void singleton(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
//单个确认
boolean flag = channel.waitForConfirms();
if(!flag){
System.out.println("第" + i + "条消息确认失败");
}
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息单独确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
private static void multi(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
//批量确认
if(i%100==0){
boolean success = channel.waitForConfirms();
if(!success){
System.out.println("第" + (i/100) + "个100条消息批量确认失败");
}
}
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息批量确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
private static void aysnc(){
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
/**
* 线程有序安全的hash表,适用于高并发的场景
* 1.轻松的将序号与消息进行关联
* 2.两个线程之间传递消息
* 3.轻松批量删除
* 4.高并发、多线程
*/
ConcurrentSkipListMap<Long,String> outContainer = new ConcurrentSkipListMap<>();
channel.queueDeclare("publish_confirm_mode",true,false,false,null);
long sss = System.currentTimeMillis();
//成功确认回调
ConfirmCallback success = (deliveryTag, multiple) -> {
//删除已经确认的消息
if(multiple){
ConcurrentNavigableMap<Long, String> yes = outContainer.headMap(deliveryTag);
yes.clear();
}else {
outContainer.remove(deliveryTag);
}
};
//失败确认回调?如何处理?重新发送?保存起来后续再发送?
ConfirmCallback fail = (deliveryTag, multiple) -> {
String msg = outContainer.get(deliveryTag);//未确认的消息
System.out.println("未确认的消息标志:" + deliveryTag);
System.out.println("未确认的消息:" + msg);
};
//监听器监听发送成功和失败的数据
channel.addConfirmListener(success,fail);
for (int i = 1; i <= 10000; i++) {
String msg = "消息" + i;
channel.basicPublish("","publish_confirm_mode",false,false,null,msg.getBytes(StandardCharsets.UTF_8));
outContainer.put(channel.getNextPublishSeqNo(),msg);
}
long eee = System.currentTimeMillis();
System.out.println("10000个消息异步确认信息耗时:" + (eee-sss) + "毫秒");
} catch (Exception e){
e.printStackTrace();
}
}
}
毋庸置疑,很明显异步肯定是快于同步的,批量是快于单条确认的
消息应答
概述
消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了,会发生什么情况。RabbitMQ一旦向消费者传递了一条消息,便立即将该消息标记为删除。在这种情况下,突然有个消费者挂掉了,那么将丢失正在处理的消息,以及后续发送给该消费这】者的消息,因为它无法接收到。
为了保证消息在发送过程中不丢失,rabbitmq引入消息应答机制,消息应答就是:消费者在接收到消息并且处理该消息之后,告诉rabbitmq它已经处理了,rabbitmq可以把该消息删除了
。
消息应答分为自动应答和手动应答两种
RabbitMQ应答的方法有三种:
-
Channel. basicAck(long deliveryTag, boolean multiple),用于肯定确认,RabbitMQ已知道该消息并且成功的处理消息,可以将其丢弃了
-
Channel.basicNack(long deliveryTag, boolean multiple, boolean requeue),用于否定确认
-
Channel. basicReject(long deliveryTag, boolean requeue),用于否定确认与Channel.basicNack相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃了
deliveryTag为消息的标识,multiple为是否批量确认,requeue为是否重新入列
自动应答
消息发送后立即被认为是已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡,因为这种模式如果消息在接收到之前,消费者出现连接或者channel关闭,那么消息就丢失了,当然另一方面这种模式消费者可以传递过载的消息,没有对传递的消息数量进行限制,当然这样有可能使得消费者由于接收太多还来不及处理的消息,导致这些消息的积压,最终使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并以某种速率能够处理这些消息的情况下使用。
因此,自动应答存在诸多问题:
- 自动应答模式比手动应答要快,但在高并发场景下容易出现数据丢失问题
- 自动应答模式容易导致生产的速度远远大于消费消息的速度,从而导入消息积压,最终使得内存耗尽导致数据丢失
- 自动应答模式只适合在消费者高效处理消息并且对数据可靠性要求不高的场景下使用
生产者
package com.ssy.www;
import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class AutoAckSendDemo {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(AutoAckSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/**
* 队列名称
* 队列是否持久化(磁盘)
* 是否消息共享(排他性):1、是否删除,2、是否排外(仅当前连接的当前通道可用),true:删除+排他,false:不删除+其他通道可用
* 自动删除
* 其他参数
*/
channel.queueDeclare("auto_ack_send_demo",true,false,false,null);
/**
* MQ自带的默认交换机
* 路由键
* mandatory
* immediate
* 消息属性(例如:可以设置消息持久化)消息持久化并不能完全保证消息不丢失,当消息存储到磁盘但是还没有存储完,消息还在缓存的一个间隔点,此时并没有真正写入磁盘
* 消费的消息
*/
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
//1-非持久化 2-持久化
.deliveryMode(2)
.build();
for (int i = 1; i <= 100; i++) {
String msg = "测试自动应答消息" + i;
channel.basicPublish("","auto_ack_send_demo",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送完毕");
}
}
启动生产者,生产的100条消息已经就绪:
消费者
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
public class AutoAckGetDemo {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(AutoAckGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info("【{}】",new String(body, StandardCharsets.UTF_8));
//眠1秒
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答:true:自动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("auto_ack_send_demo",true,deliverCallback,cancelCallback);
}
}
模拟消费者每隔1秒消费1个消息,消费者没有消费完的时候模拟消费者宕机或者关闭消费者程序:
正常情况下,消费者能消费完生产者生产的消息,但是如果消费者在消费的过程中宕机了,就会造成数据丢失的问题
手动应答
手动应答的好处是可以批量应答并且减少网络拥堵。
在RabbitMQ中,手动应答机制(Manual Acknowledgement)是指消费者接收到消息后,需要显式地发送确认消息(acknowledgement)给RabbitMQ,告知消息已经被成功处理。手动应答机制的引入主要是为了解决消息消费的可靠性问题。手动应答机制需要消费者在处理消息完成后,主动发送确认消息给RabbitMQ。
生产者
package com.ssy.www;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
public class ManualAckSendDemo {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(ManualAckSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/**
* 队列名称
* 队列是否持久化(磁盘)
* 是否消息共享(排他性):1、是否删除,2、是否排外(仅当前连接的当前通道可用),true:删除+排他,false:不删除+其他通道可用
* 自动删除
* 其他参数
*/
channel.queueDeclare("manual_ack_send_demo",true,false,false,null);
/**
* MQ自带的默认交换机
* 路由键
* mandatory
* immediate
* 消息属性(例如:可以设置消息持久化)消息持久化并不能完全保证消息不丢失,当消息存储到磁盘但是还没有存储完,消息还在缓存的一个间隔点,此时并没有真正写入磁盘
* 消费的消息
*/
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
//1-非持久化 2-持久化
.deliveryMode(2)
.build();
for (int i = 1; i <= 100; i++) {
String msg = "测试自动应答消息" + i;
channel.basicPublish("","manual_ack_send_demo",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送完毕");
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
public class ManualAckGetDemo {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(ManualAckGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
int size = 10;
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
//眠3秒
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] body = message.getBody();
log.info("【{}】",new String(body, StandardCharsets.UTF_8));
//手动应答
/**
* 消息的唯一标识,表示应答的是哪一个消息
* 是否批量应答
*/
long deliveryTag = message.getEnvelope().getDeliveryTag();
//单个确认
channel.basicAck(deliveryTag,false);
//批量确认
//size是一批中消息的个数,当消息达成sizeE时,就会触发手动确认消息的方法
//if(deliveryTag%size==0){
// channel.basicAck(deliveryTag,true);
//}
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 是否自动应答:false:手动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
channel.basicConsume("manual_ack_send_demo",false,deliverCallback,cancelCallback);
}
}
启动生产者,再启动消费者:
注意,上述都是一个生产者对应一个消费者,也就是简单模式,如果是类似于工作模式的一个生产者对应多个消费者,如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或 TCP 连接丢失),导致消息未发送 ACK 确认,RabbitMQ 将了解到消息未完全处理,并将对其重新排队。如果此时其他消费者可以处理,它将很快将其重新分发给另一个消费者。这样,即使某个消费者偶尔死亡,也可以确保不会丢失任何消息。
由于自动应答容易造成消息丢失,要想实现消息消费过程中不丢失,需要把自动应答改为手动应答并加入多个消费线程进行测试,此处不再进行赘述,具体可以参考下图:
关于批量应答,multiple的true和false代表不同意思。
- true代表批量应答channel上未应答的消息,比如说channel上有传送tag的消息 5,6,7,8,当前tag是8,那么此时 5-8的这些还未应答的消息都会被确认收到消息应答
- false同上面相比(通常用false),只会应答tag=8的消息 5,6,7这三个消息依然不会被确认收到消息应答
预取值实现不公平分发
RabbitMQ默认是按照轮询的方式进行消费的,这种方式比较公平,现在按照能者多劳的方式进行消费,这种就不公平,但是可以很好的提高消费效率与性能,那么就涉及到预取值。注意:预取值的实现必须是手动应答,自动应答将没有任何效果,因为自动应答是存在数据丢失的情况的。
生产者
package com.ssy.www;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
public class PrefetchSendDemo {
public static void main(String[] args) throws Exception {
Logger log = LoggerFactory.getLogger(PrefetchSendDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
/**
* 队列名称
* 队列是否持久化(磁盘)
* 是否消息共享(排他性):1、是否删除,2、是否排外(仅当前连接的当前通道可用),true:删除+排他,false:不删除+其他通道可用
* 自动删除
* 其他参数
*/
channel.queueDeclare("prefetch_send_demo",true,false,false,null);
/**
* MQ自带的默认交换机
* 路由键
* mandatory
* immediate
* 消息属性(例如:可以设置消息持久化)消息持久化并不能完全保证消息不丢失,当消息存储到磁盘但是还没有存储完,消息还在缓存的一个间隔点,此时并没有真正写入磁盘
* 消费的消息
*/
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
//1-非持久化 2-持久化
.deliveryMode(2)
.build();
for (int i = 1; i <= 100; i++) {
String msg = "测试自动应答消息" + i;
channel.basicPublish("","prefetch_send_demo",false,false, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送完毕");
}
}
消费者
package com.ssy.www;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DeliverCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class PrefetchGetDemo {
private static int num1 = 0;
private static int num2 = 0;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->consume("消费者1号"));
Thread t2 = new Thread(()->consume("消费者2号"));
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(2L);
Scanner scanner = new Scanner(System.in);
while(true){
System.out.print("结束输入请输入1:");
int res = scanner.nextInt();
if(res==1){
break;
}
}
scanner.close();
System.out.println("消费者1好消费了消息共" + num1 +"条");
System.out.println("消费者2好消费了消息共" + num2 +"条");
if(num1==num2){
System.out.println("实现了公平分发");
}else{
System.out.println("实现了能者多劳");
}
}
private static void consume(String name){
try{
Logger log = LoggerFactory.getLogger(PrefetchGetDemo.class);
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明接受消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
byte[] body = message.getBody();
log.info(name + "消费消息:【{}】", new String(body, StandardCharsets.UTF_8));
//预取值需要手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
if(name.equals("消费者1号")){
num1++;
} else {
num2++;
}
};
CancelCallback cancelCallback = consumerTag -> {
log.info("消息消费被取消或者中断了");
};
/**
* 队列名称
* 手动应答
* 消费者成功消费的回调
* 消费者消费失败的回调
*/
//预取值
int prefetchCount = name.equals("消费者1号") ? 1 : 9;
channel.basicQos(prefetchCount);
channel.basicConsume("prefetch_send_demo",false,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
由上述图片的两个消费者处理消息的数量对比得知,设置的不公平分发策略生效了,消费者2号预取值更大,从而承担了处理更多任务的责任,而消费者1号预取值小,从而导致处理任务的条数没有消费者2号多(写两个消费者的程序执行的话效果会更明显,此处为了简便使用多线程的方式进行执行)。
死信队列
死信队列(Dead Letter Queue)是RabbitMQ中一个特殊的队列,用于存储因消费者无法处理的消息而被拒绝的消息(或者说消费者主动拒绝的消息)。当队列中的消息成为死信后,它们可以被重新发送到另一个队列,这个队列就是死信队列
死信队列产生的原因:
- 消息过期
- 消息个数达到队列最大长度
- 消费者拒绝消费
最经典的应用场景:超时未支付订单处理
生产者
package com.ssy.www.dead;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* 死信队列(消息TTL过期、队列达到最大长度、消息被拒绝《否定应答、拒绝应答》)
*/
public class SendDemo {
public static void main(String[] args) {
//消息达到过期时间
FutureTask<Void> task1 = new FutureTask<>(()->{
reachExpiredTime();
return null;
});
//消息个数达到队列最大长度
FutureTask<Void> task2 = new FutureTask<>(()->{
reachMaxLength();
return null;
});
//消费者拒绝消费
FutureTask<Void> task3 = new FutureTask<>(()->{
consumerRejected();
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(task1);
pool.submit(task2);
pool.submit(task3);
}
private static void reachExpiredTime(){
Logger log = LoggerFactory.getLogger(SendDemo.class);
Connection connection = null;
Channel channel = null;
try{
connection = ConnectionUtil.getConnection();
channel = connection.createChannel();
//死信交换机和队列
channel.exchangeDeclare("dead_direct_ex","direct",true,false,false,null);
channel.queueDeclare("dead_queue",true,false,false,null);
channel.queueBind("dead_queue","dead_direct_ex","com.dead.ssy",null);
//---------------//
//普通交换机和队列
channel.exchangeDeclare("normal_direct_ex","direct",true,false,false,null);
Map<String, Object> arguments = new HashMap<>();
//设置转发的死信交换机
arguments.put("x-dead-letter-exchange","dead_direct_ex");
//设置转发的死信交换机匹配的死信路由键
arguments.put("x-dead-letter-routing-key","com.dead.ssy");
//设施消息过期时间 10秒 这个一般由发送消息的生产者设置,比较灵活,这里设置就固定了10秒的过期时间,不太灵活,w无法进行修改
//arguments.put("x-message-ttl",10*1000);
channel.queueDeclare("ttl_queue",true,false,false,arguments);
channel.queueBind("ttl_queue","normal_direct_ex","com.ssy.ttl",null);
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder()
.deliveryMode(2)//持久化
//10秒
.expiration("10000")//此处设置过期时间更灵活
.build();
for (int i = 1; i <= 10; i++) {
String msg = "消息" + i + "号,过期时间为10秒";
//过期
channel.basicPublish("normal_direct_ex", "com.ssy.ttl",false,false,props,msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送成功~");
} catch (Exception e){
e.printStackTrace();
}
}
private static void reachMaxLength(){
Logger log = LoggerFactory.getLogger(SendDemo.class);
Connection connection = null;
Channel channel = null;
try{
connection = ConnectionUtil.getConnection();
channel = connection.createChannel();
//死信交换机和队列
channel.exchangeDeclare("dead_direct_ex","direct",true,false,false,null);
channel.queueDeclare("dead_queue",true,false,false,null);
channel.queueBind("dead_queue","dead_direct_ex","com.dead.ssy",null);
//---------------//
//普通交换机和队列
channel.exchangeDeclare("normal_direct_ex","direct",true,false,false,null);
Map<String, Object> arguments = new HashMap<>();
//设置转发的死信交换机
arguments.put("x-dead-letter-exchange","dead_direct_ex");
//设置转发的死信交换机匹配的死信路由键
arguments.put("x-dead-letter-routing-key","com.dead.ssy");
//设置队列最大长度
arguments.put("x-max-length",5);
channel.queueDeclare("max_length_queue",true,false,false,arguments);
channel.queueBind("max_length_queue","normal_direct_ex","com.ssy.max.length",null);
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder()
.deliveryMode(2)//持久化
.build();
for (int i = 1; i <= 10; i++) {
String msg = "消息" + i + "号,队列最大长度为5";
//过期
channel.basicPublish("normal_direct_ex", "com.ssy.max.length",false,false,props,msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送成功~");
} catch (Exception e){
e.printStackTrace();
}
}
private static void consumerRejected(){
Logger log = LoggerFactory.getLogger(SendDemo.class);
Connection connection = null;
Channel channel = null;
try{
connection = ConnectionUtil.getConnection();
channel = connection.createChannel();
//死信交换机和队列
channel.exchangeDeclare("dead_direct_ex","direct",true,false,false,null);
channel.queueDeclare("dead_queue",true,false,false,null);
channel.queueBind("dead_queue","dead_direct_ex","com.dead.ssy",null);
//---------------//
//普通交换机和队列
channel.exchangeDeclare("normal_direct_ex","direct",true,false,false,null);
Map<String, Object> arguments = new HashMap<>();
//设置转发的死信交换机
arguments.put("x-dead-letter-exchange","dead_direct_ex");
//设置转发的死信交换机匹配的死信路由键
arguments.put("x-dead-letter-routing-key","com.dead.ssy");
channel.queueDeclare("reject_queue",true,false,false,arguments);
channel.queueBind("reject_queue","normal_direct_ex","com.ssy.reject",null);
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder()
.deliveryMode(2)//持久化
.build();
for (int i = 1; i <= 10; i++) {
String msg = "消息" + i + "号测试消费者拒绝消费";
//过期
channel.basicPublish("normal_direct_ex", "com.ssy.reject",false,false,props,msg.getBytes(StandardCharsets.UTF_8));
}
log.info("消息发送成功~");
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www.dead;
import com.rabbitmq.client.*;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class GetDemo {
public static void main(String[] args) {
//死信队列
Thread t1 = new Thread(()->consume("dead_queue"));
//拒绝消费
Thread t2 = new Thread(()->consume("reject_queue"));
t1.start();
t2.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 过期时间
* 队列最大长度
* 拒绝应答
* dead_queue自动应答
*/
private static void consume(String queueName){
Logger log = LoggerFactory.getLogger(GetDemo.class);
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback;
if(queueName.equals("reject_queue")){ //reject_queue满足条件的消息拒绝应答
deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
if(msg.equals("消息3号测试消费者拒绝消费")){
log.info("交换机【{}】通过路由键【{}】传送给【{}】队列的消息:【{}】,但是消息被拒绝应答了,会进入死信交换机",message.getEnvelope().getExchange(),message.getEnvelope().getRoutingKey(),queueName,new String(message.getBody(), StandardCharsets.UTF_8));
//拒绝应答
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);//不重新进入normal_queue队列,那么就会进入死信交换机
}else{
log.info("交换机【{}】通过路由键【{}】传送给【{}】队列的消息:【{}】",message.getEnvelope().getExchange(),message.getEnvelope().getRoutingKey(),queueName,new String(message.getBody(), StandardCharsets.UTF_8));
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);//肯定应答
}
};
} else{ //dead_queue自动应答
deliverCallback = (consumerTag, message) -> {
log.info("开始死信消费>>>交换机【{}】通过路由键【{}】传送给【{}】队列的消息:【{}】",message.getEnvelope().getExchange(),message.getEnvelope().getRoutingKey(),queueName,new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
log.info("队列【{}】的消息消费失败或者中断",queueName);
};
}
CancelCallback cancelCallback = consumerTag -> {
log.info("队列【{}】的消息消费失败或者中断",queueName);
};
if(queueName.equals("reject_queue")){
//开启手动应答
channel.basicConsume(queueName,false,deliverCallback,cancelCallback);
} else {
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
延迟队列
简单说,延迟队列就是死信队列中消息过期的特殊情况。
关于过期有两种情况:
1、如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中);
2、而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
生产者
package com.ssy.www.delay;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class DelaySendDemo {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(DelaySendDemo.class);
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("common_direct_ex","direct",true,false,false,null);
Map<String,Object> map1 = new HashMap<>();
map1.put("x-message-ttl",10*1000);
map1.put("x-dead-letter-exchange","delay_direct_ex");
map1.put("x-dead-letter-routing-key","com.ssy.delay");
Map<String,Object> map2 = new HashMap<>();
map2.put("x-message-ttl",40*1000);
map2.put("x-dead-letter-exchange","delay_direct_ex");
map2.put("x-dead-letter-routing-key","com.ssy.delay");
channel.queueDeclare("expire_queue_10",true,false,false,map1);
channel.queueDeclare("expire_queue_40",true,false,false,map2);
channel.queueBind("expire_queue_10","common_direct_ex","com.ssy.ten.seconds",null);
channel.queueBind("expire_queue_40","common_direct_ex","com.ssy.forty.seconds",null);
延迟队列
channel.exchangeDeclare("delay_direct_ex","direct",true,false,false,null);
channel.queueDeclare("delay_queue",true,false,false,null);
channel.queueBind("delay_queue","delay_direct_ex","com.ssy.delay",null);
///消息设置过期属性
Map<String,Object> map3 = new HashMap<>();
map3.put("x-dead-letter-exchange","delay_direct_ex");
map3.put("x-dead-letter-routing-key","com.ssy.delay");
channel.queueDeclare("common_queue",true,false,false,map3);
channel.queueBind("common_queue","common_direct_ex","com.ssy.common",null);
//队列设置消息过期属性x-message-ttl
logger.info("开始发送消息,TTL设置为40秒······");
channel.basicPublish("common_direct_ex","com.ssy.forty.seconds", null,"队列设置TTL属性,40s后会进入延迟队列进行消费".getBytes(StandardCharsets.UTF_8));
logger.info("开始发送消息,TTL设置为10秒······");
channel.basicPublish("common_direct_ex","com.ssy.ten.seconds",null,"队列设置TTL属性,10s后会进入延迟队列进行消费".getBytes(StandardCharsets.UTF_8));
//消息设置过期属性
logger.info("开始发送消息,发送者灵活设置过期时间······");
AMQP.BasicProperties props = new AMQP.BasicProperties()
.builder()
.expiration("15000")
.build();
channel.basicPublish("common_direct_ex","com.ssy.common",props,"生产者灵活设置15s过期时间后会进入延迟队列进行消费".getBytes(StandardCharsets.UTF_8));
AMQP.BasicProperties props1 = new AMQP.BasicProperties()
.builder()
.expiration("5000")
.build();
channel.basicPublish("common_direct_ex","com.ssy.common",props1,"生产者灵活设置5s过期时间后会进入延迟队列进行消费".getBytes(StandardCharsets.UTF_8));
} catch (Exception e){
e.printStackTrace();
}
}
}
消费者
package com.ssy.www.delay;
import com.rabbitmq.client.*;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 延迟队列会存在一个问题:如果生产者发送消息的时候设置消息属性为TTL,如果A消息过期时间为10s,B消息过期时间为5s,如果先发A再发B,那么B消息会在A之后消费
* 简而言之就是第一个消息延迟时间很长第二个消息延迟时间很短,那么第二个消息不会优先被消费,但是如果队列设置消息过期属性则不会出现这种情况
*/
public class DelayGetDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> consume("delay_queue"));
t1.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String queueName){
Logger logger = LoggerFactory.getLogger(DelayGetDemo.class);
try {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
logger.info("开始消费队列【{}】中的消息:【{}】",queueName,new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
logger.info("队列【{}】中的消息消费失败或者中断",queueName);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
显而易见,现在能达到消息想延迟多久就延迟多久,但对于消息灵活设置过期时间并且发送两条以上的消息时,不能达到延迟低先输出的效果。看起来似乎没什么问题,但是如果使用在消息属性上设置 TTL 的方式,消息可能并不会按时“死亡,因为 RabbitMQ 只会检查第一个消息是否过期,如果过期则丢到死信队列, 如果第一个消息的延时时长很长,而第二个消息的延时时长很短,第二个消息并不会优先得到执行。
当然,对于这种这种情况,RabbitMQ官方给出了插件进行解决(插件实现延迟队列)
1、 https://www.rabbitmq.com/community-plugins.html查看rabbitmq_delayed_message_exchange插件
2、下载地址:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases (注意需要对应MQ的大版本)
下载好之后之前放到RabbitMQ安装目录的plugins目录下
然后在RabbitMQ安装目录下的sbin目录下(以管理员身份)命令行执行名令:.\rabbitmq-plugins enable rabbitmq_delayed_message_exchange,重启RabbitMQ服务即可生效
此时RabbitMQ的Web界面对于交换机的类型就会多出现一个x-delayed-message
基于插件-生产者
package com.ssy.www.plugin.delayed;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class DelayedMessageSendDemo {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(DelayedMessageSendDemo.class);
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//队列
channel.queueDeclare("delay_queue_installed_plugin",true,false,false,null);
//交换机
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-delayed-type","direct");//指定与队列的通信方式
//交换机的类型x-delayed-message
channel.exchangeDeclare("delay_ex_installed_plugin","x-delayed-message",true,false,false,arguments);
//绑定
channel.queueBind("delay_queue_installed_plugin","delay_ex_installed_plugin","com.ssy.plugin",null);
logger.info("先开始发送延迟10秒的消息······");
Map<String,Object> headers1 = new HashMap<>();
headers1.put("x-delay","10000");
AMQP.BasicProperties props1 = new AMQP.BasicProperties()
.builder()
//对于插件的延迟,需要设置的是消息头信息:x-delay,如果设置expiration将不会生效
//.expiration("10000")
.headers(headers1)
.build();
channel.basicPublish("delay_ex_installed_plugin","com.ssy.plugin",false,false,props1,"10秒的等待".getBytes(StandardCharsets.UTF_8));
logger.info("然后开始发送延迟5秒的消息······");
Map<String,Object> headers2 = new HashMap<>();
headers1.put("x-delay","5000");
AMQP.BasicProperties props2 = new AMQP.BasicProperties()
.builder()
//对于插件的延迟,需要设置的是消息头信息:x-delay,如果设置expiration将不会生效
//.expiration("5000")
.headers(headers2)
.build();
channel.basicPublish("delay_ex_installed_plugin","com.ssy.plugin",false,false,props2,"5秒的速度".getBytes(StandardCharsets.UTF_8));
/**
*
*/
} catch (Exception e){
e.printStackTrace();
}
}
}
基于插件-消费者
package com.ssy.www.plugin.delayed;
import com.rabbitmq.client.*;
import com.ssy.www.ConnectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class DelayedMessageGetDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->consume("delay_queue_installed_plugin"));
thread.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void consume(String queueName){
Logger logger = LoggerFactory.getLogger(DelayedMessageGetDemo.class);
try{
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
DeliverCallback deliverCallback = (consumerTag, message) -> {
logger.info("开始消费交换机【{}】通过路由键【{}】传递给队列【{}】的消息:{}",message.getEnvelope().getExchange(), message.getEnvelope().getRoutingKey(),queueName, new String(message.getBody(), StandardCharsets.UTF_8));
};
CancelCallback cancelCallback = consumerTag -> {
logger.info("队列【{}】中的消息消费失败或者中断",queueName);
};
channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
} catch (Exception e){
e.printStackTrace();
}
}
}
SpringBoot整合RabbitMQ
公共pom依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
生产者模块
yml文件
server:
port: 8001
servlet:
context-path: /ssy
spring:
application:
name: rabbit-spring-producer
rabbitmq:
host: 127.0.0.1
port: 5672
username: ssy
password: ssy
virtual-host: ssy
connection-timeout: 10000 # 10秒
java代码如下:
package com.ssy.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RabbitMQApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitMQApplication.class,args);
}
}
package com.ssy.www.constant;
import java.util.HashMap;
import java.util.Map;
public interface RabbitMQConstant {
// 简单模式
String SIMPLE_MODE_QUEUE = "spring.simple_queue";
// 工作模式
String WORK_MODE_QUEUE = "spring.work.queue";
// fanout交换机
String PUBLISH_SCRIBE_MODE_QUEUE = "spring.fanout.queue";
String PUBLISH_SCRIBE_MODE_EXCHANGE = "spring.fanout.exchange";
// direct交换机
String ROUTE_MODE_QUEUE1 = "spring.direct.queue1";
String ROUTE_MODE_QUEUE2 = "spring.direct.queue2";
String ROUTE_MODE_EXCHANGE = "spring.direct.exchange";
String ROUTE_MODE_ROUTING_KEY1 = "spring.direct.routing.key1";
String ROUTE_MODE_ROUTING_KEY2 = "spring.direct.routing.key2";
// topic交换机
String TOPIC_MODE_QUEUE1 = "spring.topic.queue1";
String TOPIC_MODE_QUEUE2 = "spring.topic.queue2";
String TOPIC_MODE_EXCHANGE = "spring.topic.exchange";
String TOPIC_MODE_ROUTING_KEY1 = "com.*.www";
String TOPIC_MODE_ROUTING_KEY2 = "com.ssy.www.#";
// headers交换机
String HEADERS_QUEUE_ANY = "spring.topic.queue.any";
String HEADERS_QUEUE_ALL = "spring.topic.queue.all";
String HEADERS_EXCHANGE = "spring.headers.exchange";
// 死信队列
String DEAD_DIRECT_QUEUE = "spring.dead.queue";
String DEAD_DIRECT_EXCHANGE = "spring.dead.exchange";
String DEAD_DIRECT_ROUTING_KEY = "spring.dead.routing.key";
// 消息设置ttl(消息设置过期时间)
String TTL_MESSAGE_HEADER_QUEUE = "ttl.spring.message.header.queue";
String TTL_MESSAGE_HEADER_EXCHANGE = "ttl.spring.message.header.exchange";
String TTL_MESSAGE_HEADER_ROUTING_KEY = "ttl.spring.message.header.routing.key";
// 消息设置ttl(队列设置x-message-ttl)
String TTL_PROPERTY_QUEUE1 = "spring.ttl.property.queue1";
String TTL_PROPERTY_QUEUE2 = "spring.ttl.property.queue2";
String TTL_PROPERTY_EXCHANGE = "spring.ttl.property.exchange";
String TTL_PROPERTY_ROUTING_KEY1 = "spring.ttl.property.routing.key1";
String TTL_PROPERTY_ROUTING_KEY2 = "spring.ttl.property.routing.key2";
// 消息条数达到队列最大长度
String MAX_LENGTH_QUEUE = "spring.max.length.queue";
String MAX_LENGTH_EXCHANGE = "spring.max.length.exchange";
String MAX_LENGTH_ROUTING_KEY = "spring.max.length.routing.key";
// 消费者拒绝消费
String REJECT_CONSUME_QUEUE = "spring.reject.consume.queue";
String REJECT_CONSUME_EXCHANGE = "spring.reject.consume.exchange";
String REJECT_CONSUME_ROUTING_KEY = "spring.reject.consume.routing.key";
// 过期队列
String EXPIRED_QUEUE = "spring.expired.queue";
String EXPIRED_EXCHANGE = "spring.expired.exchange";
String EXPIRED_ROUTING_KEY = "spring.expired.routing.key";
// 延迟队列
String DELAYED_QUEUE = "spring.delayed.queue";
String DELAYED_EXCHANGE = "spring.delayed.exchange";
String DELAYED_ROUTING_KEY = "spring.delayed.routing.key";
}
package com.ssy.www.config;
import com.ssy.www.constant.RabbitMQConstant;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQComponentConfig{
/**
* 通过spring boot创建队列、交换机、进行绑定都有两种方式
*/
@Bean
public Queue createSimpleModeQueue(){
return new Queue(RabbitMQConstant.SIMPLE_MODE_QUEUE, true, false, false, null);
//return QueueBuilder.durable(RabbitMQConstant.SIMPLE_MODE_QUEUE).build();
}
@Bean
public Queue createWorkModeQueue(){
//return new Queue(RabbitMQConstant.WORK_MODE_QUEUE, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.WORK_MODE_QUEUE).build();
}
@Bean
public Queue createPublishScribeModeQueue(){
//return new Queue(RabbitMQConstant.PUBLISH_SCRIBE_MODE_QUEUE, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.PUBLISH_SCRIBE_MODE_QUEUE).build();
}
@Bean
public FanoutExchange createPublishScribeModeExchange(){
return new FanoutExchange(RabbitMQConstant.PUBLISH_SCRIBE_MODE_EXCHANGE,true,false,null);
// return ExchangeBuilder.fanoutExchange(RabbitMQConstant.PUBLISH_SCRIBE_MODE_EXCHANGE)
// .durable(true)
// .build();
}
@Bean
public Binding bindPublishScribeMode(@Qualifier("createPublishScribeModeExchange") FanoutExchange exchange, @Qualifier("createPublishScribeModeQueue") Queue queue){
return new Binding(RabbitMQConstant.PUBLISH_SCRIBE_MODE_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.PUBLISH_SCRIBE_MODE_EXCHANGE,"",null);
// return BindingBuilder.bind(queue)
// .to(exchange);
}
@Bean
public Queue createRouteModeQueue1(){
//return new Queue(RabbitMQConstant.ROUTE_MODE_QUEUE1, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.ROUTE_MODE_QUEUE1).build();
}
@Bean
public Queue createRouteModeQueue2(){
//return new Queue(RabbitMQConstant.ROUTE_MODE_QUEUE2, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.ROUTE_MODE_QUEUE2).build();
}
@Bean
public DirectExchange createRouteModeExchange(){
// return new DirectExchange(RabbitMQConstant.ROUTE_MODE_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.ROUTE_MODE_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindRouteMode1(@Qualifier("createRouteModeExchange") DirectExchange exchange, @Qualifier("createRouteModeQueue1") Queue queue){
// return new Binding(RabbitMQConstant.ROUTE_MODE_QUEUE1, Binding.DestinationType.QUEUE,RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY1,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.ROUTE_MODE_ROUTING_KEY1);
}
@Bean
public Binding bindRouteMode2(@Qualifier("createRouteModeExchange") DirectExchange exchange, @Qualifier("createRouteModeQueue2") Queue queue){
// return new Binding(RabbitMQConstant.ROUTE_MODE_QUEUE2, Binding.DestinationType.QUEUE,RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY2,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.ROUTE_MODE_ROUTING_KEY2);
}
@Bean
public Queue createTopicModeQueue1(){
//return new Queue(RabbitMQConstant.TOPIC_MODE_QUEUE1, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.TOPIC_MODE_QUEUE1).build();
}
@Bean
public Queue createTopicModeQueue2(){
//return new Queue(RabbitMQConstant.TOPIC_MODE_QUEUE2, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.TOPIC_MODE_QUEUE2).build();
}
@Bean
public TopicExchange createTopicModeExchange(){
// return new TopicExchange(RabbitMQConstant.TOPIC_MODE_EXCHANGE,true,false,null);
return ExchangeBuilder.topicExchange(RabbitMQConstant.TOPIC_MODE_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindTopicMode1(@Qualifier("createTopicModeExchange") TopicExchange exchange, @Qualifier("createTopicModeQueue1") Queue queue){
// return new Binding(RabbitMQConstant.TOPIC_MODE_QUEUE1, Binding.DestinationType.QUEUE,RabbitMQConstant.TOPIC_MODE_EXCHANGE,RabbitMQConstant.TOPIC_MODE_ROUTING_KEY1,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.TOPIC_MODE_ROUTING_KEY1);
}
@Bean
public Binding bindTopicMode2(@Qualifier("createTopicModeExchange") TopicExchange exchange, @Qualifier("createTopicModeQueue2") Queue queue){
// return new Binding(RabbitMQConstant.TOPIC_MODE_QUEUE2, Binding.DestinationType.QUEUE,RabbitMQConstant.TOPIC_MODE_EXCHANGE,RabbitMQConstant.TOPIC_MODE_ROUTING_KEY2,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.TOPIC_MODE_ROUTING_KEY2);
}
@Bean
public Queue creatHeadersQueue1(){
//return new Queue(RabbitMQConstant.HEADERS_QUEUE_ANY, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.HEADERS_QUEUE_ANY).build();
}
@Bean
public Queue creatHeadersQueue2(){
//return new Queue(RabbitMQConstant.HEADERS_QUEUE_ALL, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.HEADERS_QUEUE_ALL).build();
}
@Bean
public HeadersExchange createHeadersExchange(){
// return new HeadersExchange(RabbitMQConstant.HEADERS_EXCHANGE,true,false,null);
return ExchangeBuilder.headersExchange(RabbitMQConstant.HEADERS_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindHeaders1(@Qualifier("createHeadersExchange") HeadersExchange exchange, @Qualifier("creatHeadersQueue1") Queue queue){
Map<String,Object> map = new HashMap<>();
map.put("x-match","any");
map.put("username","ssy");
map.put("password","12345");
// return new Binding(RabbitMQConstant.ROUTE_MODE_QUEUE2, Binding.DestinationType.QUEUE,RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY2,map);
return BindingBuilder.bind(queue)
.to(exchange)
.whereAny(new HashMap<String,Object>(){
{put("username","ssy");}
{put("password","12345");}
})
.match();
}
@Bean
public Binding bindHeaders2(@Qualifier("createHeadersExchange") HeadersExchange exchange, @Qualifier("creatHeadersQueue2") Queue queue){
Map<String,Object> map = new HashMap<>();
map.put("x-match","all");
map.put("id","8023");
map.put("auth","root");
// return new Binding(RabbitMQConstant.ROUTE_MODE_QUEUE2, Binding.DestinationType.QUEUE,RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY2,map);
return BindingBuilder.bind(queue)
.to(exchange)
.whereAll(new HashMap<String,Object>(){
{put("id","8023");}
{put("auth","root");}
})
.match();
}
@Bean
public Queue createDeadQueue(){
//return new Queue(RabbitMQConstant.DEAD_DIRECT_QUEUE, true, false, false, null);
return QueueBuilder.durable(RabbitMQConstant.DEAD_DIRECT_QUEUE).build();
}
@Bean
public DirectExchange createDeadExchange(){
// return new DirectExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindDead(@Qualifier("createDeadExchange") DirectExchange exchange, @Qualifier("createDeadQueue") Queue queue){
// return new Binding(RabbitMQConstant.DEAD_DIRECT_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.DEAD_DIRECT_EXCHANGE,RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
}
@Bean
public Queue createTtlMessageHeaderQueue(){
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange",RabbitMQConstant.DEAD_DIRECT_EXCHANGE);
map.put("x-dead-letter-routing-key",RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
//return new Queue(RabbitMQConstant.TTL_MESSAGE_HEADER_QUEUE, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.TTL_MESSAGE_HEADER_QUEUE)
.deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.build();
}
@Bean
public DirectExchange createTtlMessageHeaderExchange(){
// return new DirectExchange(RabbitMQConstant.TTL_MESSAGE_HEADER_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.TTL_MESSAGE_HEADER_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindTtlMessageHeader(@Qualifier("createTtlMessageHeaderExchange") DirectExchange exchange, @Qualifier("createTtlMessageHeaderQueue") Queue queue){
// return new Binding(RabbitMQConstant.TTL_MESSAGE_HEADER_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.TTL_MESSAGE_HEADER_EXCHANGE,RabbitMQConstant.TTL_MESSAGE_HEADER_ROUTING_KEY,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.TTL_MESSAGE_HEADER_ROUTING_KEY);
}
@Bean
public Queue createTtlPropertyQueue1(){
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange",RabbitMQConstant.DEAD_DIRECT_EXCHANGE);
map.put("x-dead-letter-routing-key",RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
map.put("x-message-ttl",15*1000);
//return new Queue(RabbitMQConstant.TTL_PROPERTY_QUEUE1, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.TTL_PROPERTY_QUEUE1)
// .deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
// .deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.withArguments(map)
.build();
}
@Bean
public Queue createTtlPropertyQueue2(){
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange",RabbitMQConstant.DEAD_DIRECT_EXCHANGE);
map.put("x-dead-letter-routing-key",RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
map.put("x-message-ttl",5*1000);
//return new Queue(RabbitMQConstant.TTL_PROPERTY_QUEUE2, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.TTL_PROPERTY_QUEUE2)
.deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.ttl(5*1000)
.build();
}
@Bean
public DirectExchange createTtlPropertyExchange(){
// return new DirectExchange(RabbitMQConstant.TTL_PROPERTY_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.TTL_PROPERTY_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindTtlProperty1(@Qualifier("createTtlPropertyExchange") DirectExchange exchange, @Qualifier("createTtlPropertyQueue1") Queue queue){
// return new Binding(RabbitMQConstant.TTL_PROPERTY_QUEUE1, Binding.DestinationType.QUEUE,RabbitMQConstant.TTL_PROPERTY_EXCHANGE,RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY1,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY1);
}
@Bean
public Binding bindTtlProperty2(@Qualifier("createTtlPropertyExchange") DirectExchange exchange, @Qualifier("createTtlPropertyQueue2") Queue queue){
// return new Binding(RabbitMQConstant.TTL_PROPERTY_QUEUE2, Binding.DestinationType.QUEUE,RabbitMQConstant.TTL_PROPERTY_EXCHANGE,RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY2,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY2);
}
@Bean
public Queue createMaxLengthQueue(){
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange",RabbitMQConstant.DEAD_DIRECT_EXCHANGE);
map.put("x-dead-letter-routing-key",RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
map.put("x-max-length",2);
//return new Queue(RabbitMQConstant.MAX_LENGTH_QUEUE, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.MAX_LENGTH_QUEUE)
.deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.maxLength(2)
.build();
}
@Bean
public DirectExchange creatMaxLengthExchange(){
// return new DirectExchange(RabbitMQConstant.MAX_LENGTH_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.MAX_LENGTH_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindMaxLength(@Qualifier("creatMaxLengthExchange") DirectExchange exchange, @Qualifier("createMaxLengthQueue") Queue queue){
// return new Binding(RabbitMQConstant.MAX_LENGTH_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.MAX_LENGTH_EXCHANGE,RabbitMQConstant.MAX_LENGTH_ROUTING_KEY,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.MAX_LENGTH_ROUTING_KEY);
}
@Bean
public Queue createRejectConsumeQueue(){
Map<String,Object> map = new HashMap<>();
map.put("x-dead-letter-exchange",RabbitMQConstant.DEAD_DIRECT_EXCHANGE);
map.put("x-dead-letter-routing-key",RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY);
//return new Queue(RabbitMQConstant.REJECT_CONSUME_QUEUE, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.REJECT_CONSUME_QUEUE)
.deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.build();
}
@Bean
public DirectExchange creatRejectConsumeExchange(){
// return new DirectExchange(RabbitMQConstant.REJECT_CONSUME_EXCHANGE,true,false,null);
return ExchangeBuilder.directExchange(RabbitMQConstant.REJECT_CONSUME_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindRejectConsume(@Qualifier("creatRejectConsumeExchange") DirectExchange exchange, @Qualifier("createRejectConsumeQueue") Queue queue){
// return new Binding(RabbitMQConstant.REJECT_CONSUME_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.REJECT_CONSUME_EXCHANGE,RabbitMQConstant.REJECT_CONSUME_ROUTING_KEY,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.REJECT_CONSUME_ROUTING_KEY);
}
@Bean
public Queue createExpiredQueue(){
HashMap<String, Object> map = new HashMap<String, Object>() {
{
put("x-expires", 60*1000);
}
};
return QueueBuilder.durable(RabbitMQConstant.EXPIRED_QUEUE)
.deadLetterExchange(RabbitMQConstant.DEAD_DIRECT_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_DIRECT_ROUTING_KEY)
.withArguments(map)
.build();
}
@Bean
public DirectExchange creatExpiredExchange(){
return ExchangeBuilder.directExchange(RabbitMQConstant.EXPIRED_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindExpiredOnQueueTestDeadQueue(@Qualifier("creatExpiredExchange")DirectExchange exchange, @Qualifier("createExpiredQueue")Queue queue){
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.EXPIRED_ROUTING_KEY);
}
@Bean
public Queue createDelayQueue(){
//return new Queue(RabbitMQConstant.DELAYED_QUEUE, true, false, false, map);
return QueueBuilder.durable(RabbitMQConstant.DELAYED_QUEUE)
.build();
}
@Bean
public CustomExchange creatDelayExchange(){
HashMap<String, Object> map = new HashMap<String, Object>() {
{
put("x-delayed-type", "direct");
}//与队列的通信方式
};
return new CustomExchange(RabbitMQConstant.DELAYED_EXCHANGE,"x-delayed-message",true,false,map);
}
@Bean
public Binding bindDelay(@Qualifier("creatDelayExchange") CustomExchange exchange, @Qualifier("createDelayQueue") Queue queue){
// return new Binding(RabbitMQConstant.DELAYED_QUEUE, Binding.DestinationType.QUEUE,RabbitMQConstant.DELAYED_EXCHANGE,RabbitMQConstant.DELAYED_ROUTING_KEY,null);
return BindingBuilder.bind(queue)
.to(exchange)
.with(RabbitMQConstant.DELAYED_ROUTING_KEY)
.noargs();
}
}
package com.ssy.www.controller;
import com.ssy.www.constant.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;
/**
* 生产者发送消息给MQ服务器采用异步的形式发送, 让返回结果直接优先打印, 无需等待
*/
@RestController
@RequestMapping("/rabbit")
@Slf4j
public class RabbitMQProductController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/simple")
public String simple(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg = "简单模式消息".getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend("",RabbitMQConstant.SIMPLE_MODE_QUEUE,msg,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "简单模式";
}
@GetMapping("/work")
public String work(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
for (int i = 1; i <= 20; i++) {
byte[] msg = ("工作模式消息"+i).getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend("",RabbitMQConstant.WORK_MODE_QUEUE,msg,processor,(CorrelationData) null);
}
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "工作模式";
}
@GetMapping("/publishScribe")
public String publishScribe(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg = "发布订阅模式消息".getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.PUBLISH_SCRIBE_MODE_EXCHANGE,"",msg,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "发布订阅模式";
}
@GetMapping("/route")
public String route(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg1 = "路由模式1号消息".getBytes(StandardCharsets.UTF_8);
byte[] msg2 = "路由模式2号消息".getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY1,msg1,processor,(CorrelationData) null);
rabbitTemplate.convertAndSend(RabbitMQConstant.ROUTE_MODE_EXCHANGE,RabbitMQConstant.ROUTE_MODE_ROUTING_KEY2,msg2,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "路由模式";
}
@GetMapping("/topic")
public String topic(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg1 = "主题模式消息*样例".getBytes(StandardCharsets.UTF_8);
byte[] msg2 = "主题模式消息#样例".getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.TOPIC_MODE_EXCHANGE,"com.singleton.www",msg1,processor,(CorrelationData) null);
rabbitTemplate.convertAndSend(RabbitMQConstant.TOPIC_MODE_EXCHANGE,"com.ssy.www.multi",msg2,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "主题模式";
}
@GetMapping("/headers")
public String headers(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg1 = "头类型交换机匹配模式为any的消息".getBytes(StandardCharsets.UTF_8);
byte[] msg2 = "头类型交换机匹配模式为all的消息".getBytes(StandardCharsets.UTF_8);
MessageProperties props1 = new MessageProperties();
props1.setContentEncoding("UTF-8");
props1.setHeader("username","ssy");
props1.setHeader("score","520");
MessageProperties props2 = new MessageProperties();
props2.setContentEncoding("UTF-8");
props2.setHeader("id","8023");
props2.setHeader("auth","root");
props2.setHeader("ip","127.0.0.0");
Message message1 = new Message(msg1,props1);
Message message2 = new Message(msg2,props2);
rabbitTemplate.convertAndSend(RabbitMQConstant.HEADERS_EXCHANGE,"",message1,processor,(CorrelationData) null);
rabbitTemplate.convertAndSend(RabbitMQConstant.HEADERS_EXCHANGE,"",message2,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "头类型交换机匹配模式";
}
@GetMapping("/expire/{time}")
public String expire(@PathVariable("time") Integer time){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration(String.valueOf(time*1000));
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg = ("消息头设置过期时间为:"+ time +"秒").getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.TTL_MESSAGE_HEADER_EXCHANGE,RabbitMQConstant.TTL_MESSAGE_HEADER_ROUTING_KEY,msg,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "死信队列测试消息头设置过期时间为:"+time+"秒";
}
@GetMapping("/ttl")
public String expire(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg1 = "队列ttl为15秒消息测试".getBytes(StandardCharsets.UTF_8);
byte[] msg2 = "队列ttl为5秒消息测试".getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.TTL_PROPERTY_EXCHANGE,RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY1,msg1,processor,(CorrelationData) null);
rabbitTemplate.convertAndSend(RabbitMQConstant.TTL_PROPERTY_EXCHANGE,RabbitMQConstant.TTL_PROPERTY_ROUTING_KEY2,msg2,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "死信队列测试队列设置ttl";
}
@GetMapping("/maxLength")
public String maxLength(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
for (int i = 1; i <= 5; i++) {
byte[] msg = ("序号"+i).getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.MAX_LENGTH_EXCHANGE,RabbitMQConstant.MAX_LENGTH_ROUTING_KEY,msg,processor,(CorrelationData) null);
}
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "死信队列测试队列达到最大长度";
}
@GetMapping("/reject")
public String reject(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
for (int i = 1; i <= 5; i++) {
byte[] msg = ("消息"+i).getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.REJECT_CONSUME_EXCHANGE,RabbitMQConstant.REJECT_CONSUME_ROUTING_KEY,msg,processor,(CorrelationData) null);
}
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "死信队列测试消费者拒绝应答";
}
@GetMapping("/expireQueue/{time}")
public String expireQueue(@PathVariable("time") Integer time){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration(String.valueOf(time*1000));
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg = ("通过过期队列设置消息头设置过期时间为:"+ time +"秒测试死信队列").getBytes(StandardCharsets.UTF_8);
rabbitTemplate.convertAndSend(RabbitMQConstant.EXPIRED_EXCHANGE,RabbitMQConstant.EXPIRED_ROUTING_KEY,msg,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "过期队列测试死信队列:队列设置过期时间,(队列存在,消息的过期时间在队列删除之前)消息进入死信队列的";
}
@GetMapping("/delay")
public String delay(){
FutureTask<Void> task = new FutureTask(()->{
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
byte[] msg1 = "基于插件延迟时间为15秒".getBytes(StandardCharsets.UTF_8);
byte[] msg2 = "基于插件延迟时间为5秒".getBytes(StandardCharsets.UTF_8);
MessageProperties props1 = new MessageProperties();
props1.setDelay(15*1000);
MessageProperties props2 = new MessageProperties();
props2.setDelay(5*1000);
Message message1 = new Message(msg1,props1);
Message message2 = new Message(msg2,props2);
rabbitTemplate.convertAndSend(RabbitMQConstant.DELAYED_EXCHANGE,RabbitMQConstant.DELAYED_ROUTING_KEY,message1,processor,(CorrelationData) null);
rabbitTemplate.convertAndSend(RabbitMQConstant.DELAYED_EXCHANGE,RabbitMQConstant.DELAYED_ROUTING_KEY,message2,processor,(CorrelationData) null);
return null;
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
return "延迟队列插件样例";
}
}
消费者模块
yml文件
server:
port: 8002
servlet:
context-path: /ssy
spring:
application:
name: rabbit-spring-consumer
rabbitmq:
host: 127.0.0.1
port: 5672
username: ssy
password: ssy
virtual-host: ssy
connection-timeout: 10000 # 10秒
listener:
simple:
prefetch: 1 #能者多劳 设置不同的睡眠时间进行测试
java代码如下:
package com.ssy.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class,args);
}
}
package com.ssy.www.constant;
import java.util.HashMap;
import java.util.Map;
public interface RabbitMQConstant {
// 简单模式
String SIMPLE_MODE_QUEUE = "spring.simple_queue";
// 工作模式
String WORK_MODE_QUEUE = "spring.work.queue";
// fanout交换机
String PUBLISH_SCRIBE_MODE_QUEUE = "spring.fanout.queue";
String PUBLISH_SCRIBE_MODE_EXCHANGE = "spring.fanout.exchange";
// direct交换机
String ROUTE_MODE_QUEUE1 = "spring.direct.queue1";
String ROUTE_MODE_QUEUE2 = "spring.direct.queue2";
String ROUTE_MODE_EXCHANGE = "spring.direct.exchange";
String ROUTE_MODE_ROUTING_KEY1 = "spring.direct.routing.key1";
String ROUTE_MODE_ROUTING_KEY2 = "spring.direct.routing.key2";
// topic交换机
String TOPIC_MODE_QUEUE1 = "spring.topic.queue1";
String TOPIC_MODE_QUEUE2 = "spring.topic.queue2";
String TOPIC_MODE_EXCHANGE = "spring.topic.exchange";
String TOPIC_MODE_ROUTING_KEY1 = "com.*.www";
String TOPIC_MODE_ROUTING_KEY2 = "com.ssy.www.#";
// headers交换机
String HEADERS_QUEUE_ANY = "spring.topic.queue.any";
String HEADERS_QUEUE_ALL = "spring.topic.queue.all";
String HEADERS_EXCHANGE = "spring.headers.exchange";
// 死信队列
String DEAD_DIRECT_QUEUE = "spring.dead.queue";
String DEAD_DIRECT_EXCHANGE = "spring.dead.exchange";
String DEAD_DIRECT_ROUTING_KEY = "spring.dead.routing.key";
// 消息设置ttl(消息设置过期时间)
String TTL_MESSAGE_HEADER_QUEUE = "ttl.spring.message.header.queue";
String TTL_MESSAGE_HEADER_EXCHANGE = "ttl.spring.message.header.exchange";
String TTL_MESSAGE_HEADER_ROUTING_KEY = "ttl.spring.message.header.routing.key";
// 消息设置ttl(队列设置x-message-ttl)
String TTL_PROPERTY_QUEUE1 = "spring.ttl.property.queue1";
String TTL_PROPERTY_QUEUE2 = "spring.ttl.property.queue2";
String TTL_PROPERTY_EXCHANGE = "spring.ttl.property.exchange";
String TTL_PROPERTY_ROUTING_KEY1 = "spring.ttl.property.routing.key1";
String TTL_PROPERTY_ROUTING_KEY2 = "spring.ttl.property.routing.key2";
// 消息条数达到队列最大长度
String MAX_LENGTH_QUEUE = "spring.max.length.queue";
String MAX_LENGTH_EXCHANGE = "spring.max.length.exchange";
String MAX_LENGTH_ROUTING_KEY = "spring.max.length.routing.key";
// 消费者拒绝消费
String REJECT_CONSUME_QUEUE = "spring.reject.consume.queue";
String REJECT_CONSUME_EXCHANGE = "spring.reject.consume.exchange";
String REJECT_CONSUME_ROUTING_KEY = "spring.reject.consume.routing.key";
// 过期队列
String EXPIRED_QUEUE = "spring.expired.queue";
String EXPIRED_EXCHANGE = "spring.expired.exchange";
String EXPIRED_ROUTING_KEY = "spring.expired.routing.key";
// 延迟队列
String DELAYED_QUEUE = "spring.delayed.queue";
String DELAYED_EXCHANGE = "spring.delayed.exchange";
String DELAYED_ROUTING_KEY = "spring.delayed.routing.key";
}
package com.ssy.www.listener;
import com.rabbitmq.client.Channel;
import com.ssy.www.constant.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class ConsumeListener {
@RabbitListener(queues = {RabbitMQConstant.SIMPLE_MODE_QUEUE})
public void simple(Message msg){
log.info("简单模式>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.WORK_MODE_QUEUE})
public void work1(Message msg){
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("工作模式>>>消费1号:交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.WORK_MODE_QUEUE})
public void work2(Message msg){
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("工作模式>>>消费2号:交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.PUBLISH_SCRIBE_MODE_QUEUE})
public void publishScribe(Message msg){
log.info("发布订阅模式>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.ROUTE_MODE_QUEUE1,RabbitMQConstant.ROUTE_MODE_QUEUE2})
public void route(Message msg){
log.info("路由模式>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.TOPIC_MODE_QUEUE1,RabbitMQConstant.TOPIC_MODE_QUEUE2})
public void topic(Message msg){
log.info("主题模式>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.HEADERS_QUEUE_ANY,RabbitMQConstant.HEADERS_QUEUE_ALL})
public void headers(Message msg){
log.info("headers交换机>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.DEAD_DIRECT_QUEUE})
public void dead(Message msg){
log.info("死信队列>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
}
@RabbitListener(queues = {RabbitMQConstant.REJECT_CONSUME_QUEUE},ackMode = "MANUAL")
public void reject(Message msg, Channel channel){
try{
if(new String(msg.getBody(), StandardCharsets.UTF_8).equals("消息3")){
log.warn("消费者拒绝消费消息:{},开始进入死信队列",new String(msg.getBody(), StandardCharsets.UTF_8));
channel.basicReject(msg.getMessageProperties().getDeliveryTag(),false);
} else {
log.info("正常消费>>>交换机:{},路由键:{},队列:{},消息:{}",
msg.getMessageProperties().getReceivedExchange(),
msg.getMessageProperties().getReceivedRoutingKey(),
msg.getMessageProperties().getConsumerQueue(),
new String(msg.getBody(), StandardCharsets.UTF_8));
channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
注意:
@RabbitHandler 和 @RabbitListener 是Spring AMQP(特别是针对RabbitMQ)中常用的两个注解,它们在消息处理中扮演着不同的角色
- 当RabbitListener注解在方法上时,对应的方式就是Rabbit消息的监听器
- 当RabbitListener注解在类上时,和RabbitHandle注解配合使用,可以实现不同类型的消息的分发,类中被RabbitHandle注解的方法就是Rabbit消息的监听器
@Payload 与 @Headers
- 使用 @Payload 和 @Headers 注解可以消息中的 body 与 headers 信息
- 也可以使用@Header获取单个Header属性
SpringBoot实现可靠性消息投递
ConfirmCallback
- 消息抵达交换机通知生产者
回调监听消息接口ConfirmCallback、返回值确认接口
Confirm消息确认机制: 生产者向MQ投递完消息后,要求MQ返回一个应答,生产者异步接收该应答,用来确定该消息是否正常的发送到了Broker, 从而保障消息的可靠性投递。
Confirm消息确认机制的实现:
confirm-type有none、correlated、simple这三种类型
- none:表示禁用发布确认模式,默认值,使用此模式之后,不管消息有没有发送到Broker都不会触发 ConfirmCallback回调。
- correlated:表示消息成功到达Broker后触发ConfirmCalllBack回调(异步确认)
- simple:simple模式下如果消息成功到达Broker后一样会触发(同步确认)
spring:
rabbitmq:
publisher-confirm-type: correlated
开启ConfirmCallback确认机制:实现ConfirmCallback接口,重写confirm方法,其中重写的方法包含三个参数:
1、correlationData:发送消息时设置的correlationData。由于confirm消息是异步监听的,因此需要在发送消息时传递一个correlationData,从而在返回confirm消息时判断其属于哪个消息,所以correlationData通常设置为消息的唯一ID
2、ack:broker返回的应答,如果broker成功接收消息,则返回true代表接收成功,如果因为各种原因没有成功接收(如消息队列满了),则返回false。这里要注意,由于各种原因(如网络波动),生产端可能并没有收到confirm消息,因此不能将后续的补偿处理仅仅寄希望于在else内完成,else内做的补偿仅仅是在生产端收到confirm消息后nack的情况
3、cause: 如果没有被成功接收,则返回原因
ReturnCallback
- 消息未路由到队列通知生产者(也可以使用备份交换机,那么即使未路由到队列也不会通知生产者,而是直接进入备份交换机路由给备份队列或者报警队列)
当消息不可路由的时候,mq会触发returncallback接口的回调方法,把不可路由的消息回调回来,但是这有个问题,就是消息虽然回调过来了,但是并没有消费者去把不可路由的消息给消费掉,所以这个时候就要加一个备用队列和一个报警队列,报警队列的作用是用来通知管理员,有什么消息被回退了…然后备用队列是把消息给保存起来,需要的时候就从备用队列中取数据出来使用
注意:当设置了备用队列的时候,returncallback接口的回调方法将不会被触发,但是当消息不可路由,而且备用队列也不能使用的时候,才会触发returncallback接口的回调方法,也就是说,触发回调方法在最终条件是消息无法被任何一个队列接受,在mq丢弃前才会触发回调方法
回调监听消息接口ReturnCallback,没有路由到队列返回
Return消息返回机制:该机制用于处理一些不可路由的消息。如果生产在发送消息时,发现当前的exchange通过指定的路由key找不到队列时,生产者可以开启该模式来监听这种不可达的消息,以进行后续操作或者手动处理。(如果不开启的话,broker会自动删除该消息)
这里要注意的是,只要消息到达了MQ就换返回Confirm消息,接下来MQ再去判断能不能找到路由方式,找不到再返回Return消息。
开启ReturnCallback回调,实现RabbitTemplate.ReturnCallback即可,重写returnedMessage方法
重写的方法有五个参数:
1、message:消息以及消息属性
2、replyCode:返回状态码
3、replyTest:返回文本
4、exchange:交换机
5、routingKey:路由键
spring:
rabbitmq:
publisher-returns: true
mandatory
注意,mandatory一定要设置为true,否则找不到路由规则的消息会被broker直接抛弃。
spring:
rabbitmq:
template:
mandatory: true # 不是重新实例化的RabbitmqTemplate的配置(通过注入的方式配置),那么会生效,重新实例化的则不会生效
mandatory、publisher-confirms、publisher-return属性区别
rabbitmq客户端发送消息首先发送的交换器exchange,然后通过路由键routingKey和bindingKey比较判定需要将消息发送到那个队列queue上;在这个过程有两个地方消息可能丢失,第一消息发送到交换器exchange的过程,第二消息从交换器exchange发送到队列queue的过程。
- publiser-confirm模式可以确保生产者到交换器exchange消息有没有发送成功
#设置此属性配置可以确保消息成功发送到交换器
spring.rabbitmq.publisher-confirms=true
- publisher-return模式可以在消息没有被路由到指定的queue时将消息返回,而不是丢弃
#可以确保消息在未被队列接收时返回
spring.rabbitmq.publisher-returns=true
- mandatory指定消息在没有被队列接收时是否强行退回还是直接丢弃
#在使用上面的属性配置时通常会和mandatory属性配合一起使用
spring.rabbitmq.template.mandatory=true
此时就要研究源码查看RabbitAutoConfiguration自动化配置类进行深入分析即可(注意,不同的boot版本可能对应的代码略有差异)
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
PropertyMapper map = PropertyMapper.get();
RabbitTemplate template = new RabbitTemplate(connectionFactory);
MessageConverter messageConverter = (MessageConverter)this.messageConverter.getIfUnique();
if (messageConverter != null) {
template.setMessageConverter(messageConverter);
}
//设置rabbitmq处理未被queue接收消息的模式
template.setMandatory(this.determineMandatoryFlag());
Template properties = this.properties.getTemplate();
if (properties.getRetry().isEnabled()) {
template.setRetryTemplate((new RetryTemplateFactory((List)this.retryTemplateCustomizers.orderedStream().collect(Collectors.toList()))).createRetryTemplate(properties.getRetry(), Target.SENDER));
}
properties.getClass();
map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis).to(template::setReceiveTimeout);
properties.getClass();
map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis).to(template::setReplyTimeout);
properties.getClass();
map.from(properties::getExchange).to(template::setExchange);
properties.getClass();
map.from(properties::getRoutingKey).to(template::setRoutingKey);
properties.getClass();
map.from(properties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
return template;
}
//判定是否将未找到合适queue的消息退回
private boolean determineMandatoryFlag() {
/**
* 获取spring.rabbitmq.template.mandatory属性配置;
* 这里面会有三种可能,为null、false、true
* 而只有在mandatory为null时才会读取publisher-return属性值
**/
Boolean mandatory = this.properties.getTemplate().getMandatory();
return mandatory != null ? mandatory : this.properties.isPublisherReturns();
}
消息序列化
如果不指定消息的序列化机制,那么使用的是SimpleMessageConverter使用的是转化为二进制字节数组,接受消息的时候需要准换回string,再进一步进行操作。RabbitMQ 本身不直接处理消息内容的序列化,它主要负责消息的路由、存储和传递。当发送或接收消息时,客户端库(如:Java中的RabbitTemplate或SpringAMQP 框架)会根据内部配置或默认设置来决定如何对消息体进行序列和反序列化。Java 自带的 Serializable接口和ObjectOutputStream/ObjectInputStream 可以将对象转换为字节数组进行传输。在 Java Spring AMQP 中,默认的消息序列化方式就是 Java 原生序列化。
进行消息序列化的原因:
在使用 RabbitMQ 时,消息需要在生产者和消费者之间进行传递。由于网络通信只能传输二进制数据,因此需要对消息进行 序列化(将对象转换为二进制数据)和反序列化(将二进制数据转换回对象)。这样才能实现生产者与消费者之间的无缝通信
消息序列化的目标是:将对象转换为字节流。以便于在网络上进行传输。在选择序列化方式时,可以考虑:
- 性能: 序列化和反序列化的效率直接 影响消息传输的速度和延迟
- 空间开销: 序列化后的字节流大小会 影响网络带宽的利用 和 存储空间的占用
- 可读性: 序列化后的字节流是否 易于解析和理解,方便调试和维护
- 兼容性: 序列化方式是否 支持不同的编程语言 和 版本之间的交互
在SpringAMQP 中,可以配置 MessageConverter来替换默认的序列化器,例如使用Jackson库提供的 Jackson2JsonMessageConverter 将消息转换为JSON格式。
SpringBoot可以通过编写配置实现:
//注意MessageConverter(org.springframework.amqp.support.converter.MessageConverter
//)不要导错包了
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
注意:
如果对象中使用了 LocalDateTime,在反序列化的时候会报错:InvalidDefinitionException: Cannot construct instance of java.time.LocalDateTime (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
解决办法,引入依赖即可:
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTime;
自定义消息监听器
简单消息监听容器:SimpleMessageListenerContainer
这个类非常的强大,可以对他进行很多设置,对于消费者的配置项,这个类都可以满足
1、监听队列(多个队列)、自动启动、自动声明功能
2、设置事务特性、事务管理器、事务属性、事务容量(并发)、是否开启事务、回滚消息等
3、设置消费者数量、最小最大数量、批量消费
4、设置消息确认和自动确认模式、是否重回队列、异常捕获handler函数
5、设置消费者标签生成策略、是否独占模式、消费者属性等
6、设置具体的监听器、消息转换器等等
SimpleMessageListenerContainer可以进行动态设置,比如在运行中的应用可以动态的修改其消费者数量的大小、接收消息的模式等。很多基于RabbitMQ的自制定化后端管控台在进行动态设置的时候,也是根据这一特性去实现的
关于消息转换器,MessageConverter消息转换器,在进行发送消息的时候,正常情况下消息体为二进制的数据方式进行传输,如果希望内部进行转换,或者指定自定义的转换器,就需要用到MessageConverter。消息处理方法参数是由 MessageConverter 转化,若使用自定义 MessageConverter 则需要在 RabbitListenerContainerFactory 实例中去设置(默认 Spring 使用的实现是 SimpleRabbitListenerContainerFactory),消息的 content_type 属性表示消息 body 数据以什么数据格式存储,接收消息除了使用 Message 对象接收消息(包含消息属性等信息)之外,还可直接使用对应类型接收消息 body 内容,但若方法参数类型不正确会抛异常。
- application/octet-stream:二进制字节数组存储,使用 byte[]
- application/x-java-serialized-object:java 对象序列化格式存储,使用 Object、相应类型(反序列化时类型应该同包同名,否者会抛出找不到类异常)
- text/plain:文本数据类型存储,使用 String
- application/json:JSON 格式,使用 Object、相应类型
自定义常用转换器:MessageConverter,一般来讲都需要实现这个接口
- 重写下面两个方法:
-
- toMessage:java对象转换为Message
-
- fromMessage:Message对象转换为java对象
- MessageConverter消息转换器:
-
- Json转换器:Jackson2JsonMessageConverter:可以进行java对象的转换功能;
-
- DefaultJackson2JavaTypeMapper映射器:可以进行java对象的映射关系;
-
- 自定义二进制转换器:比如图片类型、PDF、PPT、流媒体等
消息监听适配器:MessageListenerAdapter
通过MessageListenerAdapter的代码可以看出如下核心属性:
- defaultListenerMethod默认监听方法名称:用于设置监听方法名称
- Delegate委托对象:实际真实的委托对象,用于处理消息、
- queueOrTagToMethodName: 队列标识与方法名称组成的集合
- 可以进行队列与方法名称的匹配;
- 队列和方法名称绑定,即指定队列里的消息会被绑定的方法所接收处理
自定义containerFactory
对于@RabbitListener,自定义属性containerFactory进行灵活处理。
package com.ssy.www.config;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.ConsumerTagStrategy;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.ErrorHandler;
import java.util.UUID;
@Configuration
public class RabbitListenerContainerFactoryConfig {
@Autowired
private MessageConverter messageConverter;
@Bean
public SimpleRabbitListenerContainerFactory customRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory,
ErrorHandler errorHandler
){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// 监听器容器连接工厂
factory.setConnectionFactory(connectionFactory);
// 并发消费者数目,默认为1
factory.setConcurrentConsumers(2);
// 并发组大消费者数目,默认为1
factory.setMaxConcurrentConsumers(5);
// 拒绝未确认的消息并把它重新放入队列,默认为true
factory.setDefaultRequeueRejected(false);
// 容器启动时是否自动启动,默认为true
factory.setAutoStartup(true);
// 消息确认模式,默认为auto
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 每个消费在一次请求中预获取的消息数目,默认为1
factory.setPrefetchCount(4);
// 从队列中接收消息的超时时间,默认为 0,表示没有超时限制
factory.setReceiveTimeout(5*1000L);
// 与容器一起使用的事务管理器。默认情况下,容器不会使用事务
factory.setTransactionManager(null);
// 消息转换器,用于将接收到的消息转换为 Java 对象或将 Java 对象转换为消息
factory.setMessageConverter(messageConverter);
// 用于异步消息处理的线程池。默认情况下,容器使用一个简单的 SimpleAsyncTaskExecutor
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
// 重试失败的消息之前等待的时间,默认为 5000 毫秒
factory.setRecoveryInterval(10*1000L);
// 如果消息处理器尝试监听不存在的队列,是否抛出异常。默认为 true
factory.setMissingQueuesFatal(true);
factory.setErrorHandler(errorHandler);
// 消费端的标签策略
factory.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString().replaceAll("-","");
}
});
configurer.configure(factory,connectionFactory);
return factory;
}
}
队列监听依旧使用@RabbitListener(注解在类上),配合@RabbitHandler(注解在方法上)使用,监听同意队列不同的参数类型
package com.ssy.www.listener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.rabbitmq.client.Channel;
import com.ssy.www.constant.RabbitMQConstant;
import com.ssy.www.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RabbitListener(queues = {RabbitMQConstant.OTHER_QUEUE},containerFactory = "customRabbitListenerContainerFactory")
public class CustomRabbitMessageListener {
/**
* [
* payload =
* {
* auth = root,
* createTime = 2024 - 04 - 09 T15: 51: 47.487,
* id = 4,
* password = 10086,
* token = China Mobile,
* username = 中国移动
* },
* headers =
* {
* amqp_receivedDeliveryMode = PERSISTENT,
* amqp_receivedExchange = spring.custom.exchange,
* amqp_deliveryTag = 4,
* amqp_consumerQueue = spring.custom.other.queue,
* amqp_redelivered = true,
* amqp_receivedRoutingKey = spring.custom.other.routing.key,
* amqp_contentEncoding = UTF - 8,
* spring_listener_return_correlation = 08 a66e84 - ddc2 - 4841 - b96a - aaf647594245,
* spring_returned_message_correlation = 4,
* amqp_messageId = 4,
* id = bf923aad - 1 d33 - e162 - ab5a - 6486620e10 c4,
* amqp_consumerTag = spring.custom.other.queue_de74db8c21ca4d2bb3b7425db83521b0,
* amqp_lastInBatch = false,
* contentType = application / json,
* timestamp = 1712649184175
* }
* ],
* failedMessage =
* GenericMessage
* [
* payload =
* {
* auth = root,
* createTime = 2024 - 04 - 09 T15: 51: 47.487,
* id = 4,
* password = 10086,
* token = China Mobile,
* username = 中国移动
* },
* headers =
* {
* amqp_receivedDeliveryMode = PERSISTENT,
* amqp_receivedExchange = spring.custom.exchange,
* amqp_deliveryTag = 4,
* amqp_consumerQueue = spring.custom.other.queue,
* amqp_redelivered = true,
* amqp_receivedRoutingKey = spring.custom.other.routing.key,
* amqp_contentEncoding = UTF - 8,
* spring_listener_return_correlation = 08 a66e84 - ddc2 - 4841 - b96a - aaf647594245,
* spring_returned_message_correlation = 4,
* amqp_messageId = 4,
* id = bf923aad - 1 d33 - e162 - ab5a - 6486620e10 c4,
* amqp_consumerTag = spring.custom.other.queue_de74db8c21ca4d2bb3b7425db83521b0,
* amqp_lastInBatch = false,
* contentType = application / json,
* timestamp = 1712649184175
* }
* ]
*/
//@Payload是消费者接受生产者发送的队列消息,将队列中的json字符串变成对象的注解
//@Payload 与 @Headers
//使用 @Payload 和 @Headers 注解可以获取消息中的 body 与 headers 信息
//也可以获取单个 Header 属性,@Header
//@RabbitHandler(isDefault = true)
@RabbitHandler
public void processOtherQueueMessage(User user, Message message, Channel channel) {
try {
if(user.getId()%2==0){
log.info(">>>>>拒绝消费:{},单一信息进入死信队列",user);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
} else {
log.info("收到自定义工厂factory的单一消息:消费者->{},消息->{}",message.getMessageProperties().getConsumerTag(),user);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception e){
log.error("【单一】自定义工厂应答出错:{}",e.getMessage());
}
}
@RabbitHandler
public void processOtherQueueMessage(List<User> users, Message message, Channel channel){
if(users==null || users.size()==0){
throw new IllegalArgumentException("不存在消息,无法进行消费");
}
try{
if(new Integer(message.getMessageProperties().getMessageId())%2==1){
log.info(">>>>>拒绝消费:{},集合信息进入死信队列",users);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
} else {
log.info("收到自定义工厂factory的集合消息:消费者->{},消息->{}",message.getMessageProperties().getConsumerTag(),users);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception e){
log.error("【集合】自定义工厂应答出错:{}",e.getMessage());
}
}
}
完整代码
生产端
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
</dependencies>
application配置文件
server:
port: 9101
servlet:
context-path: /ssy
spring:
application:
name: rabbit-custom-producer-demo
rabbitmq:
host: 127.0.0.1
port: 5672
username: root
password: root
virtual-host: root
connection-timeout: 10000 # 10秒
publisher-confirm-type: correlated
publisher-returns: true
template:
mandatory: true
启动类
package com.ssy.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomProductApplication {
public static void main(String[] args) {
SpringApplication.run(CustomProductApplication.class,args);
}
}
常量类
package com.ssy.www.constant;
public interface RabbitMQConstant {
String CUSTOM_QUEUE = "spring.custom.queue";
String CUSTOM_EXCHANGE = "spring.custom.exchange";
String CUSTOM_ROUTING_KEY = "spring.custom.routing.key";
String OTHER_QUEUE = "spring.custom.other.queue";
String CUSTOM_OTHER_ROUTING_KEY = "spring.custom.other.routing.key";
String DEAD_QUEUE = "spring.custom.dead.queue";
String DEAD_EXCHANGE = "spring.custom.dead.exchange";
String DEAD_ROUTING_KEY = "spring.custom.dead.routing.key";
}
实体类
package com.ssy.www.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Slf4j
@Data
@Accessors(chain = true)
public class User{
private String username;
private String password;
private String auth;
private String token;
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
private Integer id;
}
配置类
package com.ssy.www.config;
import com.ssy.www.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.DefaultClassMapper;
import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
log.info("数据ID为{}交换机{}成功接受到消息:{}",
correlationData.getId(),
correlationData.getReturnedMessage().getMessageProperties().getReceivedExchange(),
new String(correlationData.getReturnedMessage().getBody(), StandardCharsets.UTF_8));
} else {
log.error("编号{}的交换机{}没有接收到消息,请进行重新投递或人工处理或持久化到数据库,失败原因:{}",
correlationData.getId(),
correlationData.getReturnedMessage().getMessageProperties().getReceivedExchange(),
cause);
}
}
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("队列没有收到消息,请进行补偿处理或追加备份交换机和队列,失败原因:{}",replyText);
}
/**
* DefaultJackson2JavaTypeMapper & Jackson2JsonMessageConverter 支持java对象多映射转换
*/
@Bean
public MessageConverter rabbitMessageConverter(DefaultClassMapper defaultClassMapper){
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
// 信任所有的包,否则会报错
javaTypeMapper.setTrustedPackages("*");
Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
messageConverter.setJavaTypeMapper(javaTypeMapper);
//解决RabbitMQ默认将自定义的消息类转换为LinkedHashMap
//No method found for class java.util.LinkedHashMap
messageConverter.setClassMapper(defaultClassMapper);
return messageConverter;
}
@Bean
public DefaultClassMapper classMapper(){
DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
Map<String,Class<?>> map = new HashMap<>();
map.put("com.ssy.www.entity.User", User.class);
defaultClassMapper.setIdClassMapping(map);
return defaultClassMapper;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory factory,MessageConverter messageConverter){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(factory);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setEncoding("UTF-8");
rabbitTemplate.setTaskExecutor(new SimpleAsyncTaskExecutor());
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
}
package com.ssy.www.config;
import com.ssy.www.constant.RabbitMQConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class RabbitMQComponentConfig {
@Bean
public Queue initCustomQueue(){
return QueueBuilder
.durable(RabbitMQConstant.CUSTOM_QUEUE)
.build();
}
@Bean
public Queue initCustomOtherQueue(){
return QueueBuilder
.durable(RabbitMQConstant.OTHER_QUEUE)
.deadLetterExchange(RabbitMQConstant.DEAD_EXCHANGE)
.deadLetterRoutingKey(RabbitMQConstant.DEAD_ROUTING_KEY)
.build();
}
@Bean
public DirectExchange initCustomExchange(){
return ExchangeBuilder
.directExchange(RabbitMQConstant.CUSTOM_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindCustomQueueToExchange(
@Qualifier("initCustomExchange") DirectExchange exchange,
@Qualifier("initCustomQueue") Queue queue
){
return BindingBuilder
.bind(queue)
.to(exchange)
.with(RabbitMQConstant.CUSTOM_ROUTING_KEY);
}
@Bean
public Binding bindCustomOtherQueueToExchange(
@Qualifier("initCustomExchange") DirectExchange exchange,
@Qualifier("initCustomOtherQueue") Queue queue
){
return BindingBuilder
.bind(queue)
.to(exchange)
.with(RabbitMQConstant.CUSTOM_OTHER_ROUTING_KEY);
}
@Bean
public Queue initDeadQueue(){
return QueueBuilder
.durable(RabbitMQConstant.DEAD_QUEUE)
.build();
}
@Bean
public DirectExchange initDeadExchange(){
return ExchangeBuilder
.directExchange(RabbitMQConstant.DEAD_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding bindDeadQueueToExchange(
@Qualifier("initDeadExchange") DirectExchange exchange,
@Qualifier("initDeadQueue") Queue queue
){
return BindingBuilder
.bind(queue)
.to(exchange)
.with(RabbitMQConstant.DEAD_ROUTING_KEY);
}
}
工具类
package com.ssy.www.util;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
@Slf4j
public class SendMessageUtil<T> {
public SendMessageUtil(){}
public void send(T body, RabbitTemplate rabbitTemplate, String id,String exchange,String routingKey){
String jsonString = JSON.toJSONString(body);
JSON json = (JSON) JSON.toJSON(body);
MessagePostProcessor processor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
message.getMessageProperties().setContentType("application/json");
message.getMessageProperties().setContentEncoding("UTF-8");
message.getMessageProperties().setMessageId(id);
return message;
}
};
CorrelationData correlationData = new CorrelationData();
correlationData.setId(id);
MessageProperties properties = new MessageProperties();
properties.setMessageId(id);
properties.setContentEncoding("UTF-8");
properties.setContentType("application/json");
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
properties.setReceivedExchange(exchange);
properties.setReceivedRoutingKey(routingKey);
Message message = new Message(jsonString.getBytes(StandardCharsets.UTF_8),properties);
correlationData.setReturnedMessage(message);
FutureTask<Void> task = new FutureTask<>(new Callable<Void>() {
@Override
public Void call() throws Exception {
rabbitTemplate.convertAndSend(exchange,routingKey,body,processor,correlationData);
return null;
}
});
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(task);
pool.shutdown();
}
}
控制层
package com.ssy.www.controller;
import com.ssy.www.constant.RabbitMQConstant;
import com.ssy.www.entity.User;
import com.ssy.www.util.SendMessageUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("rabbit")
public class ProductController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/custom/queue/user")
public String sendCustomQueueUser(){
SendMessageUtil<User> sendMessageUtil = new SendMessageUtil<>();
LocalDateTime createTime = LocalDateTime.now();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setUsername("中国移动")
.setPassword("10086")
.setToken("China Mobile")
.setAuth("root")
.setCreateTime(createTime)
.setId(i);
sendMessageUtil.send(user,rabbitTemplate,String.valueOf(i),RabbitMQConstant.CUSTOM_EXCHANGE,RabbitMQConstant.CUSTOM_ROUTING_KEY);
}
return ("发送User对象成功--" + RabbitMQConstant.CUSTOM_QUEUE);
}
@GetMapping("custom/queue/list")
public String sendCustomQueueListUser(){
LocalDateTime createTime = LocalDateTime.now();
List<User> list = new ArrayList<>();
User one = new User();
one.setId(1008611);
one.setUsername("中国移动");
one.setPassword("10086");
one.setAuth("admin");
one.setToken("root");
one.setCreateTime(createTime);
list.add(one);
User two = new User();
two.setId(1001011);
two.setUsername("中国联通");
two.setPassword("10010");
two.setAuth("admin");
two.setToken("root");
two.setCreateTime(createTime);
list.add(two);
SendMessageUtil<List<User>> sendMessageUtil = new SendMessageUtil<>();
String id = UUID.randomUUID().toString();
id = id.replaceAll("-","");
sendMessageUtil.send(list,rabbitTemplate,id,RabbitMQConstant.CUSTOM_EXCHANGE,RabbitMQConstant.CUSTOM_ROUTING_KEY);
return ("发送List<User>对象成功--" + RabbitMQConstant.CUSTOM_QUEUE);
}
@GetMapping("/custom/otherQueue/user")
public String sendCustomOtherQueueUser(){
LocalDateTime createTime = LocalDateTime.now();
SendMessageUtil<User> sendMessageUtil = new SendMessageUtil<>();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setUsername("中国移动")
.setPassword("10086")
.setToken("China Mobile")
.setAuth("root")
.setCreateTime(createTime)
.setId(i);
sendMessageUtil.send(user,rabbitTemplate,String.valueOf(i),RabbitMQConstant.CUSTOM_EXCHANGE,RabbitMQConstant.CUSTOM_OTHER_ROUTING_KEY);
}
return ("发送User对象成功--" + RabbitMQConstant.OTHER_QUEUE);
}
@GetMapping("custom/otherQueue/list")
public String sendCustomOtherQueueListUser(){
LocalDateTime createTime = LocalDateTime.now();
SendMessageUtil<List<User>> sendMessageUtil = new SendMessageUtil<>();
for (int i = 1001; i <= 1010; i++) {
List<User> list = new ArrayList<>();
User one = new User();
one.setId(1008611);
one.setUsername("中国移动");
one.setPassword("10086");
one.setAuth("admin");
one.setToken("root");
one.setCreateTime(createTime);
list.add(one);
User two = new User();
two.setId(1001011);
two.setUsername("中国联通");
two.setPassword("10010");
two.setAuth("admin");
two.setToken("root");
two.setCreateTime(createTime);
list.add(two);
sendMessageUtil.send(list,rabbitTemplate,String.valueOf(i),RabbitMQConstant.CUSTOM_EXCHANGE,RabbitMQConstant.CUSTOM_OTHER_ROUTING_KEY);
}
return ("发送List<User>对象成功--" + RabbitMQConstant.OTHER_QUEUE);
}
}
消费端
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
</dependencies>
application文件
server:
port: 9102
servlet:
context-path: /ssy
spring:
application:
name: rabbit-custom-consumer-demo
rabbitmq:
host: 127.0.0.1
port: 5672
username: root
password: root
virtual-host: root
connection-timeout: 10000 # 10秒
启动类
package com.ssy.www;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(CustomConsumeApplication.class,args);
}
}
常量类
package com.ssy.www.constant;
public interface RabbitMQConstant {
String CUSTOM_QUEUE = "spring.custom.queue";
String CUSTOM_EXCHANGE = "spring.custom.exchange";
String CUSTOM_ROUTING_KEY = "spring.custom.routing.key";
String OTHER_QUEUE = "spring.custom.other.queue";
String CUSTOM_OTHER_ROUTING_KEY = "spring.custom.other.routing.key";
String DEAD_QUEUE = "spring.custom.dead.queue";
String DEAD_EXCHANGE = "spring.custom.dead.exchange";
String DEAD_ROUTING_KEY = "spring.custom.dead.routing.key";
}
实体类
package com.ssy.www.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Slf4j
@Data
@Accessors(chain = true)
public class User{
private String username;
private String password;
private String auth;
private String token;
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
private Integer id;
}
消息委托类
package com.ssy.www.delegate;
import com.ssy.www.entity.User;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public final class UserMessageDelegate{
public static final String methodName = "handleUserMessage";
public void handleUserMessage(User message){
log.info("基于适配器的委托模式消费死信队列的简单Java对象消息内容:{}",message);
}
public void handleUserMessage(Map<String,Object> message){
log.info("基于适配器的委托模式消费死信队列的Java的Map类型消息内容:{}",message);
}
public void handleUserMessage(List<User> message){
log.info("基于适配器的委托模式消费死信队列的集合消息内容:{}",message);
}
}
bean注册类
package com.ssy.www.bean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ErrorHandler;
@Slf4j
@Component
public class CustomErrorHandler implements ErrorHandler {
@Override
public void handleError(Throwable throwable) {
log.error("自定义的异常处理器处理Rabbit消费异常,消费消息失败,原因为:{}",throwable.getMessage());
throwable.printStackTrace();
}
}
package com.ssy.www.bean;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class CustomMessageListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) {
try{
log.info("自定义监听器监听消费:消费者{}自定义消费由交换机{}通过路由{}投递给队列{}的消息:{}",
message.getMessageProperties().getConsumerTag(),
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
new String(message.getBody(), StandardCharsets.UTF_8));
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e){
try {
if(e instanceof SocketException){
log.error("由于网络原因,允许重新入列");
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
} else {
log.error("其他原因消费失败,不允许重新入列,直接丢弃");
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception s){
log.error("否定应答出错,原因为:{}",s.getMessage());
}
}
}
}
配置类
package com.ssy.www.config;
import com.ssy.www.entity.User;
import org.springframework.amqp.support.converter.DefaultClassMapper;
import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMessageConverterConfig {
/**
* DefaultJackson2JavaTypeMapper & Jackson2JsonMessageConverter 支持java对象多映射转换
*/
@Bean
public MessageConverter rabbitMessageConverter(DefaultClassMapper defaultClassMapper){
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
// 信任所有的包,否则会报错
javaTypeMapper.setTrustedPackages("*");
Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
messageConverter.setJavaTypeMapper(javaTypeMapper);
//解决RabbitMQ默认将自定义的消息类转换为LinkedHashMap
//No method found for class java.util.LinkedHashMap
messageConverter.setClassMapper(defaultClassMapper);
return messageConverter;
}
@Bean
public DefaultClassMapper classMapper(){
DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
Map<String,Class<?>> map = new HashMap<>();
map.put("com.ssy.www.entity.User", User.class);
defaultClassMapper.setIdClassMapping(map);
return defaultClassMapper;
}
}
package com.ssy.www.config;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.ConsumerTagStrategy;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.ErrorHandler;
import java.util.UUID;
@Configuration
public class RabbitListenerContainerFactoryConfig {
@Autowired
private MessageConverter messageConverter;
@Bean
public SimpleRabbitListenerContainerFactory customRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory,
ErrorHandler errorHandler
){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// 监听器容器连接工厂
factory.setConnectionFactory(connectionFactory);
// 并发消费者数目,默认为1
factory.setConcurrentConsumers(2);
// 并发组大消费者数目,默认为1
factory.setMaxConcurrentConsumers(5);
// 拒绝未确认的消息并把它重新放入队列,默认为true
factory.setDefaultRequeueRejected(false);
// 容器启动时是否自动启动,默认为true
factory.setAutoStartup(true);
// 消息确认模式,默认为auto
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 每个消费在一次请求中预获取的消息数目,默认为1
factory.setPrefetchCount(4);
// 从队列中接收消息的超时时间,默认为 0,表示没有超时限制
factory.setReceiveTimeout(5*1000L);
// 与容器一起使用的事务管理器。默认情况下,容器不会使用事务
factory.setTransactionManager(null);
// 消息转换器,用于将接收到的消息转换为 Java 对象或将 Java 对象转换为消息
factory.setMessageConverter(messageConverter);
// 用于异步消息处理的线程池。默认情况下,容器使用一个简单的 SimpleAsyncTaskExecutor
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
// 重试失败的消息之前等待的时间,默认为 5000 毫秒
factory.setRecoveryInterval(10*1000L);
// 如果消息处理器尝试监听不存在的队列,是否抛出异常。默认为 true
factory.setMissingQueuesFatal(true);
factory.setErrorHandler(errorHandler);
// 消费端的标签策略
factory.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString().replaceAll("-","");
}
});
configurer.configure(factory,connectionFactory);
return factory;
}
}
package com.ssy.www.config;
import com.ssy.www.constant.RabbitMQConstant;
import com.ssy.www.delegate.UserMessageDelegate;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.amqp.support.ConsumerTagStrategy;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.ErrorHandler;
import java.util.UUID;
@Configuration
public class RabbitMessageListenerContainerConfig {
@Autowired
private ChannelAwareMessageListener messageListener;
@Autowired
private ErrorHandler errorHandler;
@Autowired
private MessageConverter messageConverter;
@Bean
public SimpleMessageListenerContainer customRabbitMessageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
// 设置连接工厂
container.setConnectionFactory(connectionFactory);
// 手动ack
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 监听的队列
container.setQueueNames(RabbitMQConstant.CUSTOM_QUEUE);
// 设置监听器
container.setMessageListener(messageListener);
// 设置最大的并发的消费者数量
container.setMaxConcurrentConsumers(5);
// 最小的并发消费者的数量
container.setConcurrentConsumers(2);
// 是否使用重队列
container.setDefaultRequeueRejected(false);
// 监听通道
container.setExposeListenerChannel(true);
// 容器启动时是否自动启动
container.setAutoStartup(true);
// 一个消费者每次处理一条数据
container.setPrefetchCount(1);
// 用于消息处理的线程池,尽量使用异步线程
container.setTaskExecutor(new SimpleAsyncTaskExecutor());
// 如果消息处理器尝试监听不存在的队列,是否抛出异常
container.setMissingQueuesFatal(true);
// 与容器一起使用的事务管理器
container.setTransactionManager(null);
// 消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString().replaceAll("-","");
}
});
// 从队列中接收消息的超时时间
container.setReceiveTimeout(5*1000L);
return container;
}
@Bean
public SimpleMessageListenerContainer customRabbitMessageListenerContainerOnAdapter(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
// 设置连接工厂
container.setConnectionFactory(connectionFactory);
// 自动ack
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
// 设置监听器处理类(委托:通过适配器)
// 消息监听适配器:MessageListenerAdapter
MessageListenerAdapter adapter = new MessageListenerAdapter(new UserMessageDelegate());
adapter.setDefaultListenerMethod(UserMessageDelegate.methodName);
adapter.setMessageConverter(messageConverter);
container.setMessageListener(adapter);
// 监听的队列
container.setQueueNames(RabbitMQConstant.DEAD_QUEUE);
// 最小的并发消费者的数量
container.setConcurrentConsumers(2);
// 设置最大的并发的消费者数量
container.setMaxConcurrentConsumers(5);
// 是否使用重队列
container.setDefaultRequeueRejected(false);
// 监听通道
container.setExposeListenerChannel(true);
// 容器启动时是否自动启动
container.setAutoStartup(true);
//一个消费者每次处理一条数据
container.setPrefetchCount(1);
// 用于消息处理的线程池,尽量使用异步线程
container.setTaskExecutor(new SimpleAsyncTaskExecutor());
// 如果消息处理器尝试监听不存在的队列,是否抛出异常
container.setMissingQueuesFatal(true);
// 与容器一起使用的事务管理器
container.setTransactionManager(null);
// 消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString().replaceAll("-","");
}
});
container.setErrorHandler(errorHandler);
// 从队列中接收消息的超时时间
container.setReceiveTimeout(5*1000L);
return container;
}
}
消息监听类
package com.ssy.www.listener;
import com.rabbitmq.client.Channel;
import com.ssy.www.constant.RabbitMQConstant;
import com.ssy.www.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@RabbitListener(queues = {RabbitMQConstant.OTHER_QUEUE},containerFactory = "customRabbitListenerContainerFactory")
public class CustomRabbitMessageListener {
//@Payload是消费者接受生产者发送的队列消息,将队列中的json字符串变成对象的注解
//@Payload 与 @Headers
//使用 @Payload 和 @Headers 注解可以获取消息中的 body 与 headers 信息
//也可以获取单个 Header 属性,@Header
//@RabbitHandler(isDefault = true)
@RabbitHandler
public void processOtherQueueMessage(User user, Message message, Channel channel) {
try {
if(user.getId()%2==0){
log.info(">>>>>拒绝消费:{},单一信息进入死信队列",user);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
} else {
log.info("收到自定义工厂factory的单一消息:消费者->{},消息->{}",message.getMessageProperties().getConsumerTag(),user);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception e){
log.error("【单一】自定义工厂应答出错:{}",e.getMessage());
}
}
@RabbitHandler
public void processOtherQueueMessage(List<User> users, Message message, Channel channel){
if(users==null || users.size()==0){
throw new IllegalArgumentException("不存在消息,无法进行消费");
}
try{
if(new Integer(message.getMessageProperties().getMessageId())%2==1){
log.info(">>>>>拒绝消费:{},集合信息进入死信队列",users);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
} else {
log.info("收到自定义工厂factory的集合消息:消费者->{},消息->{}",message.getMessageProperties().getConsumerTag(),users);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
} catch (Exception e){
log.error("【集合】自定义工厂应答出错:{}",e.getMessage());
}
}
}