安装
本次使用Docker安装rabbitmq。
- rabbitmq暴露端口如下,需要做映射
-
数据卷相关
- /var/lib/rabbitmq:保存数据(默认挂载的数据卷)
- /etc/rabbitmq:保存配置
- /var/log/rabbitmq:保存日志
# 1. 拉取镜像。注意:一定要拉取带management的镜像
[root@aliyunECS ~]# docker pull rabbitmq:3.9.13-management
3.9.13-management: Pulling from library/rabbitmq
08c01a0ec47e: Pull complete
1ceb23964d6c: Pull complete
64631f643e2c: Pull complete
67525726a753: Pull complete
4b28b554c25b: Pull complete
3afdc08bc87a: Pull complete
1e22dd212ab8: Pull complete
b585ed397156: Pull complete
68de743a7628: Pull complete
68b52ebaee48: Pull complete
Digest: sha256:e2b65c7fa8b09ef99786881925bea3d0465c50dc32663d45c78f8bd077ada648
Status: Downloaded newer image for rabbitmq:3.9.13-management
docker.io/library/rabbitmq:3.9.13-management
# 2. 启动容器
# 注意:
# |- 端口映射
# |- 使用具名挂载
[root@aliyunECS ~]# docker run -id --name=my-mq -p 5672:5672 -p 15672:15672 -p 25672:25672 -p 15692:15692 -v rabbitmq_data:/var/lib/rabbitmq -v rabbitmq_conf:/etc/rabbitmq -v rabbitmq_log:/var/log/rabbitmq --hostname=myrabbit rabbitmq:3.9.13-management
1a685f59f83a5140117a7ad822b4bbb27e59392047d974fe1c230b370adc0a3d
[root@aliyunECS ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1a685f59f83a rabbitmq:3.9.13-management "docker-entrypoint.s…" 4 seconds ago Up 2 seconds 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 15691-15692/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp my-mq
# 3. 开启防火墙端口5672和15672。如果是阿里云服务器则还需要在安全组打开相应端口
[root@aliyunECS ~]# firewall-cmd --zone=public --add-port=5672/tcp --add-port=15672/tcp --permanent
[root@aliyunECS ~]# systemctl restart firewalld.service
以上步骤做完,在本地输入 weixu.top:15672 访问,使用guest/guest登录,可正常进入管理控制台页面。
如果报错User can only log in via localhost。由于guest用户只能在服务器端本地访问,因此这里考虑新建一个用户做解决方案:
1.首先进入容器 docker exec -it my-mq /bin/bash
2.创建用户 rabbitmqctl add_user admin admin
3.给用户授权角色 rabbitmqctl set_user_tags admin administrator
4.给用户添加权限 rabbitmqctl set_permissions -p / admin "." "." ".*"
参考阅读:python.iitter.com/other/98438…
Rabbitmq架构
相关概念解释:
-
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
-
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
-
Connection:publisher/consumer 和 broker 之间的 TCP 连接
-
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
-
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
-
Queue:消息最终被送到这里等待 consumer 取走
-
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
Rabbitmq六种模式
1、简单模式
特点:
- 没有交换机(使用默认交换机)
- 一个发送方一个消费者
- routing key为队列的名字
2、Work Queue模式
与简单模式相比,增加以下特点:
- 多个消费者监听一个队列。多个消费者之间竞争消费消息
3、Publisher/Subscriber模式(Fanout模式)
特点:
- 有一个交换机,绑定多个队列
- 没有routing key,即发送到X的消息会直接转发到所有绑定的队列
4、Routing模式(Direct模式)
特点:
- 交换机-routing key-队列 绑定
- 消息携带的routing key必须完全相同才会被X指派到指定的队列上
5、Topic模式
topic模式和routing模式几乎相同,不同点在于:
topic模式的routing key可以使用通配符。
通配符规则:
- #:匹配一个或多个单词
- *:匹配一个单词
例如,item.#可以匹配item.apple.insert、item.banana.update等消息;item.*可以匹配item.insert、item.update等消息,但不能匹配item.apple.insert消息。
基本代码实现
- 创建父工程mq-demo
<?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>
<groupId>com.kkb</groupId>
<artifactId>mq-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>rabbitmq-demo</module>
<module>spring-rabbitmq-producer</module>
<module>spring-rabbitmq-consumer</module>
<module>springboot-rabbitmq-producer</module>
<module>springboot-rabbitmq-consumer</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>8</java.version>
<springcloud.version>2020.0.1</springcloud.version>
<springcloud-alibaba.version>2021.1</springcloud-alibaba.version>
<nacos.version>1.4.1</nacos.version>
<sentinel.version>1.8.0</sentinel.version>
<rocketMQ.version>4.4.0</rocketMQ.version>
<dubbo.version>2.7.8</dubbo.version>
<seata.version>1.3.0</seata.version>
<swagger.version>2.9.2</swagger.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup paent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 创建rabbitmq-demo模块,导入以下依赖
<?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">
<parent>
<artifactId>mq-demo</artifactId>
<groupId>com.kkb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rabbitmq-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
</dependencies>
</project>
简单模式
- 编写ConnectionUtil来方便创建连接
package com.kkb.hd.util;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @className: ChannelUtil
* @description: 产生channel的帮助类
* @author: HanDing
* @date: 2022/2/23
**/
public class ConnectionUtil {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
private static final String DEFAULT_HOST = "120.78.228.224";
private static final int AMQP_PORT = 5672;
private static final String DEFAULT_VIRTUAL_HOST = "/xzk";
private static final String DEFAULT_USERNAME = "handing";
private static final String DEFAULT_PASS = "123456";
public static Connection getDefaultConnection() throws IOException, TimeoutException {
connectionFactory.setUsername(DEFAULT_USERNAME);
connectionFactory.setPassword(DEFAULT_PASS);
connectionFactory.setHost(DEFAULT_HOST);
connectionFactory.setPort(AMQP_PORT);
connectionFactory.setVirtualHost(DEFAULT_VIRTUAL_HOST);
return connectionFactory.newConnection();
}
}
- provider
package com.kkb.hd.simple;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @className: Producer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/22
**/
public class Producer {
public static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws Exception{
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
//创建频道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//发布消息
//简单模式:使用默认交换机"",routing key为队列名称
channel.basicPublish("", QUEUE_NAME, null, "你好,小兔子".getBytes());
System.out.println("消息发送完成");
//释放资源
channel.close();
connection.close();
}
}
- consumer
package com.kkb.hd.simple;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @className: MyConsumer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/23
**/
public class MyConsumer {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息id:" + envelope.getDeliveryTag());
System.out.println("exchange:" + envelope.getExchange());
System.out.println("routing key" + envelope.getRoutingKey());
System.out.println("收到消息:" + new String(body, StandardCharsets.UTF_8));
}
});
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
- MyDefaultConsumer:消息消费的回调类,上面DefaultConsumer匿名内部类抽象出来的一个类
package com.kkb.hd.consumer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/23
**/
public class MyDefaultConsumer extends DefaultConsumer {
/**
* Constructs a new instance and records its association to the passed-in channel.
*
* @param channel the channel to which this consumer is attached
*/
public MyDefaultConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息id:" + envelope.getDeliveryTag());
System.out.println("exchange:" + envelope.getExchange());
System.out.println("routing key:" + envelope.getRoutingKey());
System.out.println("收到消息:" + new String(body, StandardCharsets.UTF_8));
}
}
Work queue模式
和简单模式类似,只要多创建一个消费者即可。
Publish/Subscribe模式
- Producer
package com.kkb.hd.fanout;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sun.org.apache.xml.internal.resolver.readers.ExtendedXMLCatalogReader;
/**
* @className: Producer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/22
**/
public class Producer {
public static final String QUEUE_NAME_1 = "fanout_queue_1";
public static final String QUEUE_NAME_2 = "fanout_queue_2";
public static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception{
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机exchange
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//声明两个队列
channel.queueDeclare(QUEUE_NAME_1, true, false, false, null);
channel.queueDeclare(QUEUE_NAME_2, true, false, false, null);
//将两个队列分别和交换机进行绑定
//fanout模式交换机将消息广播到所有绑定的queue上,因此不用指定routing queue
channel.queueBind(QUEUE_NAME_1, EXCHANGE_NAME, "");
channel.queueBind(QUEUE_NAME_2, EXCHANGE_NAME, "");
//发布消息
for(int i = 1; i <=10; i++){
String s = "你好,小兔子==fanout模式===消息" + i;
channel.basicPublish(EXCHANGE_NAME, "", null, s.getBytes());
}
System.out.println("消息发送完成");
//释放资源
channel.close();
connection.close();
}
}
- 消费者1:监听fanout_queue_1
package com.kkb.hd.fanout;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer1
* @description: 消费queue 1的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class MyConsumer1 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_NAME_1, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_NAME_1, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息id:" + envelope.getDeliveryTag());
System.out.println("exchange:" + envelope.getExchange());
System.out.println("routing key:" + envelope.getRoutingKey());
System.out.println("收到消息:" + new String(body, StandardCharsets.UTF_8));
}
});
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
- 消费者2:监听fanout_queue_2
package com.kkb.hd.fanout;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer2
* @description: 消费queue 2的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class MyConsumer2 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_NAME_2, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_NAME_2, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息id:" + envelope.getDeliveryTag());
System.out.println("exchange:" + envelope.getExchange());
System.out.println("routing key:" + envelope.getRoutingKey());
System.out.println("收到消息:" + new String(body, StandardCharsets.UTF_8));
}
});
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
Routing模式
上述把Exchange创建类型修改为DIRECT即可:
- provider
package com.kkb.hd.direct;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.nio.charset.StandardCharsets;
/**
* @className: Producer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/22
**/
public class Producer {
public static final String QUEUE_UPDATE = "update_queue";
public static final String QUEUE_INSERT = "insert_queue";
public static final String DIRECT_EXCHANGE = "direct_exchange";
public static void main(String[] args) throws Exception{
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机exchange
channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明两个队列
channel.queueDeclare(QUEUE_INSERT, true, false, false, null);
channel.queueDeclare(QUEUE_UPDATE, true, false, false, null);
//将两个队列分别和交换机进行绑定
//fanout模式交换机将消息广播到所有绑定的queue上,因此不用指定routing queue
channel.queueBind(QUEUE_INSERT, DIRECT_EXCHANGE, "insert");
channel.queueBind(QUEUE_UPDATE, DIRECT_EXCHANGE, "update");
//发布消息 -> insert-queue
channel.basicPublish(DIRECT_EXCHANGE, "insert", null, "新增商品完成".getBytes(StandardCharsets.UTF_8));
channel.basicPublish(DIRECT_EXCHANGE, "update", null, "商品更新完成".getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完成");
//释放资源
channel.close();
connection.close();
}
}
- InsertConsumer:监听insert_queue的消息
package com.kkb.hd.direct;
import com.kkb.hd.consumer.MyDefaultConsumer;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
/**
* @className: MyConsumer1
* @description: 消费queue 1的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class InsertConsumer {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_INSERT, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_INSERT, true, new MyDefaultConsumer(channel));
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
- UpdateConsumer:监听update_queue的消息
package com.kkb.hd.direct;
import com.kkb.hd.consumer.MyDefaultConsumer;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer2
* @description: 消费queue 2的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class UpdateConsumer {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_UPDATE, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_UPDATE, true, new MyDefaultConsumer(channel));
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
Topic模式
routing key可以使用通配符==#==或者==*== 。
- Producer:发送三个消息,分别为item.insert、item.update、item.delete
package com.kkb.hd.topic;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* @className: Producer
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/22
**/
public class Producer {
public static final String INSERT_UPDATE_QUEUE = "insert_update_queue"; //可以接收item.insert和item.update路由消息
public static final String QUEUE_ALL = "queue_all"; //可以接收item.*的路由消息
public static final String TOPIC_EXCHANGE = "topic_exchange";
public static void main(String[] args) throws Exception{
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
//创建频道
Channel channel = connection.createChannel();
//声明交换机exchange
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
//声明两个队列
channel.queueDeclare(INSERT_UPDATE_QUEUE, true, false, false, null);
channel.queueDeclare(QUEUE_ALL, true, false, false, null);
//将两个队列分别和交换机进行绑定
//fanout模式交换机将消息广播到所有绑定的queue上,因此不用指定routing queue
channel.queueBind(QUEUE_ALL, TOPIC_EXCHANGE, "item.*");
channel.queueBind(INSERT_UPDATE_QUEUE, TOPIC_EXCHANGE, "item.insert");
channel.queueBind(INSERT_UPDATE_QUEUE, TOPIC_EXCHANGE, "item.update");
//添加商品
String s = "增加商品item.insert";
channel.basicPublish(TOPIC_EXCHANGE, "item.insert", null, s.getBytes());
//更新商品
s = "更新商品item.update";
channel.basicPublish(TOPIC_EXCHANGE, "item.update", null, s.getBytes());
//删除商品
s = "删除商品item.delete";
channel.basicPublish(TOPIC_EXCHANGE, "item.delete", null, s.getBytes());
System.out.println("消息发送完成");
//释放资源
channel.close();
connection.close();
}
}
- 消费者1:监听insert_update_queue队列,可以消费到两条消息item.insert和item.update
package com.kkb.hd.topic;
import com.kkb.hd.consumer.MyDefaultConsumer;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer1
* @description: 消费queue 1的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class MyConsumer1 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.INSERT_UPDATE_QUEUE, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.INSERT_UPDATE_QUEUE, true, new MyDefaultConsumer(channel));
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
- 消费者2:监听queue_all队列,可以消费到所有三条消息
package com.kkb.hd.topic;
import com.kkb.hd.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @className: MyConsumer2
* @description: 消费queue 2的消息
* @author: HanDing
* @date: 2022/2/23
**/
public class MyConsumer2 {
public static void main(String[] args) throws Exception {
//创建连接
Connection connection = ConnectionUtil.getDefaultConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_ALL, true, false, false, null);
// autoAck: 接收成功后是否自动向消息队列发送确认。消息队列接受到确认信息后会自动删除消息。如设置为false需要手动发送确认信息
channel.basicConsume(Producer.QUEUE_ALL, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消息id:" + envelope.getDeliveryTag());
System.out.println("exchange:" + envelope.getExchange());
System.out.println("routing key:" + envelope.getRoutingKey());
System.out.println("收到消息:" + new String(body, StandardCharsets.UTF_8));
}
});
//不能关闭连接,这里要让消费者一直阻塞等待队列的消息
}
}
整合spring
这里使用topic模式做示例。
producer模块
- 创建spring-rabbitmq-producer模块
- 导入依赖(spring-rabbit)
<?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">
<parent>
<artifactId>mq-demo</artifactId>
<groupId>com.kkb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rabbitmq-spring</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
</project>
注意:这里应该使用继承的依赖,不要自己指定版本号,容易出现版本冲突
rabbitmq配置文件
- rabbitmq.properties
rabbit.host=120.78.228.224
rabbit.port=5672
rabbit.virtual-host=/xzk
rabbit.username=handing
rabbit.password=123456
xml文件声明连接工厂、队列、交换机、rabbitTemplate
rabbit.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
">
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!--创建连接工厂-->
<rabbit:connection-factory id="connectionFactory"
host="${rabbit.host}"
port="${rabbit.port}"
username="${rabbit.username}"
password="${rabbit.password}"
virtual-host="${rabbit.virtual-host}"
/>
<!--声明队列和交换机-->
<rabbit:admin id="admin" connection-factory="connectionFactory" />
<rabbit:queue id="spring-simple-queue" name="spring-simple-queue" auto-declare="true" durable="true" />
<rabbit:queue id="spring-work-queue-1" name="spring-work-queue-1" auto-declare="true" durable="true" />
<rabbit:queue id="spring-work-queue-2" name="spring-work-queue-2" auto-declare="true" durable="true" />
<rabbit:queue id="spring-direct-queue" name="spring-direct-queue" auto-declare="true" durable="true" />
<rabbit:queue id="spring-update-queue" name="spring-update-queue" auto-declare="true" durable="true" />
<rabbit:queue id="spring-insert-queue" name="spring-insert-queue" auto-declare="true" durable="true" />
<rabbit:queue id="spring-topic-queue-star" name="spring-topic-queue-star" auto-declare="true" durable="true" />
<rabbit:queue id="spring-topic-queue-well" name="spring-topic-queue-well" auto-declare="true" durable="true" />
<rabbit:fanout-exchange id="spring-fanout-exchange" name="spring-fanout-exchange" durable="true" auto-declare="true" >
<rabbit:bindings>
<rabbit:binding queue="spring-work-queue-1" />
<rabbit:binding queue="spring-work-queue-2" />
</rabbit:bindings>
</rabbit:fanout-exchange>
<rabbit:direct-exchange id="spring-direct-exchange" name="spring-direct-exchange" auto-declare="true" durable="true" >
<rabbit:bindings>
<rabbit:binding queue="spring-direct-queue" key="direct"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:topic-exchange id="spring-topic-exchange" name="spring-topic-exchange" durable="true" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="hd.*" queue="spring-topic-queue-star" />
<rabbit:binding pattern="hd.#" queue="spring-topic-queue-well" />
</rabbit:bindings>
</rabbit:topic-exchange>
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" />
</beans>
发送消息:rabbitTemplate.convertAndSend
package com.kkb.hd;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @className: Test
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/24
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:rabbit.xml")
public class RabbitProviderTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimple(){
rabbitTemplate.convertAndSend("","spring-simple-queue", "simple模式发送消息");
}
@Test
public void testDirectExchange(){
rabbitTemplate.convertAndSend("spring-direct-exchange","direct", "direct模式发送消息");
}
@Test
public void testDirectExchangeMore(){
for(int i = 0; i < 100; i++) {
rabbitTemplate.convertAndSend("spring-direct-exchange", "direct", "direct模式发送消息----- " + i);
}
}
@Test
public void testFanoutExchange(){
rabbitTemplate.convertAndSend("spring-fanout-exchange", "", "fanout消息");
}
@Test
public void testTopicExchange(){
rabbitTemplate.convertAndSend("spring-topic-exchange", "hd.color", "hd.color消息");
rabbitTemplate.convertAndSend("spring-topic-exchange", "hd.mos.hello", "hd.mos.hello消息");
}
}
consumer模块
- 创建spring-rabbitmq-consumer模块
- 导入依赖
<?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">
<parent>
<artifactId>mq-demo</artifactId>
<groupId>com.kkb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-rabbitmq-consumer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
</project>
rabbitmq配置文件
- properties文件和producer里的一致,直接拷贝即可
- rabbit.xml文件:主要声明监听器<rabbitmq:listener>以及其监听的队列
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
">
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!--创建连接工厂-->
<rabbit:connection-factory id="connectionFactory"
host="${rabbit.host}"
port="${rabbit.port}"
username="${rabbit.username}"
password="${rabbit.password}"
virtual-host="${rabbit.virtual-host}"
/>
<!--声明队列和交换机-->
<rabbit:admin id="admin" connection-factory="connectionFactory" />
<bean id="simple-listener" class="com.kkb.hd.listener.SimpleListener" />
<bean id="insert-listener" class="com.kkb.hd.listener.InsertListener" />
<bean id="update-listener" class="com.kkb.hd.listener.UpdateListener" />
<bean id="topic-listener-star" class="com.kkb.hd.listener.TopicListenerStar" />
<bean id="topic-listener-well" class="com.kkb.hd.listener.TopicListenerWell" />
<bean id="work-listener-1" class="com.kkb.hd.listener.WorkListener1" />
<bean id="work-listener-2" class="com.kkb.hd.listener.WorkListener2" />
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="simple-listener" queue-names="spring-simple-queue" />
<rabbit:listener ref="insert-listener" queue-names="spring-insert-queue" />
<rabbit:listener ref="update-listener" queue-names="spring-update-queue" />
<rabbit:listener ref="topic-listener-star" queue-names="spring-topic-queue-star" />
<rabbit:listener ref="topic-listener-well" queue-names="spring-topic-queue-well" />
<rabbit:listener ref="work-listener-1" queue-names="spring-work-queue-1" />
<rabbit:listener ref="work-listener-2" queue-names="spring-work-queue-2" />
</rabbit:listener-container>
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" />
</beans>
定义监听器:实现MessageListener端口
package com.kkb.hd.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessageProperties;
import java.nio.charset.StandardCharsets;
/**
* @className: SimpleListener
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/25
**/
public class SimpleListener implements MessageListener {
@Override
public void onMessage(Message message) {
MessageProperties messageProperties = message.getMessageProperties();
System.out.println("接收到的消息:" + new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println("交换机:" + messageProperties.getReceivedExchange());
System.out.println("队列:" + messageProperties.getConsumerQueue());
System.out.println("Routing key:" + messageProperties.getReceivedRoutingKey());
}
}
其余监听器均是SimpleListener的子类,类内容为空,这里省略。
public class InsertListener extends SimpleListener{
}
测试类
package com.kkb.hd;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @className: ConsumerTest
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/25
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:rabbit.xml")
public class ConsumerTest {
@Test
public void test(){
while(true){
}
}
}
测试
- 首先在producer模块里发送消息
- 其次启动consumer模块测试
注意:先启动生产者还是消费者理论上没有差别。但实际情况下应该==先启动有绑定交换机和队列的模块==。没有绑定的话生产者发送的消息因为找不到队列会被自动丢弃(因为exchange不保存消息只转发)
整合springboot
springboot最大的优势:省略复杂的xml配置,使用全注解配置。
为了更好的复习回顾之前所学习的nacos配置中心,这里我将生产者和消费者模块的配置全部放到远程nacos上了。
producer模块
- 创建springboot-rabbitmq-producer模块
- 导入配置
<?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">
<parent>
<artifactId>mq-demo</artifactId>
<groupId>com.kkb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springboot-rabbitmq-producer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--使用bootstrap连接配置中心时务必导入此依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
</project>
小坑:因为有一段时间没写nacos了,一开始编写的时候犯了一个错误,没有导入spring-cloud-starter-bootstrap依赖。这样一来bootstrap.yml中所定义的配置中心会失效,所有配置都使用默认的,比如rabbitmq连接到了默认的localhost:5672。因此务必导入==spring-cloud-starter-bootstrap==这个依赖。
另一方面,如果导入了此依赖但还是没有连接到配置中心,则检查一下==版本冲突==。
bootstrap.yml
spring:
application:
name: rabbitmq-producer
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 120.78.228.224:1111
file-extension: yaml
extension-configs[0]:
data-id: common.yaml
group: DEFAULT_GROUP
refresh: true
rabbitmq-producer-dev.yaml
spring:
rabbitmq:
host: 120.78.228.224
port: 5672
virtual-host: /xzk
username: handing
password: 123456
publisher-confirm-type: correlated # 生产者消息确认机制
publisher-returns: true # 生产者消息返回机制
template:
mandatory: true
配置config
做三件事:==创建队列、创建交换机、两者绑定==
- RabbitMQNormalConfig
package com.kkb.hd.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @className: RabbitMQNormalConfig
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/25
**/
@Configuration
public class RabbitMQNormalConfig {
public static final String ITEM_TOPIC_EXCHANGE = "item-topic-exchange";
public static final String ITEM_TOPIC_QUEUE = "item-topic-queue";
@Bean
public Exchange itemTopicExchange(){
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
@Bean
public Queue itemTopicQueue(){
return QueueBuilder.durable(ITEM_TOPIC_QUEUE).build();
}
@Bean
public Binding itemBinding(@Qualifier("itemTopicQueue") Queue queue, @Qualifier("itemTopicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
}
消息发送测试
package com.kkb.hd;
import com.kkb.hd.config.RabbitMQDlxConfig;
import com.kkb.hd.config.RabbitMQNormalConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* @className: ProducerApplicationTest
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/25
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerApplicationTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendNormalMessage(){
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.insert", "新增商品");
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.update", "更新商品");
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "删除商品");
System.out.println("消息发送完毕!");
}
}
consumer模块
bootstrap.yml
spring:
application:
name: rabbitmq-consumer
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 120.78.228.224:1111
file-extension: yaml
extension-configs:
- dataId: rabbitmq-producer-dev.yaml
group: DEFAULT_GROUP
refresh: true
创建listener
package com.kkb.hd.listener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* @className: MyListener
* @description: TODO 类描述
* @author: HanDing
* @date: 2022/2/25
**/
@Component
public class MyListener {
@RabbitListener(queues = "item-topic-queue")
public void listener1(String message, Channel channel){
System.out.println("监听到的消息:" + message);
}
}
测试
- producer使用测试类发送消息
- consumer启动Application接收消息
高级特性
可靠性投递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
- confirm 确认模式
- return 退回模式
rabbitmq 整个消息投递的路径为: producer--->rabbitmq broker--->exchange--->queue--->consumer
- 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
- 消息从 exchange-->queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递。
ConfirmCallback
- 在生产者配置文件中增加配置:
spring:
rabbitmq:
publisher-confirm-type: correlated
- rabbitTemplate消息发送时设置ConfirmCallback参数
@Test
public void testConfirm(){
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
System.out.println("执行confirmCallback方法");
if(ack){
System.out.println("消息成功发送");
}else{
System.out.println("消息发送失败,原因" + cause);
}
});
//测试发送到一个不存在的交换机
rabbitTemplate.convertAndSend("xxxxx", "item.insert", "新增商品");
}
ReturnCallback
- 在生产者中设置回退模式:
spring:
rabbitmq:
publisher-returns: true
-
rabbitTemplate需要设置setMandatory(true)
-
false:如果消息没有路由到Queue,则丢弃消息(默认)
- true:如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
-
-
rabbitTemplate设置setReturnsCallback参数
@Test
public void testReturn(){
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnsCallback(returnedMessage -> {
System.out.println("return执行了...");
String exchange = returnedMessage.getExchange();
String routingKey = returnedMessage.getRoutingKey();
//String queue = returnedMessage.getMessage().getMessageProperties().getConsumerQueue();
System.out.println("消息从" + exchange + "到路由key为" + routingKey);
System.out.println("消息为:" + new String(returnedMessage.getMessage().getBody(), StandardCharsets.UTF_8));
});
//测试发送一个不存在的routing key
rabbitTemplate.convertAndSend("spring-direct-exchange", "kkkk", "新增商品" );
try {
TimeUnit.SECONDS.sleep(1L); //延时一秒从而等待exchange消息发送线程回调函数执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
也可以在配置文件中设置
spring.rabbitmq.template.mandatory=true
注意:如果测试的时候ReturnCallback没有执行,参考这篇博客:blog.csdn.net/u013165358/…
Consumer ack
消费端确认。
消费者默认收到消息后就自动向queue发送一个ack,表示收到消息,queue收到ack之后删除消息。
但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用==channel.basicAck()==,手动签收,如果出现异常,则调用==channel.basicNack()==方法,让其自动重新发送消息。
三种确认方式:
- 自动确认:acknowledge="none"
- 手动确认:acknowledge="manual"
- 根据异常情况确认:acknowledge="auto",(这种方式使用麻烦,不作讲解)
消费者配置
开启手动ack
- spring:在<rabbit:listener-container>标签中设置
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="simple-listener" queue-names="spring-simple-queue" />
</rabbit:listener-container>
- springboot中的配置:
spring:
rabbitmq:
listener:
type: simple
simple:
acknowledge-mode: manual
prefetch: 1 #消费者限流配置:每个线程每次只取一个消息
concurrency: 3 #启动三个消费线程接收消息
消息发送后手动ack
- spring方式
实现ChannelAwareMessageListener接口,里面的方法含有Channel参数,可以直接使用channel.basicAck或者channel.basicNack。
public class AnotherListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//参考springboot方式
}
}
- springboot方式
在MyListener中添加如下方法
@RabbitListener(queues = "spring-direct-queue")
public void listener2(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
int i = 3 / 0; //模拟消息消费失败的情况
System.out.println("消息接收成功,为:" + msg);
channel.basicAck(deliveryTag, true);
}catch(Exception e){
e.printStackTrace();
System.out.println("后续业务操作失败,消息回退到queue");
channel.basicNack(deliveryTag, true, true);
}
}
消费端限流
在MyListener中修改listener2方法:
@RabbitListener(queues = "spring-direct-queue")
public void listener2(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 在yml中定义了三个消费线程,每个线程每次取一个消息。这里设置每个线程有1秒延时。因此最后效果为每1秒消费3个消息
TimeUnit.SECONDS.sleep(1L); //模拟测试消费端限流:每个线程每秒取一个消息
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("消息接收成功,为:" + msg);
channel.basicAck(deliveryTag, true);
}catch(Exception e){
e.printStackTrace();
System.out.println("后续业务操作失败,消息回退到queue");
channel.basicNack(deliveryTag, true, true);
}
}
TTL
队列可以设置TTL,从而使得在TTL之后所有消息丢弃。
队列配置TTL
@Bean
public Queue itemTopicQueue(){
// 声明一个具有消息过期时间的队列:过期时间为10秒
return QueueBuilder.durable(ITEM_TOPIC_QUEUE).ttl(10000).build();
}
消息配置TTL
@Test
public void sendNormalMessage(){
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.insert", "新增商品", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000"); //单独设置消息的过期时间为5秒
// 注意:这种具有过期时间的消息必须在消息队列的最上面(最先发送的),否则会不生效
return message;
}
});
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.update", "更新商品");
rabbitTemplate.convertAndSend(RabbitMQNormalConfig.ITEM_TOPIC_EXCHANGE, "item.delete", "删除商品");
System.out.println("消息发送完毕!");
}
注意:
- 消息和队列都配置TTL时,以短的为准
- 消息如果配置了TTL,则它必须是最前面的消息(第一个发送的)才会生效
死信队列/死信交换机
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的几种情况:
- 消息超时(TTL)
- 消息数量超过了队列的容量上限
- 消息没有被消费端正常消费(消费端返回Nack)
注意:如果要测试第三种情况,消费者返回Nack这种情况,需要在basicNack中设置不重回队列 requeue=false channel.basicNack(deliveryTag,true,false);
生产者代码:
package com.kkb.hd.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @className: RabbitMQDlxConfig
* @description: 死信队列测试
* @author: HanDing
* @date: 2022/2/25
**/
@Configuration
public class RabbitMQDlxConfig {
public static final String TEST_DLX_EXCHANGE = "test-dlx-exchange"; //测试交换机
public static final String DLX_EXCHANGE = "dlx-exchange"; //死信交换机
public static final String TEST_DLX_QUEUE = "test-dlx-queue"; //测试队列
public static final String DLX_QUEUE = "dlx-queue"; //死信队列
//首先创建:1、test交换机 2、test队列 3、绑定
@Bean
public Exchange testDlxExchange(){
return ExchangeBuilder.topicExchange(TEST_DLX_EXCHANGE).durable(true).build();
}
@Bean
public Queue testDlxQueue(){
return QueueBuilder.durable(TEST_DLX_QUEUE)
.ttl(10000) //设置队列的超时时间:超时后所有消息变为死信
.maxLength(10) //设置队列的最大消息数量:超过的消息直接为死信
.deadLetterExchange(DLX_EXCHANGE) //绑定死信交换机:死信消息直接发送到该交换机上
.deadLetterRoutingKey("dlx.abc") //发送到死信交换机时使用的routingKey
.build();
}
@Bean
public Binding testBinding(@Qualifier("testDlxQueue") Queue queue, @Qualifier("testDlxExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs();
}
//其次创建: 1、dlx交换机 2、dlx队列 3、绑定
@Bean
public Exchange dlxExchange(){
return ExchangeBuilder.topicExchange(DLX_EXCHANGE).durable(true).build();
}
@Bean
public Queue dlxQueue(){
return QueueBuilder.durable(DLX_QUEUE).build();
}
@Bean
public Binding bindDlx(@Qualifier("dlxExchange") Exchange exchange, @Qualifier("dlxQueue") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
}
消费端略。
延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
需求:
- 下单后,30分钟未支付,取消订单,回滚库存。
- 新用户注册成功7天后,发送短信问候。
实现方式:
- 定时器
- 延迟队列
Rabbitmq中没有延迟队列,使用TTL+死信队列间接实现。
- TTL:超时时间
- 死信队列:超时之后消息发送到的地方。死信队列由处理超时消息的监听器处理。
具体实现和死信队列的代码类似,这里省略。