RabbitMQ3.8.9+Springboot2.X实战
一. 介绍
二. Centos7.X rabbitmq骨架搭建
1.docker部署
#依次运行以下命令添加yum源
yum update
yum install epel-release -y
yum clean all
yum list
#安装并运行Docker。
yum install docker-io -y
systemctl start docker
#检查安装结果。
docker info
#启动使用Docker
systemctl start docker #运行Docker守护进程
systemctl stop docker #停止Docker守护进程
systemctl restart docker #重启Docker守护进程
#修改镜像仓库
vim /etc/docker/daemon.json
#改为下面内容,然后重启docker
{
“debug”:true,“experimental”:true,
“registry-mirrors”:[“https://xxx.mirror.aliyuncs.com”,“https://hub-mirror.c.163.com”,“https://docker.mirrors.ustc.edu.cn”]
}
#查看信息
docker info
2.下载rabbitmq镜像
#拉取镜像
docker pull rabbitmq:management
docker run -d --hostname rabbit_host1 --name xd_rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:management
#介绍
-d 以守护进程方式在后台运行
-p 15672:15672 management 界面管理访问端口
-p 5672:5672 amqp 访问端口
--name:指定容器名
--hostname:设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts,作为容器主机IP的别名,并且将显示在容器的bash中
-e 参数
RABBITMQ_DEFAULT_USER 用户名
RABBITMQ_DEFAULT_PASS 密码
开放相关端口:
4369 erlang 发现口
5672 client 端通信口
15672 管理界面 ui 端口
25672 server 间内部通信口(集群使用)
关闭防火墙:
停止firewall
systemctl stop firewalld.service
禁止firewall开机启动
systemctl disable firewalld.service
三.Rabbitmq模式使用
1.添加maven依赖
官方地址:https://www.rabbitmq.com/java-client.html
依赖地址:https://mvnrepository.com/artifact/com.rabbitmq/amqp-client/5.10.0
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
</dependencies>
2.简单模式测试
1.生产端
package net.custompang;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* 生产者
*/
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
try ( //JDK7语法 或自动关闭 connnection和channel
//创建连接
Connection connection = factory.newConnection();
//创建信道
Channel channel = connection.createChannel()) {
/**
* 队列名称
* 持久化配置:mq重启后还在
* 是否独占:只能有一个消费者监听队列;当connection关闭是否删除队列,一般是false,发布订阅是独占
* 自动删除: 当没有消费者的时候,自动删除掉,一般是false
* 其他参数
*
* 队列不存在则会自动创建,如果存在则不会覆盖,所以此时的时候需要注意属性
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
/**
* 参数说明:
* 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由,
* 路由健名称
* 配置信息
* 发送的消息数据:字节数组
*/
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
2.消费端
package net.custompang;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
}
};
channel.basicConsume(QUEUE_NAME,true,consumer);
// DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
// String message = new String(msg, "UTF-8");
// System.out.println(" [x] Received '" + message + "'");
// };
//自动确认消息
//channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
3.工作模式测试
1.生产端
package net.custompang.work.rr;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* 生产者
*/
public class Send {
private final static String QUEUE_NAME = "work_mq_rr";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
try ( //JDK7语法 或自动关闭 connnection和channel
//创建连接
Connection connection = factory.newConnection();
//创建信道
Channel channel = connection.createChannel()) {
/**
* 队列名称
* 持久化配置:mq重启后还在
* 是否独占:只能有一个消费者监听队列;当connection关闭是否删除队列,一般是false,发布订阅是独占
* 自动删除: 当没有消费者的时候,自动删除掉,一般是false
* 其他参数
*
* 队列不存在则会自动创建,如果存在则不会覆盖,所以此时的时候需要注意属性
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/**
* 参数说明:
* 交换机名称:不写则是默认的交换机,那路由健需要和队列名称一样才可以被路由,
* 路由健名称
* 配置信息
* 发送的消息数据:字节数组
*/
for(int i=0;i<10;i++){
String message = "Hello World!"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
}
2.消费端
- 轮训策略 rr
package net.custompang.work.rr;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "work_mq_rr";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
// DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
// String message = new String(msg, "UTF-8");
// System.out.println(" [x] Received '" + message + "'");
// };
//自动确认消息
//channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
- 公平策略
package net.custompang.work.rr;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "work_mq_fair";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
/**
* 消费者每次消费1个,消费完成再消费另一个(实现公平策略)
*/
channel.basicQos(1);
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
// DeliverCallback deliverCallback = (consumerTag, envelop, delivery,properties, msg) -> {
// String message = new String(msg, "UTF-8");
// System.out.println(" [x] Received '" + message + "'");
// };
//自动确认消息
//channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
4.发布订阅模型(fanout)
1.生产端:
package net.custompang.pub;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* 生产者
*/
public class Send {
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
try ( //JDK7语法 或自动关闭 connnection和channel
//创建连接
Connection connection = factory.newConnection();
//创建信道
Channel channel = connection.createChannel()) {
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String msg = "rabbitmq发布任务";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes(StandardCharsets.UTF_8));
System.out.println("广播消息发送成功");
}
}
}
2.消费端:
package net.custompang.pub;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//获取队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列,fanout交换机不用routingkey
channel.queueBind(queueName,EXCHANGE_NAME,"");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(queueName,false,consumer);
}
}
5.Routing路由模式(direct)
和发布订阅类似
- 应用场景:日志采集,一个队列全部收集,其它队列指定收集某一种
1.生产端
package net.custompang.direct;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* 生产者
*/
public class Send {
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
try ( //JDK7语法 或自动关闭 connnection和channel
//创建连接
Connection connection = factory.newConnection();
//创建信道
Channel channel = connection.createChannel()) {
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String error = "error日志";
String info = "info日志";
String debug = "debug错误日志";
channel.basicPublish(EXCHANGE_NAME,"errorRoutingKey",null,error.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME,"infoRoutingKey",null,info.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME,"debugRoutingKey",null,debug.getBytes(StandardCharsets.UTF_8));
System.out.println("直连消息发送成功");
}
}
}
2.消费端
- 消费1
package net.custompang.direct;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//获取队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列,direct交换机需要指定routingkey
channel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey");
channel.queueBind(queueName,EXCHANGE_NAME,"debugRoutingKey");
channel.queueBind(queueName,EXCHANGE_NAME,"infoRoutingKey");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(queueName,false,consumer);
}
}
- 消费2
package net.custompang.direct;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv2 {
private final static String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//获取队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列,direct交换机需要指定routingkey
channel.queueBind(queueName,EXCHANGE_NAME,"errorRoutingKey");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(queueName,false,consumer);
}
}
6.Toipcs主体模式
和路由模式类似
1.生产端
package net.custompang.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
/**
* 生产者
*/
public class Send {
private final static String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
try ( //JDK7语法 或自动关闭 connnection和channel
//创建连接
Connection connection = factory.newConnection();
//创建信道
Channel channel = connection.createChannel()) {
//绑定交换机,fanout扇形,即广播
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String error = "error日志";
String info = "info日志";
String debug = "debug错误日志";
channel.basicPublish(EXCHANGE_NAME,"order.log.error",null,error.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME,"order.log.info",null,info.getBytes(StandardCharsets.UTF_8));
channel.basicPublish(EXCHANGE_NAME,"product.log.debug",null,debug.getBytes(StandardCharsets.UTF_8));
System.out.println("直连消息发送成功");
}
}
}
2.消费端
*:代表一个词
#:代表一个或多个词
- 消费1:
package net.custompang.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//获取队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列,topic交换机需要指定routingkey
channel.queueBind(queueName,EXCHANGE_NAME,"order.log.error");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(queueName,false,consumer);
}
}
- 消费2:
package net.custompang.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv2 {
private final static String EXCHANGE_NAME = "exchange_topic";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("8.131.119.145");
factory.setUsername("admin");
factory.setPassword("password");
factory.setVirtualHost("/dev");
factory.setPort(5672);
//消费者一般不增加自动关闭
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//绑定交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//获取队列
String queueName = channel.queueDeclare().getQueue();
//绑定交换机和队列,topic交换机需要指定routingkey
channel.queueBind(queueName,EXCHANGE_NAME,"*.log.*");
//回调方法,下面两种都行
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// consumerTag 是固定的 可以做此会话的名字, deliveryTag 每次接收消息+1
System.out.println("consumerTag消息标识="+consumerTag);
//可以获取交换机,路由健等
System.out.println("envelope元数据="+envelope);
System.out.println("properties配置信息="+properties);
System.out.println("body="+new String(body,"utf-8"));
//手工确认消息消费,不是多条确认
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(queueName,false,consumer);
}
}
四.Springboot2.x整合RabbitMQ3.8.9实战
1.简单实战(topics)
- 1.添加maven依赖
<properties>
<java.version>11</java.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 代码库 -->
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
- 2.配置yml文件
#消息队列
spring:
rabbitmq:
host: 8.131.119.145
port: 5672
virtual-host: /dev
password: password
username: admin
- 3.配置类
public static final String EXCHANGE_NAME = "order_exchange";
public static final String QUEUE_NAME = "order_queue";
/**
* topic交换机
* @return
*/
@Bean
public Exchange orderExchange() {
//durable是否持久化
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
//return new TopicExchange(EXCHANGE_NAME, true, false);
}
/**
* 队列
* @return
*/
@Bean
public Queue orderQueue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
/**
* 交换机和队列绑定关系
*/
@Bean
public Binding orderBinding(Queue queue, Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("order.#").noargs();
}
- 4.发送端
template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new","新订单");
- 5.消费端
@Component
@RabbitListener(queues = "order_queue")
public class OrderMQListener {
/**
*
* @param body 是什么类型都可以传,如果是某个类,也可以改成指定类
* @param message
* @param channel
*/
@RabbitHandler
public void messageHandler(Order body, Message message){
//消息的编号
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag="+deliveryTag);
System.out.println("message="+message.toString());
System.out.println("body="+body);
}
}
2.可靠性投递实战
RabbitMQ消息投递路径
生产者–>交换机->队列->消费者
通过两个的点控制消息的可靠性投递
生产者到交换机
通过confirmCallback
交换机到队列
通过returnCallback
建议
开启消息确认机制以后,保证了消息的准确送达,但由于频繁的确认交互, rabbitmq 整体效率变低,吞吐量下降严重,不是非常重要的消息真心不建议用消息确认机制
- 1.生产者到交换机 通过confirmCallback
#旧版,确认消息发送成功,通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调
spring.rabbitmq.publisher-confirms=true
#新版,NONE值是禁用发布确认模式,是默认值,CORRELATED值是发布消息成功到交换器后会触发回调方法
spring.rabbitmq.publisher-confirm-type: correlated
-
- yml配置
#开启消息二次确认生产者到broker的交换机,默认是none
publisher-confirm-type: correlated
-
- 发送端:
/**
* 处理发送者到交换机
*/
@Test
void testConfirmCallback(){
//配置发送端到broker交换机的可靠性
template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 配置
* @param ack 交换机是否收到消息,true是成功,false是失败
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback===>");
System.out.println("ConfirmCallback===>"+correlationData);
System.out.println("ConfirmCallback===>"+ack);
//成功时为null
System.out.println("ConfirmCallback===>"+cause);
if(ack){
System.out.println("发送成功");
//更新数据库消息的状态为成功 TODO
}else{
System.out.println("发送失败,记录到日志或数据库");
//更新数据库消息的状态为失败 TODO
}
}
});
//数据库新增一个消息记录,状态是发送 TODO
//发送消息
template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new","新订单");
}
- 2.交换机到队列 通过returnCallback
两种模式
交换机到队列不成功,则丢弃消息(默认)
交换机到队列不成功,返回给消息生产者,触发returnCallback -
- 配置yml
#开启消息二次确认,交换机到队列的可靠性投递,默认是false
publisher-returns: true
#交换机处理消息到路由失败,则会返回给生产者
template:
mandatory: true
-
- 生产端
/**
* 处理交换机到路由队列
*/
@Test
void testReturnCallback(){
template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
/**
* 正常情况不会被触发
* @param returnedMessage
*/
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
int code = returnedMessage.getReplyCode();
//TODO 写入数据库
System.out.println("code="+code);
System.out.println("returnedMessage="+returnedMessage.toString());
}
});
//数据库新增一个消息记录,状态是发送 TODO
//发送消息
template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"oo.order.new","新订单");
}
3.避免重复消费实战
开启手工确认
- 配置yml
#消息手工确认ACK,默认是自动
listener:
simple:
acknowledge-mode: manual
- 消费端
package com.example.rabbitmqdemo.mq;
import com.example.rabbitmqdemo.pojo.Order;
import com.rabbitmq.client.Channel;
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.io.IOException;
@Component
@RabbitListener(queues = "order_queue")
public class OrderMQListener {
/**
*
* @param body 是什么类型都可以传,如果是某个类,也可以改成指定类
* @param message
* @param channel
*/
@RabbitHandler
public void messageHandler(Order body, Message message, Channel channel) throws IOException {
//消息的编号
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag="+deliveryTag);
System.out.println("message="+message.toString());
System.out.println("body="+body);
try {
//告诉broker消息已经被确认并消费成功
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
//告诉broker,消息拒绝被确认,让broker重新发送,如果不重新入队就会变成死信
channel.basicNack(deliveryTag,false,true);
//告诉broker,消息拒绝被确认,让broker重新发送,只能一个一个,如果不重新入队就会变成死信
channel.basicReject(deliveryTag,true);
e.printStackTrace();
}
}
}
4.死信队列+延时消费介绍
-
什么是TTL
time to live 消息存活时间
如果消息在存活时间内未被消费,则会别清除 -
RabbitMQ支持两种ttl设置
单独消息进行配置ttl :容易出现问题(前面的消息堵塞后面的消息,使得不能过期)
整个队列进行配置ttl(居多) -
什么是rabbitmq的死信队列
没有被及时消费的消息存放的队列 -
什么是rabbitmq的死信交换机
Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机。
-
消息有哪几种情况成为死信
-
- 消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false
-
- 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live)
-
- 队列的消息长度达到极限
结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
- 队列的消息长度达到极限
RabbitMQ管控台消息TTL测试
队列过期时间使用参数,对整个队列消息统一过期
x-message-ttl
单位ms(毫秒)
消息过期时间使用参数(如果队列头部消息未过期,队列中级消息已经过期,已经还在队列里面)
expiration
单位ms(毫秒)
两者都配置的话,时间短的先触发
RabbitMQ Web控制台测试
新建死信交换机(和普通没区别)
新建死信队列 (和普通没区别)
死信交换机和队列绑定
新建普通队列,设置过期时间、指定死信交换机
测试:直接web控制台往product_qeueu发送消息即可
应用场景:
通过消息触发一些定时任务,比如在某一固定时间点向用户发送提醒消息
用户登录之后5分钟给用户做分类推送、用户多少天未登录给用户做召回推送;
消息生产和消费有时间窗口要求:比如在天猫电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条 延时消息。这条消息将会在 30 分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。 如支付未完成,则关闭订单。如已完成支付则忽略
5.死信队列+延时消费实战
- 配置类
/**
* 新商家审核通过->new_merchant_queue -> 死信交换机 -> lock_merchant_dead_queue
*/
/**
* 死信队列
*/
public static final String LOCK_MERCHANT_DEAD_QUEUE = "lock_merchant_dead_queue";
/**
* 死信交换机
*/
public static final String LOCK_MERCHANT_DEAD_EXCHANGE = "lock_merchant_dead_exchange";
/**
* 死信routingkey
*/
public static final String LOCK_MERCHANT_ROUTING_KEY = "lock_merchant_routing_key";
/**
* 创建死信交换机
* @return
*/
@Bean
public Exchange lockMerchantExchange(){
return new TopicExchange(LOCK_MERCHANT_DEAD_EXCHANGE,true,false);
}
/**
* 创建死信队列
* @return
*/
@Bean
public Queue lockMerchantDeadQueue(){
return QueueBuilder.durable(LOCK_MERCHANT_DEAD_QUEUE).build();
}
/**
* 绑定死信交换机和死信队列
* @return
*/
@Bean
public Binding lockMerchantBinding(){
return new Binding(LOCK_MERCHANT_DEAD_QUEUE,Binding.DestinationType.QUEUE,LOCK_MERCHANT_DEAD_EXCHANGE,LOCK_MERCHANT_ROUTING_KEY,null);
}
/**
* 普通队列,绑定死信交换机
*/
public static final String NEW_MERCHANT_QUEUE = "new_merchant_queue";
/**
* 绑定普通交换机
*/
public static final String NEW_MERCHANT_EXCHANGE = "new_merchant_exchange";
/**
* routingkey
*/
public static final String NEW_MERCHANT_ROUTING_KEY = "new_merchant_routing_key";
/**
* 创建普通交换机
* @return
*/
@Bean
public Exchange newMerchantExchange(){
return new TopicExchange(NEW_MERCHANT_EXCHANGE,true,false);
}
/**
* 创建普通队列绑定死信交换机
* 修改队列需要先删除原有的队列再创建
* @return
*/
@Bean
public Queue newMerchantQueue(){
Map<String,Object> args = new HashMap<>(3);
args.put("x-dead-letter-exchange",LOCK_MERCHANT_DEAD_EXCHANGE);
args.put("x-dead-letter-routing-key",LOCK_MERCHANT_ROUTING_KEY);
args.put("x-message-ttl",10000);//10s
return QueueBuilder.durable(NEW_MERCHANT_QUEUE).withArguments(args).build();
}
/**
* 绑定交换机和队列
* @return
*/
@Bean
public Binding newMerchantBinding(){
return new Binding(NEW_MERCHANT_QUEUE,Binding.DestinationType.QUEUE,NEW_MERCHANT_EXCHANGE,NEW_MERCHANT_ROUTING_KEY,null);
}
- 生产端
package com.example.rabbitmqdemo.controller;
import com.example.rabbitmqdemo.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RequestMapping("api/admin/merchant")
@RestController
public class MerchantController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("check")
public Object check(){
//修改数据库的商家账号状态
rabbitTemplate.convertAndSend(RabbitMQConfig.NEW_MERCHANT_EXCHANGE,RabbitMQConfig.NEW_MERCHANT_ROUTING_KEY,"商家账号通过审核");
Map<String,Object> map = new HashMap<>();
map.put("code",0);
map.put("msg","账号审核通过,请10秒内上传一个商品");
return map;
}
}
- 消费端
package com.example.rabbitmqdemo.mq;
import com.rabbitmq.client.Channel;
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.io.IOException;
@Component
@RabbitListener(queues = "lock_merchant_dead_queue")
public class MerchantMQListener {
@RabbitHandler
public void messageHandler(String body, Message message, Channel channel){
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag="+deliveryTag);
System.out.println("message="+message.toString());
System.out.println("body="+body);
try {
//告诉broker,消息已经被确认并成功
channel.basicAck(deliveryTag,false);
//告诉broker,消息拒绝确认并返回队列重复消费(可批量)
//channel.basicNack(deliveryTag,false,true);
//告诉broker,消息拒绝确认并返回队列重复消费
//channel.basicReject(deliveryTag,true);
} catch (IOException e) {
e.printStackTrace();
}
}
}