1. Exchange
fanout:把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中direct:把消息路由到BindingKey和RoutingKey完全匹配的队列中topic:匹配规则:
-
- RoutingKey 为一个 点号
.分隔的字符串。 比如: java.xiaoka.show - BindingKey和RoutingKey一样也是点号
.分隔的字符串 - BindingKey可使用 * 和 # 用于做模糊匹配,*匹配一个单词,#匹配多个或者0个
- RoutingKey 为一个 点号
headers:不依赖路由键匹配规则路由消息。是根据发送消息内容中的headers属性进行匹配,性能差,基本用不到
2. Listenter
@org.springframework.amqp.rabbit.annotation.RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "direct.ms", declare = "true"),
exchange = @Exchange(name = "sun.direct", type = ExchangeTypes.DIRECT, declare = "true"),
key = {"green"}
))
3. 消息转换器 MessageConverter
- 默认消息转换器使用(jdk)
SimpleMessageConverter - 自定义转换器使用(jackson)
Jackson2JsonMessageConverter,消费者、生产者需要配置相同消息转换器,防止消息转换出错
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 配置并返回一个MessageConverter实例,用于消息的转换
* 该方法使用了Spring的@Bean注解,表明它负责创建并配置一个Bean
* (在这里是MessageConverter的实例)供Spring容器管理
* @return MessageConverter 实例,用于将消息转换为JSON格式
*/
@Bean
public MessageConverter messageConverter() {
// 创建一个Jackson2JsonMessageConverter实例,用于将消息转换为JSON格式
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
// 设置converter以自动生成消息ID
converter.setCreateMessageIds(true);
// 返回配置好的MessageConverter实例
return converter;
}
4. 发送者可靠性
4.1. 发送者重连机制
spring:
rabbitmq:
connection-timeout: 1s # 设置连接超时时间
template:
retry:
enabled: true # 开始超时重试机制
initial-interval: 1000ms # 失败后初始等待时间
max-attempts: 3 # 最大重试次数
multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长=initial-interval*multiplier
# springAQMP 消息重试机制是阻塞式的重试,多次重试时会阻塞当前线程,影响性能。
# 在业务性能有要求时,需要禁用重试机制。当业务不得不使用时,需合理配置等待时长和重试次数,或考虑异步线程执行消息发送。
4.2. 发送者确认机制
springAMQP 提供Publisher Confirm 和Publisher Return 两种机制,开启确认机制后,当发送者发送消息给MQ后,MQ会返回确认结果给发送者,有以下情况:
- 消息投递MQ,路由失败。此时通过PublisherReturn 返回失败原因,然后返回ACK,告知投递成功(投递成功MQ已经接收到消息投递的请求,但路由分发失败)
- 临时消息(no durable queue)投递MQ,且入队成功,返回ACK,告知投递成功
- 持久消息(durable queue)投递MQ,且入队完成持久化(写入磁盘),返回ACK,告知投递成功
- 其他情况返回NACK,告知投递失败
其中ack和nack属于Publisher Confirm机制,ack是投递成功;nack是投递失败。而return则属于Publisher Return机制。 默认两种机制都是关闭状态,需要通过配置文件来开启。
spring:
rabbitmq:
# 开启PublisherConfirm机制,设置confirm类型(SIMPLE,CORRELATED,NONE)
publisher-confirm-type: correlated
# 开启PublisherReturn机制
publisher-returns: true
publisher-confirm-type有三种模式:
- none:关闭confirm机制
- simple:同步阻塞等待MQ的回执消息
- correlated:MQ异步回调方式返回回执消息
示例:
// 创建一个CorrelationData对象,用于确认消息是否发送成功
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
// 为CorrelationData对象的Future添加回调,处理消息发送成功或失败的情况
correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
// 发送失败时执行的方法
@Override
public void onFailure(Throwable ex) {
log.error("send fail", ex);
}
// 发送成功时执行的方法
@Override
public void onSuccess(CorrelationData.Confirm result) {
if (result.isAck()){
log.info("send success");
}else{
log.error("send fail");
log.error("cause: {}", result.getReason());
}
}
});
// 发送消息到指定的队列中,这里使用convertAndSend方法,参数分别为交换机名称、路由键、消息内容和CorrelationData对象
rabbitTemplate.convertAndSend("sun.direct", "red", "hello rabbitmq red", correlationData);
5. MQ可靠性
5.1. 数据持久化
RabbitMQ 实现持久化三个方面:
- 交换机持久化(默认)
- 消息队列持久化(默认) *
- 消息持久化(单独指定,springAMQP 默认消息持久化):设置Delivery 属性
-
- MessageDeliveryMode.NON_PERSISTENT(枚举值为
1):非持久化 - MessageDeliveryMode.PERSISTENT(枚举值 为
2):持久化
- MessageDeliveryMode.NON_PERSISTENT(枚举值为
SpringAMQP 非持久化设置:
String exchangeName = "sun.direct";
String msg = "hello rabbitmq";
Message message = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT)
.build();
rabbitTemplate.convertAndSend(exchangeName, message);
- 消息非持久化在内存写满之后会产生阻塞,内存数据写入磁盘,写入速度严重下降
- 消息持久化消息每次写入都会写入磁盘,不会产生阻塞请求,写入速度稳定(缺点:并发有所下降)
5.2. Lazy Queue
从RabbitMQ3.6.0版本开始,引入Lazy Queue概念(延迟队列)
延迟队列特征:
- 接收到消息后直接写入磁盘,不在写入内存
- 消费者需要消费消息时从磁盘中读取并加载到内存中(可以提前缓存部分消息到内存中,最多2048条)
注意:在3.12版本后,所有队列均是Lazy Queue模式,无法更改
注:3.12之前如下设置:
@org.springframework.amqp.rabbit.annotation.RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "queue", declare = "true", arguments = @Argument(name = "x-queue-mode", value = "lazy")),
exchange = @Exchange(name = "exchange", type = ExchangeTypes.DIRECT, declare = "true"),
key = {"email"}
)
)
设置队列模式为:lazy (@Argument(name = "x-queue-mode", value = "lazy"))
6. 消费者可靠性
6.1. 消费者确认机制
消费者确认机制(Consumer ACK)是为了确认消费者是否成功处理消息。当消费者处理消息结束后,应该向RabbitMQ发送回执,告知RabbitMQ消息处理状态:
- ack:消息处理成功,RabbitMQ从队列中删除该消息
- nack:消息处理失败,RabbitMQ需要再次投递消息
- reject:消息处理失败并拒绝该消息,RabbitMQ从队列中删除该消息
SpringAMQP已经实现消息确认机制,通过配置文件选择ACK处理方式,支持三种方式:
- none:不处理,消息投递给消费者后立即ack,消息会立刻从MQ删除。不安全,不建议使用
- manual:手动模式,需要业务代码中调用api,发送ack或reject,存在业务侵入,但灵活性高
- auto:自动模式,SpringAMQP利用AOP对消息处理做环绕增强,当业务正常执行时,自动返回ack
当业务执行异常时,根据异常判断返回不同结果:
- 若业务异常,自动返回nack
- 若消息处理异常或校验异常,自动返回reject
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto
6.2. 失败重试机制
SpringAMQP支持消费者失败重试机制,在消费者出现异常时使用本地重试,而非无限requeue到MQ。通过添加配置启用消费者本地重试机制:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启重试
initial-interval: 1000ms # 初始失败等待时长
max-attempts: 3 # 最大重试次数
multiplier: 1 # 重试间隔倍数
stateless: true # 是否无状态,业务中包含事务,则设置为false
在启用失败重试机制后,重试次数耗尽,若消息处理依然失败,需要在MessageRecoverer接口处理,存在三种不同实现:
- RejectAndDontRequeueRecoverer:重试次数耗尽,直接reject,丢弃消息。默认方法
- ImmediateRequeueMessageRecoverer:重试次数耗尽,返回nack,消息重新入队
- RepublishMessageRecoverer:重试次数耗尽,将失败消息投递指定交换机
修改失败处理策略为RepublishMessageRecoverer:
- 定义接收失败消息的交换机、队列及其绑定关系
- 定义RepublishMessageRecoverer
@Bean
public MessageRecoverer errorMessageRecoverer(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
6.3. 业务幂等性
同一个业务执行一次或多次对业务影响是一致的。
方案一:唯一消息id
- 每条消息生成一个唯一的id,与消息一起投递给消费者
- 消费者接收消息后处理自身业务,业务处理成功后将消息id存入数据库
- 若下次收到相同消息,去数据库查询判断是否存在,存在则视为重复消息放弃处理
@Bean
public MessageConverter messageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setCreateMessageIds(true);
return converter;
}
方案二:业务判断
具体方案需要基于业务指定策略
7. 延迟消息
延迟消息:发送者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才收到消息
延迟任务:设置在一定时间之后才执行的任务
方案一:死信交换机
当一个队列中的消息满足下列情况之一时,就会成为死信(dead letter):
- 消费者使用basic.reject或basic.nack声明消费失败,并且消息的requeue参数设置为false
- 消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费
- 要投递的队列消息堆积满了,最早的消息可能成为死信
若队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。该交换机称为死信交换机(Dead Letter Exchange,简称DLX)
实现示例:
@Bean
public Queue normalQueue() {
return QueueBuilder
// 持久化
.durable("normal.queue")
// 绑定死信交换机
.deadLetterExchange("dlx.direct").build();
}
//消息发送:
public void publishDlxQueue() {
String exchangeName = "normal.direct";
String msg = "hello rabbitmq dlx";
rabbitTemplate.convertAndSend(exchangeName, "dlx", msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
}
方案二:延迟消息插件
该插件可以将普通交换机改造为支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后载投递到队列。
示例一(监听器):
@org.springframework.amqp.rabbit.annotation.RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "delay.queue", declare = "true"),
exchange = @Exchange(name = "delay.direct", type = ExchangeTypes.DIRECT, declare = "true", delayed = "true"),
key = {"delay"}
)
)
public void listerDelayMessage(String msg) {
try {
log.info("delay message: " + msg + ", " + LocalDateTime.now());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
示例二(声明DirectExchange交换机):
@Bean
public DirectExchange delayExchange() {
return ExchangeBuilder
.directExchange("delay.direct")
//设置delayed属性,表示延迟交换机
.delayed()
//设置持久化
.durable(true)
.build();
}
消息发送:
public void delayQueue() {
String msg = "hello rabbitmq delay";
// 发送消息,利用消息后置处理器设置消息的过期时间
rabbitTemplate.convertAndSend("delay.direct", "delay", msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 设置消息的过期时间,单位为毫秒
message.getMessageProperties().setDelay(10000);
return message;
}
});
}