学习内容:
- Rabbitmq的架构
- Rabbitmq的消息模型
- Rabbitmq交换机类型
- Rabbitmq死信队列
- Rabbitmq延迟队列
一、Rabbitmq的架构
二、Rabbitmq的消息模型、Rabbitmq交换机类型
Ⅰ.简单队列模式(会使用默认的交换机)
看似是消费者直接将消息发送到队列中,实际上使用了默认的交换机,只有一个消费者,消费队列
交换机类型:直连交换机
Ⅱ.工作队列模式(会使用默认的交换机)
存在多个消费者消费队列中的消息,提升消费能力,防止消息堆积
交换机类型:直连交换机
Ⅲ.发布订阅模式
①广播
消费者将消息通过交换机路由到所有与之绑定的队列中,每个队列中的消费者消费消息
交换机类型:扇形交换机
②路由
交换机会将消息转发给指定的队列
交换机类型:定向交换机
③主题
交换机会将消息转发给符合通配符的队列
交换机类型:主题交换机
三、Rabbitmq死信队列
死信队列:死信队列中会存储死信消息
什么情况下消息会成为死信消息?
- 消息过期:消息设置过期时间后,如果没有被消费者消费,rabbitmq会将消息发送到死信队列。
- 消息被拒绝:消息被消费者消费时,无法处理该消息,此时可以将消息拒绝,将requeue设置为false。
- 队列达到最大长度:队列达到最大长度,无法发送到队列。
四、Rabbitmq延迟队列
rabbitmq中没有专门的延迟队列,可以通过ttl过期时间 + 死信队列实现延迟队列.
生产者发送消息到队列中,当消息存活时间过期后,消息会被死信交换机放入到死信队列,最后在被消费者消费,达到实现延迟队列的效果。
五、Rabbitmq的高级特性-消息的可靠传输(保证消息不丢失)
消息传输的三个阶段,每个阶段都可能发生消息的丢失:
- 生产者发送消息到丢列
- rabbitmq存储消息
- 消费者消费消息:由于自动应答的情况,消费者在接收到消息后,会返回一个ack,此时rabbitmq会将消息直接从rabbitmq中删除,如果此时消费者出现宕机,消息就会丢失。
生产者保证消息不丢失:
生产者回退机制
回退机制
rabbitmq宕机:
消息的持久化、交换机的持久化、队列的持久化
消费者导致消息丢失:
开启手动应答:在消费者处理完消息后,手动告知 RabbitMQ 该消息已被成功处理并可以从队列中删除
自定义RabbitTemplate
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class RabbitMqConfiguration {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
//创建rabbitTemplate对象
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
//设置confirmCallBcak
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息正常发送给了交换机");
} else {
log.info("消息没有正常发送给交换机,{}", cause); //cause失败原因
}
}
});
//设置returnCallBcak
rabbitTemplate.setMandatory(true); //让mq服务端把消息返回
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("{}, {}, {}, {}, {}", message.toString(), replyCode, replyText, exchange, routingKey);
log.info("发送给队列失败");
}
});
return rabbitTemplate;
}
}
publisher-confirm-type: correlated # 开启生产者确认机制 publisher-returns: true # 开启生产者回退机制
自定义重试次数
package com.atguigu.gmall.rabbit.service.impl;
import com.atguigu.gmall.rabbit.constant.RabbitConstant;
import com.atguigu.gmall.rabbit.service.RabbitBizService;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.DigestUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 消息的有限重试
*/
@Slf4j
public class RabbitBizServiceImpl implements RabbitBizService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void retry(Channel channel, Long deliveryTag, String messageContent, int count) {
//获取内容的md5加密
String digestAsHex = DigestUtils.md5DigestAsHex(messageContent.getBytes(StandardCharsets.UTF_8));
//从redis中获取剩余重试次数
Long consumeCount = redisTemplate.opsForValue().increment(RabbitConstant.CONSUME_COUNT + digestAsHex);
if (consumeCount <= 5) {
//重试次数小于5,继续重试
try {
channel.basicNack(deliveryTag, true, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
//重试次数大于5次,
try {
//存入数据库
log.info("存入数据库。。。。。");
channel.basicAck(deliveryTag, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
//redi中删除
redisTemplate.delete(RabbitConstant.CONSUME_COUNT + digestAsHex);
}
}
}