死信队列
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
延迟队列场景:
订单业务:在电商中,用户下单后30分钟后未付款则取消订单。
短信通知:用户下单并付款后,1分钟后发短信给用户。
Time To Live(TTL)
RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)
RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。
如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter
Dead Letter Exchanges(DLX)
RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送
队列出现dead letter的情况有:1、消息或者队列的TTL过期 2、队列达到最大长度 3、消息被消费端拒绝(basic.reject or basic.nack)并且不再重新投递requeue=false

示例
- 配置业务队列,绑定到业务交换机上
- 为业务队列配置死信交换机和路由key
- 为死信交换机配置死信队列
一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。
那么“死信”被丢到死信队列中后,会发生什么变化呢?
如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。
消费者
@Slf4j public class DlxConsumer { public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.223.144"); connectionFactory.setPort(5672); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); String businessExchange = "business_exchange"; String businessQueue = "business_queue"; String businessRouteKey = "business.#"; String dlxExchange = "dlx.exchange"; String dlxRouteKey = "#"; String dlxQueue = "dlx_queue"; channel.exchangeDeclare(businessExchange, "topic", true, false, null); Map<String, Object> arguments = new HashMap<>(); //声明当前队列绑定的死信交换机 arguments.put("x-dead-letter-exchange", dlxExchange); //声明当前队列的死信路由key arguments.put("x-dead-letter-routing-key", dlxRouteKey); channel.queueDeclare(businessQueue, true, false, false, arguments); channel.queueBind(businessQueue, businessExchange, businessRouteKey); channel.exchangeDeclare(dlxExchange, "topic", true, false, null); channel.queueDeclare(dlxQueue, true, false, false, null); channel.queueBind(dlxQueue, dlxExchange, dlxRouteKey); Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss"); log.info("业务接收消息:{} 时间:{}", new String(body, "UTF-8"), dateFormat.format(new Date())); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //消息确认 channel.basicAck(envelope.getDeliveryTag(), false); } }; channel.basicConsume(businessQueue, false, consumer); channel.basicConsume(dlxQueue, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { log.info("死信队列接收消息:{}", new String(body, "UTF-8")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //消息确认 channel.basicAck(envelope.getDeliveryTag(), false); } }); } }
生产者
public class DlxProvider { public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.223.144"); connectionFactory.setPort(5672); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); String exchange = "business_exchange"; String routingKey = "business.save"; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:MM:ss"); for (int i = 0; i < 5; i++) { String format = dateFormat.format(new Date()); String message = "hello DLX Message" + format; AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().deliveryMode(2) .contentEncoding("utf-8").expiration("1000").build(); channel.basicPublish(exchange, routingKey, true, properties, message.getBytes()); } } }
死信消息的生命周期:
- 业务消息被投入业务队列
- 消费者消费业务队列的消息,由于处理过程中发生异常,于是进行了nck或者reject操作
- 被nck或reject的消息由RabbitMQ投递到死信交换机中
- 死信交换机将消息投入相应的死信队列
- 死信队列的消费者消费死信消息
浙公网安备 33010602011771号