RabbitMQ学习记录
RabbitMQ基于AMQP(Advanced Message Queuing Protocol)协议,是一种消息代理和队列服务器。
RabbitMQ的作用
- 异步
- 生产者向RabbitMQ发送消息之后,继续执行之后的业务,消费者可以在适当的时候消费
- 削峰
- 当某时刻有大量的请求需要打到数据库时,为了保证数据库安全,使用RabbitMQ暂时存储消息,而消费者以一定的速率处理消息,RabbitMQ还可以实现负载均衡的功能,以一定的策略将消息分发给不同的消费者处理,降低服务器的压力。
- 解耦
- RabbitMQ作为中间件联系不同的服务模块,有一定的解耦作用
RabbitMQ!启动!
docker-compose
version: '3'
services:
rabbitmq:
image: rabbitmq:3.7.8-management # 原镜像`rabbitmq:3.7.8-management` 【 注:该版本包含了web控制页面 】
container_name: rabbitmq # 容器名为'rabbitmq'
hostname: my-rabbit
restart: unless-stopped # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器
environment: # 设置环境变量,相当于docker run命令中的-e
TZ: Asia/Shanghai
LANG: en_US.UTF-8
RABBITMQ_DEFAULT_VHOST: my_vhost # 主机名
RABBITMQ_DEFAULT_USER: admin # 登录账号
RABBITMQ_DEFAULT_PASS: zlpen9hth # 登录密码
volumes: # 数据卷挂载路径设置,将本机目录映射到容器目录
- "./rabbitmq/data:/var/lib/rabbitmq"
ports: # 映射端口
- "5672:5672"
- "15672:15672"
启动rabbitmq
docker-compose -f docker-compose-rabbitmq-3.7.8-management.yml -p rabbitmq up -d
# 进入容器
docker exec -it rabbitmq /bin/bash
# 启用延时插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 查看已安装插件
rabbitmq-plugins list
web管理端:ip地址:15672
MQ基础
数据隔离(Vitual host)
RabbitMQ之中有虚拟主机(Vituall host)的概念,用于隔离数据,不同虚拟主机下的数据互不影响。每个用户可以拥有多个虚拟主机,用户只能控制拥有虚拟主机下的MQ。
交换机(exchange)
通常向RabbitMQ之中发送消息,是向交换机中发送消息,交换机通过一定的策略与队列绑定,队列再与消费者绑定,从而消费者能够消费RabbitMQ之中的消息。
其中交换机根据类型分为以下几种交换机
- Direct exchange(直接交换机)
- 通过路由键(Routing key)绑定队列
- Fanout exchange(广播交换机)
- 将消息分发到全部与之绑定的队列
- Topic exchange(主题交换机)
- 主题交换机是使用最多的交换机,通过路由键绑定队列,与Direct exchange不同的是路由键可以是一个句点分隔的字符串,支持通配符
*(匹配一个单词)和#(匹配零个或多个单词)。
- 主题交换机是使用最多的交换机,通过路由键绑定队列,与Direct exchange不同的是路由键可以是一个句点分隔的字符串,支持通配符
- Headers Exchange(头交换机):
- 根据消息头属性进行路由,而不是根据路由键。可以匹配头属性的值和存在性。
队列(queue)
队列:一种先进先出的数据结构。
消息进入队列,被消费者消费后出队列。
RabbitMQ的Java客户端
rabbitmq被springboot所集成,所以使用springboot的版本。
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
修改配置文件
spring:
rabbitmq:
host: localhost
username: admin
password: admin
port: 5672
virtual-host: my_vhost
public class RabbitTemplate extends RabbitAccessor implements BeanFactoryAware, RabbitOperations, ChannelAwareMessageListener, ApplicationContextAware, ListenerContainerAware, PublisherCallbackChannel.Listener, BeanNameAware, DisposableBean
MQ高级
消息可靠性问题
在rabbitmq之中有三个角色,分别是生产者,mq服务端,消费者,无论这三者谁出现了问题,都会导致消息可靠性的问题。
生产者可靠性
- 生产者重试机制
connection-timeout: 1s # MQ的连接超时的时间
template:
retry:
enabled: true # 开启MQ的连接重试机制
initial-interval: 1000ms # 失败后重试的等待时间
multiplier: 2 # 重试的等待时间的倍数,下次失败后等待时长 = initial-interval * multiplier
max-attempts: 4 # 最大连接次数,超过这个次数就不再重试
这里最大连接次数为4,三次重连的等待时间依次是1s,2s,4s

