一、介绍
RabbitMq是一个消息中间件,用于流量削峰,业务解耦等场景
二、安装
2.1、物理机安装
2.1.1、下载Erlang
2.1.2、安装Erlang
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
2.1.3、下载RabbitMQ
2.1.3、安装RabbitMQ
下载socat插件
yum install socat -y
安装RabbitMQ
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
2.1.4、启动RabbitMQ
# 启动服务
systemctl start rabbitmq-server
# 查看服务状态
systemctl status rabbitmq-server
# 开机自启动
systemctl enable rabbitmq-server
# 停止服务
systemctl stop rabbitmq-server
# 重启服务
systemctl restart rabbitmq-server
2.1.5、安装WEB页面
rabbitmq-plugins enable rabbitmq_management
2.2、docker安装
2.2.1、安装RabbitMQ
docker run -d --name myRabbitMQ -e RABBITMQ_DEFAULT_USER=用户名 -e RABBITMQ_DEFAULT_PASS=密码 -p 15672:15672 -p 5672:5672 rabbitmq:3.8.14-management
2.3、访问WEB页面
默认用户名密码为guest
三、集成
3.1、Maven集成
3.1.1、引入POM依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
3.1.2、编写简单生产者
package com.xx.producer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe 生产者
* @since 2023/5/25 10:55
*/
public class Producer {
/**
* topic
*/
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("ip");
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
Connection connection = connectionFactory.newConnection();
//获取信道
Channel channel = connection.createChannel();
/*
创建队列
参数
1、队列名称
2、是否持久化
3、该队列是否进行消费共享,提供给多个消费者消费
4、是否自动删除,最后一个消费者断开连接之后,是否自动删除
5、其他参数
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
/*
发送消息
1、交换机
2、路由的key值/队列的名称
3、其他参数
4、消息体
*/
channel.basicPublish("", QUEUE_NAME, null, "hello rabbit mq".getBytes());
System.out.println("消息发送完毕...");
}
}
3.1.3、编写简单消费者
package com.xx.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe
* @since 2023/5/25 16:53
*/
public class Consumer {
/**
* topic
*/
public static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("ip");
connectionFactory.setUsername("root");
connectionFactory.setPassword("root");
Connection connection = connectionFactory.newConnection();
// 获取信道
Channel channel = connection.createChannel();
// 声明接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println(new String(message.getBody()));
};
// 取消消息时的回调
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息消费被中断");
};
/*
消费
1.消费队列
2.是否自动提交
3.消费未成功地回调
4.消费者取消消费的回调
*/
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
3.1.4、工作队列
RabbitMQ中当一个生产者对应多个消费者,消费者轮询消费,相当于Kafka中的消费者组
3.1.5、交换机
交换机分为:直接(direct)、主题(topic)、标题(headers)、扇出(fanout)、默认(exchange)等模式
3.1.5.1、fanout模式
介绍:其实就是发布订阅模式,将收到的所有消息广播到它知道的所有队列中(所有消费者都会接收到消息),当一个队列绑定的是#号,那么所有队列都会接收这个消息
3.1.5.1.1、生产者
package com.xx.exchange.fanout;
import com.rabbitmq.client.Channel;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe 发布订阅-生产者
* @since 2023/5/29 14:06
*/
public class FanoutProducer {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机(exchange:交换机名称,type:交换机类型)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String next = scanner.next();
channel.basicPublish(EXCHANGE_NAME, "", null, next.getBytes());
System.out.println("生产者发送消息:" + next);
}
}
}
3.1.5.1.2、消费者
package com.xx.exchange.fanout;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe 发布订阅-消费者
* @since 2023/5/29 14:06
*/
public class FanoutConsumer1 {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个临时队列(临时队列:名称随机,当消费者断开连接时自动删除)
String queue = channel.queueDeclare().getQueue();
// 绑定交换机和队列(queue:队列名称,exchange:交换机名称,routingKey:用于绑定的路由密钥)
channel.queueBind(queue, EXCHANGE_NAME, "");
// 消费
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息消费异常");
};
channel.basicConsume(queue, true, deliverCallback, cancelCallback);
}
}
3.1.5.2、direct模式
介绍:消息只去到它绑定的 routingKey 队列中,当然也可以设置相同的routingKey ,产生和fanout模式一样的效果,没有绑定关系则会被丢弃
3.1.5.2.1、生产者
package com.xx.exchange.direct;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe
* @since 2023/5/29 14:37
*/
public class DirectProducer {
public static final String EXCHANGE_NAME = "logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机(exchange:交换机名称,type:交换机类型)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明一个队列
channel.queueDeclare("disk", false, false, false, null);
// 绑定交换机和队列
channel.queueBind("disk", EXCHANGE_NAME, "error");
// 声明一个队列
channel.queueDeclare("console", false, false, false, null);
// 绑定交换机和队列
channel.queueBind("console", EXCHANGE_NAME, "warn");
// 绑定交换机和队列
channel.queueBind("console", EXCHANGE_NAME, "info");
Map<String, String> message = new HashMap<>();
message.put("info", "info消息");
message.put("error", "error消息");
message.put("warn", "warn消息");
message.put("other", "other消息");
message.forEach((k, v) -> {
try {
channel.basicPublish(EXCHANGE_NAME, k, null, v.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
3.1.5.2.2、消费者
error队列消费者
package com.xx.exchange.direct;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe error队列消费者
* @since 2023/5/29 14:36
*/
public class DirectErrorConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 消费
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息消费异常");
};
channel.basicConsume("error", true, deliverCallback, cancelCallback);
}
}
console队列消费者
package com.xx.exchange.direct;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe console队列消费者
* @since 2023/5/29 14:36
*/
public class DirectConsoleConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 消费
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("消息消费异常");
};
channel.basicConsume("console", true, deliverCallback, cancelCallback);
}
}
3.1.5.3、topic模式
介绍:最为灵活,可是使用通配符来设置routingKey
3.1.5.3.1、消费者
消费者1
package com.xx.exchange.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe topic类型消费者
* @since 2023/5/29 15:31
*/
public class TopicQ1Consumer {
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 声明队列
channel.queueDeclare("Q2", false, false, false, null);
// 交换机绑定队列
channel.queueBind("Q2", EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind("Q2", EXCHANGE_NAME, "lazy.#");
// 消费
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收队列:Q2," + "绑定routingKey:" + message.getEnvelope().getRoutingKey() + ",接收消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> System.out.println("消息消费异常");
channel.basicConsume("Q2", true, deliverCallback, cancelCallback);
}
}
消费者2
package com.xx.exchange.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe topic类型消费者
* @since 2023/5/29 15:31
*/
public class TopicQ2Consumer {
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 声明队列
channel.queueDeclare("Q1", false, false, false, null);
// 交换机绑定队列
channel.queueBind("Q1", EXCHANGE_NAME, "*.orange.*");
// 消费
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("接收队列:Q1," + "绑定routingKey:" + message.getEnvelope().getRoutingKey() + ",接收消息:" + new String(message.getBody()));
};
CancelCallback cancelCallback = consumerTag -> System.out.println("消息消费异常");
channel.basicConsume("Q1", true, deliverCallback, cancelCallback);
}
}
3.1.5.3.2、生产者
package com.xx.exchange.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.xx.utils.RabbitMqUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author aqi
* @describe
* @since 2023/5/29 15:40
*/
public class TopicProducer {
public static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
// 声明一个交换机(exchange:交换机名称,type:交换机类型)
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit","被队列Q1Q2接收到");
bindingKeyMap.put("lazy.orange.elephant","被队列Q1Q2接收到");
bindingKeyMap.put("quick.orange.fox","被队列Q1接收到");
bindingKeyMap.put("lazy.brown.fox", "被队列Q2接收到");
bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列Q2接收一次");
bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配Q2");
bindingKeyMap.forEach((k, v) -> {
try {
channel.basicPublish(EXCHANGE_NAME, k, null, v.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
3.1.6、死信队列
介绍:无法被消费的消息,以下三种情况的数据会被认定为死信
1、消息 TTL 过期
2、队列达到最大长度
3、消息被拒绝
过期时间最好在生产者处配置,这样更加灵活,如果在队列处设置,那这个队列的过期时间就被定死了,不够灵活
3.1.6.1、设置死信队列的交换机和RoutingKey
Map<String, Object> arguments = new HashMap<>();
// 死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 死信RoutingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 声明队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
3.1.6.2、通过消息的过期时间来产生死信消息(TTL)
这种方式也可以叫做延迟队列,可以通过这种方式来实现某些延时业务,比如未支付订单的过期时间,或者是某些需要等待别的系统处理结果的场景
// 设置TTL时间(过期时间)
AMQP.BasicProperties props = new AMQP.BasicProperties().builder().expiration("10000").build();
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", props, "message".getBytes());
3.1.6.3、通过队列达到最大长度来产生死信消息
Map<String, Object> arguments = new HashMap<>();
// 死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 死信RoutingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 设置正常队列的长度
arguments.put("x-max-length", 6);
// 声明队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
3.1.6.4、通过消息被拒绝来产生死信消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String s = new String(message.getBody());
System.out.println("Consumer1接收到的消息:" + s);
// 如果接收到的消息满足某种条件,可以将其拒绝接收,然后投递到死信队列中,或者重新投递到原来的队列中,通过requeue这个参数设置,true:投递到原始队列,false:投递到死信队列
if (Objects.equals(s, "xxxx")) {
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
}
// 拒绝接收需要设置手动签收,自动签收则无法拒绝
else {
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
3.1.7、参数说明
3.1.7.1、创建交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, null);
参数 | 说明 |
---|---|
exchange | 交换机名称 |
type | 交换机类型,默认一共有四种类型:direct,fanout,topic,headers |
durable | 交换机是否持久化,true:在MQ重启之后保留,false:在MQ重启之后不保留 |
autoDelete | 交换机是否自动删除,true:当该交换机不再使用时将其删除,false:不删除 |
arguments | 其他参数 |
arguments参数详细说明,下面只包含部分常用的,所有的参数可以查看官网官网地址
参数 | 说明 |
---|---|
alternate-exchange | 备份交换机的名称,当消息无法路由到当前交换机时,会被转发到备份交换机 |
3.1.7.2、创建队列
channel.queueDeclare("disk", false, false, false, null);
参数 | 说明 |
---|---|
queue | 队列名称 |
durable | 队列是否持久化,true:在MQ重启之后保留,false:在MQ重启之后不保留 |
exclusive | 该队列是否声明为排他队列,如果设置为true只能被当前连接的通道使用,并在连接关闭时自动删除,并且不能设置为持久化队列 |
autoDelete | 队列是否自动删除,true:当该队列不再使用时将其删除,false:不删除 |
arguments | 其他参数 |
arguments参数详细说明,下面只包含部分常用的,所有的参数可以查看官网官网地址
参数 | 说明 |
---|---|
x-message-ttl | 消息 TTL(Time To Live),表示消息在队列中最多存活多长时间 |
x-dead-letter-exchange | 死信交换机的名称,当消息成为死信时,会被转发到此交换机 |
x-dead-letter-routing-key | 死信消息的路由键,当消息成为死信时,会被使用该路由键重新发送到死信队列中 |
x-max-length | 队列中允许存储的最大消息数目 |
x-max-length-bytes | 队列中允许存储的最大字节数 |
x-max-priority | 队列中支持的最大优先级数目 |
x-queue-mode | 队列模式,可取值为 lazy 或 default,分别表示懒惰模式和默认模式 |
3.1.7.3、绑定交换机和队列
注意:这里其实不仅可以绑定交换机和队列,也可以绑定交换机和交换机,将一个交换机的数据分发到多个交换机中
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "routingKey", null);
参数 | 说明 |
---|---|
queue | 队列名称 |
exchange | 交换机名称 |
routingKey | 路由key,交换机通过路由key来判断将消息发送给哪个队列 |
arguments | 其他参数 |
3.1.7.4、创建生产者
参数 | 说明 |
---|---|
exchange | 交换机,如果将其设置为空字符串,则表示使用默认的交换机 |
routingKey | 消息的路由键,用于将消息路由到指定的队列 |
mandatory | 当消息无法被路由到任何一个队列时,是否需要将消息返回给生产者 |
immediate | 当至少有一个消费者正在等待该队列的消息时,是否需要立即将消息传递给消费者 |
props | 设置消息的属性 |
body | 消息体 |
props参数详细说明
参数 | 说明 |
---|---|
contentType | 消息的内容类型,例如 text/plain、application/json 等 |
contentEncoding | 消息的编码方式,例如 UTF-8、ISO-8859-1 等 |
deliveryMode | 消息的持久化模式,可取值为 1 或 2,分别表示非持久化和持久化。如果将消息设置为持久化,则在消息发送到队列时会将其保存到磁盘上 |
expiration | 消息的 TTL(Time To Live),表示消息在队列中最多存活多长时间 |
priority | 消息的优先级,表示消息在队列中的优先级,可取值范围为 0 到 9,数字越大表示优先级越高 |
messageId | 消息的唯一标识符,用于跟踪消息 |
timestamp | 消息的时间戳,表示消息发送的时间 |
3.1.7.5、创建消费者
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);
参数 | 说明 |
---|---|
queue | 队列名称 |
autoAck | 是否自动提交 |
deliverCallback | 消费者接收到的消息 |
cancelCallback | 消费者取消消息订阅的回调 |
3.1.7.6、Channel中常用API说明
API | 说明 |
---|---|
basicPublish | 发送消息 |
basicConsume | 消费消息 |
basicAck | 手动确认消息 |
basicNack | 拒绝消息并重新将其放回队列中 |
basicReject | 拒绝消息并将其从队列中删除 |
basicQos | 设置消费者每次预取的消息数量 |
queueDeclare | 声明一个队列 |
exchangeDeclare | 声明一个交换机 |
queueBind | 将队列绑定到指定的交换机上 |
queueUnbind | 将队列从指定的交换机上解绑 |
3.2、SpringBoot集成
3.2.1、引入POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.2.2、修改配置文件
spring:
rabbitmq:
username: root
password: root
host: 192.168.133.200
port: 5672
3.2.3、创建交换机、队列、绑定
package com.xx.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;
import java.util.HashMap;
import java.util.Map;
/**
* @author aqi
* @describe
* @since 2023/5/30 11:17
*/
@Configuration
public class RabbitMqConfig {
/**
* 创建交换机,可以直接new一个交换机,也可以通过ExchangeBuilder来创建交换机
*/
@Bean("topicExchange")
public TopicExchange topicExchange() {
return new TopicExchange("topic.exchange");
}
@Bean("directExchange")
public DirectExchange directExchange() {
return new DirectExchange("direct.exchange");
}
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange() {
ExchangeBuilder fanoutExchange = ExchangeBuilder.fanoutExchange("fanout.exchange");
fanoutExchange.durable(true);
return fanoutExchange.build();
}
/**
* 自定义交换机(这里声明了一个延迟交换机)
*/
@Bean("delayedExchange")
public CustomExchange delayedExchange() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-delayed-type", "direct");
return new CustomExchange("delayed.exchange", "x-delayed-message", true, false, arguments);
}
/**
* 创建队列
*/
@Bean("queue")
public Queue queue() {
return QueueBuilder.durable("q").build();
}
/**
* 绑定交换机和队列
*/
@Bean
public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("topicExchange") TopicExchange topicExchange) {
return BindingBuilder.bind(queue).to(topicExchange).with("");
}
}
3.2.4、编写简单生产者
@Test
void send() {
rabbitTemplate.convertAndSend("topic.exchange", "", "hello springboot rabbitmq...");
}
3.2.5、编写简单消费者
@Test
@RabbitListener(queues = "q")
void listen(Message message, Channel channel) {
String msg = new String(message.getBody());
System.out.println("接收到的消息:" + msg);
}
3.2.6、SpringBoot提供对象说明
3.2.6.1、交换机
3.2.6.1.1、AbstractExchange
SpringBoot一共提供了如下5种交换机类型,包括:
CustomExchange(自定义交换机)、DirectExchange、FanoutExchange、HeadersExchange、TopicExchange
3.2.6.1.2、ExchangeBuilder
除了上面的方法可以创建交换机外,SpringBoot还提供了静态链式变成的方式声明交换机
3.2.6.2、队列
队列和交换机一样,也提供了两种创建方式
3.2.6.2.1、Queue
3.2.6.2.1、QueueBuilder
3.2.6.3、绑定交换机和队列
BindingBuilder.bind(queue).to(topicExchange).with("")
3.2.6.4、RabbitTemplate部分参数说明
由于RabbitTemplate中的API过多,这里只对部分进行说明
API | 说明 |
---|---|
send | 发送消息(只能发送Message对象) |
sendAndReceive | 发送消息并且返回消息(会等待消息返回) |
convertAndSend | 发送消息(可以发送任意数据) |
convertSendAndReceive | 发送消息并且返回消息(会等待消息返回) |
convertSendAndReceiveAsType | 发送消息并且返回指定类型消息(会等待消息返回) |
setMandatory | 是否开启返回回调 |
setConfirmCallback | confirm方式回调 |
setReturnsCallback | 事务回调 |
setReplyTimeout | 请求响应的超时时间,默认为 5 秒 |
setMessageConverter | 设置消息转换器 |
setExchange | 设置默认的交换机名称 |
setRoutingKey | 设置默认的路由键名称 |
3.2.6.4、@RabbitListener
参数 | 说明 |
---|---|
containerFactory | 指定监听器工厂,如果不指定则使用默认的工厂 |
queues | 监听队列名称(队列不存在会报错) |
queuesToDeclare | 监听队列名称(队列不存在自动创建) |
exclusive | 该队列是否声明为排他队列(默认为false),如果设置为true只能被当前连接的通道使用,并在连接关闭时自动删除,并且不能设置为持久化队列 |
priority | 消息的优先级,表示消息在队列中的优先级,数字越大表示优先级越高 |
bindings | 绑定交换机和队列 |
errorHandler | 用于指定处理消息消费过程中出现异常时的错误处理器,通过实现ErrorHandler接口,然后注入容器 |
concurrency | 消费者并发数 |
ackMode | ack模式(none:不提交ack,如果消费失败则数据直接丢失,auto:如果消费失败则重新进入队列,再次消费,配合retry等配置一起使用,避免无限重试,manual:手动提交ack) |
messageConverter | 消息转换器 |
3.2.6.4、配置文件部分配置说明
配置 | 说明 |
---|---|
spring.rabbitmq.host | 主机地址 |
spring.rabbitmq.port | 端口号 |
spring.rabbitmq.username | 用户名 |
spring.rabbitmq.password | 密码 |
spring.rabbitmq.virtual-host | 虚拟主机名称,默认值为 “/” |
spring.rabbitmq.connection-timeout | 连接超时时间,单位为毫秒,默认值为 60000 |
spring.rabbitmq.template.mandatory | 定义消息路由失败时候的策略,true:调用ReturnCallback,false:直接丢弃消息 |
spring.rabbitmq.publisher-confirm-type | 开启confirm机制,simple:同步等待confirm结果,直到超市,correlated:发布消息成功到交换器后会触发回调方法,none:禁用发布确认模式(默认为none) |
spring.rabbitmq.publisher-returns | 是否开启消息返回模式,默认值为 false |
spring.rabbitmq.listener.simple.retry.enabled | 是否开启费失败重试机制,默认值为false |
spring.rabbitmq.listener.simple.retry.initial-interval | 初次失败的等待时长,默认为10000ms |
spring.rabbitmq.listener.simple.retry.multiplier | 下次失败的等待时长倍数,默认为1 |
spring.rabbitmq.listener.simple.retry.max-attempts | 最大重试次数,默认为3 |
spring.rabbitmq.listener.simple.retry.stateless | true:无状态,false:有状态,如果业务中包含事务,则这里需要改为false,默认为true |
spring.rabbitmq.listener.simple.concurrency | 消费者最小线程数 |
spring.rabbitmq.listener.simple.max-concurrency | 消费者最大线程数 |
spring.rabbitmq.listener.simple.prefetch | 消费者预取值,消费者每次从队列中取多少的数据,如果过大会导致消费速度慢的消费者取过多的数据,默认值为1 |
spring.rabbitmq.listener.simple.consumer-batch-enabled | 是否开启批处理,默认为false |
spring.rabbitmq.listener.simple.batch-size | 每次消费者处理的消息数目,这里如果concurrency设置了5,batch-size设置了10,则表示这5个消费者线程每次处理10条消息 |
spring.rabbitmq.listener.type | 设置 RabbitMQ 消息监听器的类型,simple:简单监听器,每个连接只能启动一个消费者线程,但可以通过增加 concurrency 来提高并发处理能力,direct:直接消息监听器,可以同时启动多个消费者线程来处理消息,默认为simple |
spring.rabbitmq.template.reply-timeout | 指定请求响应的超时时间,单位为毫秒,默认值为 5000 |
四、补充
4.1、消费者消费失败(如何保证消费者成功消费)
4.1.1、原生方式
如果消费者设置为自动提交,可能消费者消费失败了,但是却被自动提交了ack,就会导致消息丢失,如果是手动提交,不论消费者发生了什么,只要没有提交ack,即使是挂掉了,也可以由别的消费者完成消费,所以一般情况下消费者消费使用手动提交
通过设置消费者的autoAck(true:自动提交,false:手动提交)参数
并且增加消费之后的手动提交代码channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
手动应答的其他API
// 肯定确认应答 消息的标记(每条消息都有一个唯一ID), 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
// 否定确认应答 消息的标记(每条消息都有一个唯一ID), 是否批量应答, true:重新入队列,false:丢弃或者进入死信队列
channel.basicNack(message.getEnvelope().getDeliveryTag(), false, false);
// 否定确认应答 消息的标记(每条消息都有一个唯一ID), true:重新入队列,false:丢弃或者进入死信队列
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
// 是否恢复消息到队列 true:重新入队列,并且尽可能的将之前 recover 的消息投递给其他消费者消费,而不是自己再次消费。false:消息会重新被投递给自己
channel.basicRecover(false);
4.1.2、SpringBoot方式
SpringBoot中的自动提交,通过配置文件来实现自动提交,如果重试次数耗尽,可以指定其后续策略,默认为直接丢弃
/**
* 开启充重试模式之后,重试次数耗尽,如果消息依然失败,则可以通过MessageRecoverer接口来处理
* 1、RejectAndDontRequeueRecoverer:重试耗尽之后,直接丢弃消息,默认为该方法
* 2、ImmediateRequeueMessageRecoverer:重试耗尽之后,返回nack,消息重新入队
* 3、RepublishMessageRecoverer:重试耗尽之后,将消息投递到指定的交换机
*/
@Bean
public MessageRecoverer republishMessageRecover(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error.routing.key");
}
SpringBoot中的手动提交,如果手动提交出现问题,ack一直没有提交会导致消息堆积在队列中
try {
int i = 1 / 0;
// 手动提交ack(如果不设置try-catch,这里会受到retry配置的影响,并且被投递到死信队列中)
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 出现异常,将消息重新投递到队列中(requeue=true标识重新投递,这里的重新投递不受retry重试机制影响,反之丢弃)
channel.basicNack(deliveryTag, false, false);
}
4.2、持久化(如何保证生产者发送消息不丢失)
生产者发送消息时可能会遇到各种问题最终导致消息并没有发送出去或者发送失败,通过以下3种方式确保消息可以不丢失
1、通过队列持久化保证MQ宕机之后队列不会被删除
2、通过消息持久化保证MQ宕机之后消息不会丢失
3、方式2可能会有中间时间,消息只持久化了一半,结果MQ宕机了,这时就需要采用发布确认机制保证消息不丢失,只有队列接收到消息之后并且持久化成功之后才会告诉生产者,这条消息持久化成功了
4.2.1、队列持久化
4.2.1.1、原生方式
创建队列时将durable参数设置为true,当mq宕机重启之后,该队列依旧存在
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
4.2.1.2、SpringBoot方式
SpringBoot创建持久化队列,执行durable方法实现持久化,但是SpringBoot其实默认队列就是持久化的
@Bean("queue")
public Queue queue() {
return QueueBuilder.durable("q").build();
}
持久化的队列Features显示为D
4.2.2、消息持久化
4.2.2.1、原生方式
生产者发送消息时将props参数设置为MessageProperties.PERSISTENT_TEXT_PLAIN,当mq当即重启之后,该数据依旧存在
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbit mq".getBytes());
4.2.2.2、SpringBoot方式
SpringBoot消息持久化,页面上消息显示delivery_mode=2时表示持久化,这里即使不这么发消息,SpringBoot默认消息也是持久化的
Message build = MessageBuilder.withBody("message".getBytes()).setDeliveryMode(MessageDeliveryMode.PERSISTENT).build();
页面效果
4.2.3、交换机持久化
4.2.3.1、原生方式
交换机持久化其实不会影响到消息是否丢失,因为交换机是不存储数据的,所以这里的持久化只会影响到交换机本身
原生的API通过设置durable持久化
4.2.3.2、SpringBoot方式
SpringBoot配置的参数和原生一样,但是SpringBoot默认就是持久化的
4.2.4、发布确认机制
生产者开启发布确认
4.2.4.1、单个确认
单个确认代码量最少,但是性能最差
开启发布确认:channel.confirmSelect();
等待队列确认:channel.waitForConfirms();
public static void publishMessageIndividually() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitMqUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
// 开启发布确认
channel.confirmSelect();
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "";
channel.basicPublish("", queueName, null, message.getBytes());
// 接收确认
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("消息发送成功");
}
}
}
4.2.4.2、批量确认
批量确认其实就是在单个确认的基础上手动调整了等待队列确认的频率,以此提高了性能,但是如果一批数据出现了异常,无法知道具体是那一条数据出现了问题
public static void publishMessageBatch() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitMqUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
// 开启发布确认
channel.confirmSelect();
// 批量确认消息大小
int batchSize = 100;
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "";
channel.basicPublish("", queueName, null, message.getBytes());
if (i % batchSize == 0) {
// 等待发送消息的确认
boolean flag = channel.waitForConfirms();
if (flag) {
System.out.println("消息发送成功");
}
}
}
}
4.2.4.3、异步确认
异步确认需要自己将消息存放起来,成功则删除,失败则重新发送,或者采用其他机制
注意:我在这里测试了很多次,发现ConfirmCallback不论是成功还是失败都不会回调所有的数据回来,会出现缺失的情况,暂时不知道是什么原因造成的
public static void publishMessageAsync() throws IOException, TimeoutException, InterruptedException {
Channel channel = RabbitMqUtils.getChannel();
String queueName = UUID.randomUUID().toString();
channel.queueDeclare(queueName, true, false, false, null);
// 开启发布确认
channel.confirmSelect();
// 用于存储全量的推送数据,key:消息的序列号,value:消息内容
ConcurrentSkipListMap<Long, String> outstandingConfirms = new ConcurrentSkipListMap<>();
// 消息发送成功回调
ConfirmCallback ackCallback = (deliveryTag, multiple) -> {
System.out.println("成功收到回调");
outstandingConfirms.remove(deliveryTag);
};
// 消息发送失败回调
ConfirmCallback nackCallback = (deliveryTag, multiple) -> {
System.out.println("异步未确认消息");
// 将消息重新发送一次再删除
String message = outstandingConfirms.get(deliveryTag);
System.out.println("消息key:" + deliveryTag + "消息体:" + message);
if (message != null) {
channel.basicPublish("", queueName, null, message.getBytes());
}
};
// 监听器,监听哪些消息发送成功了,哪些消息发送失败了
channel.addConfirmListener(ackCallback, nackCallback);
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "消息";
channel.basicPublish("", queueName, null, message.getBytes());
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
}
}
4.2.5、SpringBoot保证生产者消息不丢失
4.2.5.1、修改配置文件
分别开启confirm和return消息确认机制
4.2.5.2、生产者代码编写
void sendCallback() {
// confirm(可以保证消息到交换机,无法保证消息到达队列中)
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
correlationData.getFuture().addCallback(new ListenableFutureCallback<>() {
@Override
public void onFailure(Throwable ex) {
log.error("消息发送失败!!原因:", ex);
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()) {
log.info("消息成功发送到交换机!消息ID:{}", correlationData.getId());
} else {
log.error("消息发送到交换机失败!消息ID:{},失败原因:{}", correlationData.getId(), result.getReason());
}
}
});
// 事务(可以保证消息到了队列中)
rabbitTemplate.setReturnsCallback(returned -> {
Message message = returned.getMessage();
int replyCode = returned.getReplyCode();
String replyText = returned.getReplyText();
String exchange = returned.getExchange();
String routingKey = returned.getRoutingKey();
log.error("消息发送到队列失败,应答码:{}, 失败原因:{}, 交换机:{}, 路由键:{}, 消息:{}", replyCode, replyText, exchange, routingKey, message);
});
rabbitTemplate.convertAndSend("topic.exchange", "123", "message", correlationData);
}
也可以通过如下方法设置confirm回调
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息成功发送到交换机!消息ID:{}", correlationData.getId());
} else {
log.error("消息发送到交换机失败!消息ID:{},失败原因:{}", correlationData.getId(), cause);
}
});
4.3、不公平分发(如何解决消费者消费性能不一致)
多个消费者可能存在消费能力不一致的情况,默认的轮询消费可能会导致有些消费者积压有些消费者闲置,所以可以让效率低的减少工作量,让能力强的增加工作量,在消费者增加如下配置启用不公平分发,SpringBoot默认就是不公平分发
channel.basicQos(1);
页面上Prefetch count为0表示轮询分发,为1表示不公平分发
4.4、多个消费者如何同时消费一个队列中的数据
RabbitMq是没有消费者组这个概念的,所以需要创建多个队列,绑定这个交换机,以此来实现相同的数据被分发到不同的队列中,然后再消费不同的队列
4.5、如何保证顺序消费
RabbitMq没有broker的概念,所以无法像Kafka那样将相同ID的数据投放到同一个partition中
所以RabbitMq保证顺序消费的方式只能是每个队列都只有一个消费者,并且消费者不能并发消费,并且手动将相同ID的数据投放到一个队列中,以此来保证顺序消费
4.6、延迟队列顺序执行问题
由于消息的延迟消息的时间不一样,后入的数据延迟时间可能较短,但是也得等待前面的数据被执行之后才可以被处理,可以通过使用延迟消息插件来解决这个问题,延迟插件实际上就是将延迟放到了交换机中,而不是在生产者或者队列
五、WEB页面说明
5.1、总览
菜单 | 说明 |
---|---|
Overview | 展示 RabbitMQ 服务器的运行状态、队列数量、连接数和内存使用情况等概要信息 |
Connections | 展示当前 RabbitMQ 服务器中所有的客户端连接列表,包括客户端名称、IP 地址、协议和连接时间等信息。可以在此界面上断开不需要的连接,并监控客户端连接的状态 |
Channels | 展示当前 RabbitMQ 服务器中所有的通道列表,包括通道编号、所属连接和状态等信息。可以在此界面上关闭不需要的通道,并监控通道的状态 |
Exchanges | 展示当前 RabbitMQ 服务器中所有的交换机列表,包括交换机名称、类型、绑定信息和消息路由等属性。可以在此界面上创建、删除和编辑交换机,并监控交换机的运行状态 |
Queues | 展示当前 RabbitMQ 服务器中所有的队列列表,包括队列名称、消息数目、消费者数量、持久化策略和 TTL 等属性。可以在此界面上创建、删除和编辑队列,并监控队列的运行状态 |
Admin | 提供高级管理功能,包括用户权限管理、策略配置、插件管理和集群扩展等功能 |
5.1.1、Overview
菜单 | 说明 |
---|---|
Totals | 综合显示当前 RabbitMQ 服务器所有队列、交换器和连接等资源使用情况 |
Nodes | 展示当前节点的名称、类型和状态信息。如果使用了多个节点,则可以在这里查看各个节点的状态 |
Churn statistics | 前队列中消息的流动情况 |
Ports and contexts | 端口和上下文信息 |
Export definitions | 将服务器中的对象(如交换器、队列、绑定、用户等)导出为 JSON 格式的文件 |
Import definitions | 将先前导出的 JSON 文件导入到当前 RabbitMQ 服务器中,并选择是否覆盖现有的对象等选项。 |
5.1.1、Totals
菜单 | 说明 |
---|---|
Queued messages | 时间段内所有队列中的消息 |
Ready | 待消费的消息数量 |
Unacked | 待应答的消息数量 |
Total | 消息总数:Ready+Unacked |
Message rates | 时间段内所有队列的消费情况 |
Publish | 发布者发布消息的速率 |
Publisher confirm | 发布者confirm消息的速率 |
Deliver(manual ack) | |
Deliver(auto ack) | |
Consumer ack | |
Redelivered | |
Get(manual ack) | |
Get(auto ack) | 消费者自动确认的 |
Return | 发布者事务的速率 |
Disk read | 从磁盘读取消息的速率 |
Disk write | 队列写入磁盘的速率 |
Connections | 连接总数 |
Channels | 通道总数 |
Exchanges | 交换机总数 |
Queues | 队列总数 |
Consumers | 消费者总数 |
5.1.1、Nodes
菜单 | 说明 |
---|---|
Name | 节点名称 |
File descriptors | 当前节点打开的文件描述符和限制 |
Socket descriptors | 当前节点管理的网络套接字数量 |
Erlang processes | erlang启动的进程数量 |
Memory | 当前节点占用的内存大小 |
Disk space | 当前节点占用的磁盘空间 |
Uptime | 当前节点持续运行的时长 |
Reset stats | 重启操作 |
5.1.1、Ports and contexts
菜单 | 说明 |
---|---|
Connection operations | 某段时间内连接操作的速率 |
Channel operations | 某段时间内通道操作的速率 |
Queue operations | 某段时间内队列操作的速率 |
Created | 创建 |
Closed | 关闭 |
Deleted | 删除 |
5.1.2、Connections
菜单 | 说明 |
---|---|
Name | 客户端IP和端口 |
User name | 用户 |
State | 状态(running:运行中,idle:空闲) |
SSL/TLS | 是否使用ssl进行连接 |
Channels | channel总数 |
From client | 每秒发送的数据包 |
To client | 每秒收到的数据包 |
5.1.2、Channels
菜单 | 说明 |
---|---|
Channel | 通道IP和端口号 |
User name | 用户名 |
State | 状态(running:运行中,idle:空闲) |
Unconfirmed | 待confirm回调的消息总数 |
Prefetch | 设置的prefetch每次拉取的数量 |
Unacked | 待ack的消息数量 |
publish | 生产端 pub消息的速率 |
confirm | 生产端confirm回调的速率 |
unroutable (drop) | 路由的消息被直接删除的数量 |
deliver / get | 消费端获取消息的速率 |
ack | 消费端 ack消息的速率 |
5.1.3、Exchanges
5.1.3.1、All Exchanges
所有交换机列表
菜单 | 说明 |
---|---|
Name | 交换机名称 |
Type | 交换机类型 |
Features | 交换机配置(D:持久化,TTL:消息存在过期时间,Args:该交换机设置了额外参数) |
Message rate in | 消息输入速率 |
Message rate out | 消息输出速率 |
5.1.3.1、Add a new exchange
创建一个交换机
菜单 | 说明 |
---|---|
Name | 交换机名称 |
Type | 交换机类型 |
Durability | 交换机是否持久化(Durable:持久化,Transient:不持久化) |
Auto delete | 当没有消息的时候是否自动删除交换机 |
Internal | 是否是内部专用exchange |
Arguments | 拓展参数 |
5.1.4、Queues
5.1.4.1、All queues
所有队列列表
菜单 | 说明 |
---|---|
Name | 队列名称 |
Type | 队列类型(Classic:基础队列,先进先出,Quorum:拥抱队列,采用了分布式架构和多副本机制,以提高系统的可靠性和性能) |
Features | 队列的配置(D:持久化,TTL:消息存在过期时间,Args:该队列设置了额外参数) |
State | 当前的状态(running:运行中,idle:空闲) |
Ready | 待消费的消息数量 |
Unacked | 待应答的消息数量 |
Total | 总消息数量=Ready+Unacked |
incoming | 队列接收到消息的平均速率 |
deliver / get | 消息的平均传送速率 |
ack | 消息确认速率 |
5.1.4.2、Add a new queue
创建一个队列
菜单 | 说明 |
---|---|
Type | 队列类型(Classic:基础队列,先进先出,Quorum:拥抱队列,采用了分布式架构和多副本机制,以提高系统的可靠性和性能) |
Name | 队列名称 |
Durability | 队列是否持久化 |
Auto delete | 当队列不再使用时,是否自动删除 |
Arguments | 队列额外配置 |
5.1.5、Admin
用户信息