目录
2.1. 搭建demo工程
创建两个demo工程:
依赖相同:
2.1.1. 生产者
application.yml:
server:
port: 8080
spring:
rabbitmq:
host: 172.16.116.100
port: 5672
virtual-host: /fengge
username: fengge
password: fengge
publisher-confirm-type: simple # SIMPLE-同步确认(阻塞) CORRELATED-异步确认
publisher-returns: true # 确认消息是否到达队列
RabbitConfig配置类:配置发送方确认
@Configuration
@Slf4j
public class RabbitConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
// 确认消息是否到达交换机
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack){
log.warn("消息没有到达交换机:" + cause);
}
});
// 确认消息是否到达队列,到达队列该方法不执行
this.rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.warn("消息没有到达队列,来自于交换机:{},路由键:{},消息内容:{}", exchange, routingKey, new String(message.getBody()));
});
}
}
ProducerDemoApplicationTests测试用例发送消息
@SpringBootTest
class ProducerDemoApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {//Exchange队列所绑定的交换机:SPRING_RABBIT_EXCHANGE,Routing Key队列和交换机绑定的路由键:a.b
this.rabbitTemplate.convertAndSend("SPRING_RABBIT_EXCHANGE", "a.b", "hello spring rabbit!");
}
}
2.1.2. 消费者
application.yml:
server:
port: 8081
spring:
rabbitmq:
host: 172.16.116.100
port: 5672
virtual-host: /fengge
username: fengge
password: fengge
listener:
type: simple # simple-listener容器使用一个额外线程处理消息 direct-listener(监听器)容器直接使用consumer线程
simple:
acknowledge-mode: manual # manual-手动 auto-自动(无异常直接确认,有异常无限重试) none-不重试
prefetch: 1 # 能者多劳
concurrency: 3 # 避免消息堆积,初始化多个消费者线程
ConsumerListener消费者代码:
@Component
public class ConsumerListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "SPRING_RABBIT_QUEUE", durable = "true"),
exchange = @Exchange(value = "SPRING_RABBIT_EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
key = {"a.b"}
))
public void listener(String msg, Channel channel, Message message) throws IOException {
try {
System.out.println(msg);
// 手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
// 是否已经重试过
if (message.getMessageProperties().getRedelivered()){
// 已重试过直接拒绝
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} else {
// 未重试过,重新入队
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
}
注解说明:
@Componet
:类上的注解,注册到Spring容器@RabbitListener
:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:bindings
:指定绑定关系,可以有多个。值是@QueueBinding
的数组。@QueueBinding
包含下面属性:value
:这个消费者关联的队列。值是@Queue
,代表一个队列exchange
:队列所绑定的交换机,值是@Exchange
类型key
:队列和交换机绑定的RoutingKey
类似listen这样的方法在一个类中可以写多个,就代表多个消费者。
2.2. 延时队列及死信队列
2.2.1. 概念回顾
延时队列:
如果队列不设置TTL,表示消息永远不会过期
如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃
死信队列 : 如果队列里的消息出现以下情况:
- 消息被否定确认,使用
channel.basicNack
或channel.basicReject
,并且此时requeue
属性被设置为false
。 - 消息在队列的存活时间超过设置的TTL时间。
- 消息队列的消息数量已经超过最大队列长度。
那么该消息将成为“死信”。
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
一般死信队列和延时队列一起使用,使用场景:
- 订单在十分钟之内未支付则自动取消。
- 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
- 账单在一周内未支付,则自动结算。
- 用户注册成功后,如果三天内没有登陆则进行短信提醒。
- 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
- 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。
2.2.2. 结构及步骤
使用方式如下:
步骤:
-
声明延时交换机
-
声明延时队列
x-message-ttl:指定TTL时间 x-dead-letter-exchange:死信转发所需的死信交换机(DLX) x-dead-letter-routing-key:转发死信时的routingKey(DLK)
-
延时队列绑定到延时交换机
-
声明死信交换机(DLX)
-
声明死信队列(DLQ)
-
死信队列绑定到死信交换机,rontingKey要和第2步的DLK一致。
2.2.3. 配置延时及死信队列
修改生产者RabbitConfig,添加延时队列 死信队列及他们之间的绑定关系:
@Configuration
@Slf4j
public class RabbitConfig {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
// 确认消息是否到达交换机
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack){
log.warn("消息没有到达交换机:" + cause);
}
});
// 确认消息是否到达队列,到达队列该方法不执行
this.rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.warn("消息没有到达队列,来自于交换机:{},路由键:{},消息内容:{}", exchange, routingKey, new String(message.getBody()));
});
}
@Bean
public TopicExchange delayExchange(){
return ExchangeBuilder.topicExchange("· ").build();
}
@Bean
public Queue delayQueue(){
return QueueBuilder.durable("SPRING_DELAY_QUEUE")
.withArgument("x-message-ttl", 60000)
.withArgument("x-dead-letter-exchange", "SPRING_DEAD_EXCHANGE")
.withArgument("x-dead-letter-routing-key", "ab.dead")
.build();
}
@Bean
public Binding delayBinding(TopicExchange delayExchange, Queue delayQueue){
return BindingBuilder.bind(delayQueue).to(delayExchange).with("ab.delay");
}
@Bean
public TopicExchange deadExchange(){
return ExchangeBuilder.topicExchange("SPRING_DEAD_EXCHANGE").build();
}
@Bean
public Queue deadQueue(){
return QueueBuilder.durable("SPRING_DEAD_QUEUE").build();
}
@Bean
public Binding deadBinding(TopicExchange deadExchange, Queue deadQueue){
return BindingBuilder.bind(deadQueue).to(deadExchange).with("ab.dead");
}
}
2.2.4. 生产者发送消息给延时队列
2.2.5. 消费者通过监听器获取消息
修改消费者的ConsumerListener添加listener2方法:
@Component
public class ConsumerListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "SPRING_RABBIT_QUEUE", durable = "true"),
exchange = @Exchange(value = "SPRING_RABBIT_EXCHANGE", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC),
key = {"a.b"}
))
public void listener(String msg, Channel channel, Message message) throws IOException {
try {
System.out.println(msg);
// 手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
// 是否已经重试过
if (message.getMessageProperties().getRedelivered()){
// 已重试过直接拒绝
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} else {
// 未重试过,重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, true);
}
}
}
@RabbitListener(queues = "SPRING_DEAD_QUEUE")
public void listener2(String msg, Channel channel, Message message) throws IOException {
try {
System.out.println(msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
if (message.getMessageProperties().getRedelivered()){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} else {
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
}