==注意==
失败重试时是阻塞式等待,会阻塞当前线程继续执行后续的业务,建议禁用重试机制
-
生产者确认机制
-
消息投递到MQ,但是路由失败,返回
ACK,告知投递成功 -
临时消息投递到MQ,并且入队成功,返回
ACK -
持久消息投递到MQ,入队并完成持久化,返回
ACK -
其他情况返回NACK
-
spring:
rabbitmq:
publisher-confirm-type: correlated # 启用生产者确认
配置RabbitTemplate的回调机制
package com.forzlp.cloud.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class RabbitMQConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
rabbitTemplate.setConfirmCallback(
(correlationData, ack, cause) -> {
if (ack) {
// 消息投递成功
log.debug("消息投递成功");
} else {
// 消息投递失败
log.info("消息投递失败" + cause);
}
});
}
}
MQ可靠性
默认情况下,RabbitMQ中的消息都是持久化的(重启MQ后依旧还在)。
持久化的过程:当生产者向RabbitMQ中发送消息,消息会先写入内存,然后从内存中写入到磁盘。
==容易崩掉==,试了一下,2核2G的服务器发送消息三十万条消息,内存直接爆掉,服务器宕机
Lazy queue
lazy queue(惰性队列)的持久化方式:所有消息直接写入磁盘而不经过内存
优点:
- 延迟加载消息:当消息进入lazy queue时,它们不会立即被加载到内存中,而是保存在磁盘上。这有助于减轻队列对内存的压力,适用于处理大量消息的场景。
- 按需加载消息:当消费者需要消费消息时,RabbitMQ会将消息从磁盘加载到内存中再传递给消费者。这种按需加载的方式可以有效地管理资源使用。
- 更好的可靠性:由于消息被保存在磁盘上,即使节点宕机,消息也不会丢失。这提高了队列的可靠性。
- 更高的吞吐量:因为不需要将所有消息一次性加载到内存中,lazy queue可以处理更高的消息吞吐量。
配置lazy queue
-

-
通过Java代码
@RabbitListener(bindings = @QueueBinding( value = @Queue( name = "lazy.queue2", durable = "true", arguments = @Argument(name = "x-queue-mode", value = "lazy") ), exchange = @Exchange(name = "lazy.exchange", type = ExchangeTypes.FANOUT) )) public void listenLazyQueue(String msg) { System.out.println("lazy.queue" + msg); }
配置为lazy queue之后,直接写入到磁盘之中,不经过内存,速度很快。

优点是我的服务器没有爆掉了。
消费者可靠性
消费者在处理消息后会被MQ回执,告诉消息处理的状态,分别有三种类型的回执
-
ack: MQ收到后会从队列中删除消息
-
nack: MQ收到后会再次投递消息
-
reject: MQ收到后会删除消息(代表消息有问题,消费者拒收)
SpringAMQP已经实现了消费者确认机制,只需要我们修改配置文件即可,默认机制是none
spring:
rabbitmq:
listener:
simple:
acknowledge-mode:
消费者确认机制:
- none: 消费者收到消息后立即返回ack,不建议使用,当出现异常时会造成消息的丢失
- mannul: 手动配置确认机制
- auto: 使用spring的确认机制
- 正常处理ack
- 异常处理nack
- 校验异常reject
当配置auto机制之后,消费者端出现异常,会返回nack,此时消息返回到MQ之中,MQ再次投递消息,消费者端再次出现异常,此时会陷入无限的循环之中,为了避免这种情况。我们可以开启消费者重试机制
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto
retry:
enabled: true
消费者重试机制
当消息失败后,不会立即向MQ返回nack,而是会在本地进行重试,重试后在根据一定的策略返回给MQ。
- RejectAndDontRequeueRecoverer: 重试耗尽后,直接reject, 丢弃消息,默认重试机制
- ImmediateRequeueMessageRevoverer: 重试耗尽后,重新入队
- RepublishMessageRecoverer: 重试耗尽后,将失败消息投递到指定交换机(推荐使用)
配置RepublishMessageRecoverer机制
package com.forzlp.cloud.config;
import lombok.AllArgsConstructor;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Description: Rabbit消费者失败重试机制配置
*/
@Configuration
@AllArgsConstructor
// 开启这个配置时,这个配置类才会生效
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")
public class RabbitMQConsumerRetryConfig {
private RabbitTemplate rabbitTemplate;
@Bean
public DirectExchange errorExchange() {
return new DirectExchange("error.exchange");
}
@Bean
public Queue errorQueue() {
return new Queue("error.queue");
}
@Bean
public Binding errorBinding(DirectExchange errorExchange, Queue errorQueue) {
return BindingBuilder
.bind(errorQueue)
.to(errorExchange)
.with("error");
}
@Bean
public MessageRecoverer messageRecoverer() {
return new RepublishMessageRecoverer(rabbitTemplate, "error.exchange", "error");
}
}
消费幂等性问题
幂等性问题就是当消费者重复消费的问题。
-
我们可以给消息设置一个唯一ID,用于判断重复的消息并且放弃处理
-
@Bean public MessageConverter jsonMessageConverter() { Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter(); jjmc.setCreateMessageIds(true); return jjmc; }
-
-
业务层面解决
- 消费消息时,查询数据库判断是否已经消费过。
延迟消息
延迟消息: 生产者发送消息时指定一个时间,消费者不会立即消费消息,而是在指定时间之后才消费消息
RabbitMQ中的延迟消息主要用于以下几种业务场景:
- 定时任务:
- 将需要定时执行的任务发送到延迟队列中,在指定的时间后自动被消费。
- 例如:定期发送邮件提醒、定期清理过期数据等。
- 预定功能:
- 用户预定某个服务或商品,系统将预定信息发送到延迟队列中,在预定时间到达时触发相关的业务逻辑。
- 例如:酒店房间预订、会议室预订等。
- 订单支付超时处理:
- 用户下单后,系统将订单信息发送到延迟队列中,如果在指定时间内未支付,则自动取消订单。
- 可以避免订单长时间占用系统资源。
死信队列
死信:MQ之中的消息在以下几种情况下会成为死信
- 消费者返回reject或nack,并且队列配置requeue为false
- 消息经过了过期时间还没有被消费
- 队列中消息满了,最早的消息会成为死信
死信在默认情况下会被直接丢弃,但是我们可以给队列配置一个死信交换机,将死信传递到死信交换机之中。