死信队列
死信队列是什么?
首先死信队列中消息不能在规定的条件下无法发送给消费者, 到转发到死信队列中的整个过程
消息变成死信消息的条件
- TTL 超时
- 消息队列满了, 无法继续添加
- 消息被拒绝
死信队列应该怎么设计?
那么死信队列的代码应该怎么写?
可以根据上面的图片照着写就成
/**
* 正常消费者
*/
public class Consumer1 {
public static final String EXCHANGE = "exchange";
public static final String DEAD_EXCHANGE = "dead_exchange";
public static final String QUEUE = "queue";
public static final String DEAD_QUEUE = "dead_queue";
public static final String DEAD_ROUTING_KEY = "dead_routing_key";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.DIRECT, true);
HashMap<String, Object> map = new HashMap<>();
// 消息过期时间
map.put("x-message-ttl", 10000);
// queue消息触发三个死信条件后, 将消息提交给哪个 exchange
map.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 跟死信交换机绑定的死信routing key
map.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
channel.queueDeclare(QUEUE, true, false, false, map);
// 正常的交换机和正常队列绑定
channel.queueBind(QUEUE, EXCHANGE, "");
// 死信交换机的定义
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT, true);
// 定义死信队列
channel.queueDeclare(DEAD_QUEUE, true, false, false, null);
// 绑定死信交换机和死信队列和死信routing key
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, DEAD_ROUTING_KEY);
channel.basicConsume(QUEUE, false, (consumerTag, message) -> {
if (message.getEnvelope().getDeliveryTag() % 3 == 0L) {
// 模拟消息被拒绝
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
return;
}
System.err.println("消息id: " + consumerTag + ", 消息内容: " + new String(message.getBody(), Charset.defaultCharset())
+ ", 累计发送了: " + message.getEnvelope().getDeliveryTag() + "个消息");
}, System.err::println);
}
}
/**
* 死信消费者
*/
public class Consumer2 {
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.basicConsume(DEAD_QUEUE, false, (consumerTag, message) -> {
System.err.println("消息id: " + consumerTag + ", 消息内容: " + new String(message.getBody(), Charset.defaultCharset()) +
", 累计发送了: " + message.getEnvelope().getDeliveryTag() + "个消息");
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}, System.err::println);
}
}
public class Producer {
public static final String EXCHANGE = "exchange";
public static void main(String[] args) throws Exception {
ConcurrentSkipListMap<Long, String> map = new ConcurrentSkipListMap<>();
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
ConcurrentNavigableMap<Long, String> headMap = map.headMap(deliveryTag);
headMap.clear();
} else {
map.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.err.println("消息: " + map.get(deliveryTag) + ", 是否批处理: " + multiple);
handleAck(deliveryTag,multiple);
}
});
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder().expiration("10000").build();
for (int i = 0; i < 100; i++) {
String message = "message" + i;
map.put(channel.getNextPublishSeqNo(), message);
channel.basicPublish(EXCHANGE, "", basicProperties, message.getBytes(Charset.defaultCharset()));
}
}
}
代码过长, 最好别看, 直接看图, 你就能写出代码
TTL超时时间
消息和queue都可以配置超时时间
消费者
rabbitTemplate.convertAndSend(DELAY_EXCHANGE, "", message,
m -> {
m.getMessageProperties().setDelay(delayTime);
return m;
});
配置延迟时间就可以
生产者
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DELAY_QUEUE)
.ttl(3000) // 延迟 3 秒
.autoDelete()
.build();
}
对管道做延迟, 延迟3秒, 这个3秒是针对消息的, 不是队列的哦
延迟队列如何实现?
这里有两种方法
- 通过DLX
- 通过延迟插件
通过DLX
延迟队列给予死信队列, 将正常的消费者删除掉, 只留下死信消费者
Producer:
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT, true, true, null);
channel.confirmSelect();
channel.addConfirmListener((deliveryTag, multiple) -> {
String dateTime = DatePattern.NORM_DATETIME_FORMAT.format(new Date());
System.err.println(deliveryTag + ": 消息应答" + " '发送'时间为: " + dateTime);
}, (deliveryTag, multiple) -> System.err.println(deliveryTag + ": 消息未得到应答"));
for (int i = 0; i < 10; i++) {
String message = UUID.randomUUID().toString() + i;
channel.basicPublish(NORMAL_EXCHANGE,"",
MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(Charset.defaultCharset()));
}
Consumer:
public static final String NORMAL_EXCHANGE = "exchange";
public static final String NORMAL_QUEUE = "queue";
public static final String DELAY_EXCHANGE = "delay_exchange";
public static final String DELAY_ROUTING_KEY = "routing_key_name";
public static final String DELAY_QUEUE = "delay_queue";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(DELAY_EXCHANGE, BuiltinExchangeType.DIRECT, true, true, null);
channel.queueDeclare(DELAY_QUEUE, true, false, true, null);
channel.queueBind(DELAY_QUEUE, DELAY_EXCHANGE, DELAY_ROUTING_KEY);
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT, true, true, null);
var builder = QueueBuilder.durable(DELAY_QUEUE)
.deadLetterRoutingKey(DELAY_ROUTING_KEY)
.deadLetterExchange(DELAY_EXCHANGE).build();
channel.queueDeclare(NORMAL_QUEUE, true, false, true, builder.getArguments());
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "");
DeliverCallback deliverCallback = (consumerTag, message) -> System.err.println("消息接收时间: "
+ DatePattern.NORM_DATETIME_FORMAT.format(new Date())
+ " consumerTag: " + consumerTag + " message: " + message);
CancelCallback cancelCallback = System.err::println;
channel.basicConsume(DELAY_QUEUE, false, deliverCallback, cancelCallback);
}
这种方式实现延迟队列的问题
比如 A 需要延迟 5s 后执行消息, B 需要 10s 后执行消息, C 需要 30s 后执行消息
那么将会有三个queue
问: 那么有无限中延迟需求呢?
答: 专门给出一个队列, 消费者往该队列发送存在带过期时间的消息
消费者给发送的消息添加延迟时间
public static final String NORMAL_EXCHANGE = "exchange";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = RabbitUtils.INSTANCE.connectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT, true, true, null);
channel.confirmSelect();
channel.addConfirmListener((deliveryTag, multiple) -> {
String dateTime = DatePattern.NORM_DATETIME_FORMAT.format(new Date());
System.err.println(deliveryTag + ": 消息应答" + " '发送'时间为: " + dateTime);
}, (deliveryTag, multiple) -> System.err.println(deliveryTag + ": 消息未得到应答"));
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("10000");
for (int i = 0; i < 10; i++) {
String message = UUID.randomUUID().toString() + i;
channel.basicPublish(NORMAL_EXCHANGE,"",
builder.build(), message.getBytes(Charset.defaultCharset()));
}
}
插件延迟队列需要整合springboot才会比较方便使用, 将会在下一章节讲解