前言
目的主要是学习RabbitMQ的TTL+DLX实现延迟队列,大概会简单介绍学习为主:毕竟还是要来演示Springboot整合RabbitMQ注解的方式来使用。
一. TTL
TTL,Time to Live的简称,即过期时间。RabbitMQ 可以对消息和队列设置TTL。
消息的 TTL 指的是消息的存活时间,RabbitMQ 支持消息、队列两种方式设置 TTL,分别如下:
消息设置 TTL:对消息的设置是在发送时进行 TTL 设置,通过 x-message-ttl 或expiration 字段设置,单位为毫秒,代表消息的过期时间,每条消息的 TTL 可不同。
队列设置 TTL:对队列的设置是在消息入队列时计算,通过 x-expires 设置,队列中的所有消息都有相同的过期时间,当超过了队列的超时设置,消息会自动的清除。
注意:如果以上两种方式都做了设置,消息的 TTL 则以两者之中最小的那个为准。
二.死信队列
DLX,全称为Dead-Letter-Exchange,可以称之为死信交换器,也有人称之为死信邮箱。当消息在一个队列中变成死信( dead message)之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列。
消息变成死信一般是由于以下几种情况:
1). 消息被拒绝(Basic. Reject/Basic.Nack),并且设置requeue参数为false;
2). 消息过期;
3). 队列达到最大长度。
2.1 DLX 交换机
DLX也是一个正常的交换器,和一般的交换器没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中存在死信时,RabbitMQ就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。可以监听这个队列中的消息以进行相应的处理
####### 2.2 DLX声明
声明步骤如下:
第一步:声明正常的交换机,队列,路由键
第二步:在正常的队列里设置过期时间,绑定死信队列交换机名称以及设置发送给死信队列交换机路由键
第三步:声明死信交换机,队列,路由键
上面的做法是为了设置一个过期的正常队列路由到死信队列上面,让任意的队列去监听消费它。
/* 设置队列过期时间*/
@Bean
public Exchange queueTimeOutExchange(){
return new DirectExchange("queueTimeOut.exchange.test",true,false);
}
// 声明过期的队列并定义队列名称
@Bean
public Queue queueTimeOutQueue(){
// 消息过期 特殊的args
Map<String,Object> args = new HashMap<>(16);
args.put("x-message-ttl",20000);
// 绑定死信交换机名称
args.put("x-dead-letter-exchange", "dead.exchange.test");
// 置发送给死信交换机的routingKey
args.put("x-dead-letter-routing-key", "key.queue.dead");
// 设置队列可以存储的最大消息数量
args.put("x-max-length", 10);
return new Queue("queueTimeOut.queue.test"
,true,false,false,args);
}
@Bean
public Binding queueTimeOutBinding(){
return new Binding("queueTimeOut.queue.test",
Binding.DestinationType.QUEUE,
"queueTimeOut.exchange.test",
"key.queuetime.out",null);
}
/* 死信队列*/
@Bean
public Exchange deadExchange(){
return new DirectExchange("dead.exchange.test",true,false);
}
// 声明过期的队列并定义队列名称
@Bean
public Queue deadQueue(){
return new Queue("dead.queue.test"
,true,false,false);
}
@Bean
public Binding deadBinding(){
return new Binding("dead.queue.test",
Binding.DestinationType.QUEUE,
"dead.exchange.test",
"key.queue.dead",null);
}
上图标记的队列就是TTL+DLX。
上面标记之前为这个队列设置的特性
三,实现简单实例
3.1 生产端
service
// 设置 死信队列
public void deadMessag() throws JsonProcessingException;
impl
/**
* 測試死信隊列
*/
@Override
public void deadMessag() throws JsonProcessingException {
/* 使用MessageProperties传递的对象转换成message*/
MessageProperties messageProperties = new MessageProperties();
OrderMessageDTO orderMessageDTO = new OrderMessageDTO();
orderMessageDTO.setProductId(100);
orderMessageDTO.setPrice(new BigDecimal("20"));
orderMessageDTO.setOrderId(1);
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
Message message = new Message(messageToSend.getBytes(),messageProperties);
// 发送端确认是否确认消费
CorrelationData correlationData = new CorrelationData();
// 唯一ID
correlationData.setId(orderMessageDTO.getOrderId().toString());
rabbitTemplate.convertAndSend("dead.exchange.test","key.queue.dead",message,correlationData);
}
controller
@RestController
@Slf4j
@RequestMapping("/api")
public class SendController {
@GetMapping("/deadQueue")
public void deadQueue() throws JsonProcessingException {
directService.deadMessag();
}
}
3.2 消费端
service
// 监听死信队列, 死信队列可以在任何的队列上被指定,实际上就是设置某个队列的属性
public void deadQueueListenter(Message message, Channel channel) throws IOException;
impl
/**
* 监听死信队列
*/
@RabbitListener(
containerFactory = "rabbitListenerContainerFactory",
bindings = {
@QueueBinding(
value = @Queue(name = "dead.queue.test"),
exchange = @Exchange(name = "dead.exchange.test",
type = ExchangeTypes.DIRECT),
key = "key.queue.dead"
)
}
)
@Override
public void deadQueueListenter(@Payload Message message, Channel channel) throws IOException {
log.info("========direct死信队列,业务场景超时===========");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String message = new String(body, "UTF-8");
log.info("[x] Received '" + message + "'");
// 手动 false ack
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//. 设置 Channel 消费者绑定队列
channel.basicConsume("dead.queue.test",false,consumer);
}