在定义业务队列的时候,要考虑指定一个死信交换机,死信交换机可以和任何一个普通的队列进行绑定,实际上就是设置某个队列的属性,然后在业务队列出现死信的时候就会将数据发送到死信队列。
进入死信队列的情况:
- 消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
- 消息超期 (rabbitmq Time-To-Live -> messageProperties.setExpiration())
- 队列超载。比如,发送邮件队列的长度已满
变成了 “死信” 后 被重新投递(publish)到另一个Exchange 该Exchange 就是DLX
然后该Exchange 根据绑定规则 转发到对应的 队列上 ,监听该队列 就可以重新消费。
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd">
<!--配置connection-factory,指定连接rabbit server参数 -->
<rabbit:connection-factory id="rabbitConnectionFactory" username="${rabbit_username}" password="${rabbit_password}"
host="${rabbit_host}" port="${rabbit_port}" publisher-confirms="true"/>
<!--定义rabbit template用于数据的接收和发送 -->
<rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory"exchange="exchangeTest"
confirm-callback="publishService" mandatory="true"/>
<!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 -->
<rabbit:admin connection-factory="rabbitConnectionFactory" />
<!--定义queue -->
<rabbit:queue name="queueTest" durable="true" auto-delete="false" exclusive="false" >
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="deadExchange" />
<entry key="x-dead-letter-routing-key" value="queueTestKey" />
</rabbit:queue-arguments>
</rabbit:queue>
<!-- 定义direct exchange,绑定queueTest -->
<rabbit:direct-exchange name="exchangeTest" durable="true" auto-delete="false">
<rabbit:bindings>
<rabbit:binding queue="queueTest" key="queueTestKey" > </rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!--定义死信队列 -->
<rabbit:queue name="deadQueue" durable="true" auto-declare="false" exclusive="false" />
<!-- 定义死信exchange -->
<rabbit:direct-exchange name="deadExchange" durable="true" auto-delete="false" >
<rabbit:bindings>
<rabbit:binding queue="deadQueue" key="queueTestKey"> </rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 消息接收者 -->
<bean id="confirmListener" class="com.example.hello2.controller.ConfirmListener"></bean>
<!-- 死信消息接收者 -->
<bean id="deadQueueListener" class="com.example.hello2.controller.DeadQueueListener"></bean>
<rabbit:listener-container connection-factory="rabbitConnectionFactory" acknowledge="manual" prefetch="3" >
<rabbit:listener queues="queueTest" ref="confirmListener" />
<rabbit:listener queues="deadQueue" ref="deadQueueListener"/>
</rabbit:listener-container>
</beans>
此时队列情况:
正常消费者:
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
String messageStr = new String(message.getBody());
System.out.println("消费者接收到信息 : " + messageStr);
if (messageStr.equals("hello")) {
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}else{
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, false);
}
} catch (Exception e) {
e.printStackTrace();//TODO 业务处理
//丢弃这条消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
死信消费者:
@Service("deadQueueListener")
public class DeadQueueListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
String messageStr = new String(message.getBody());
System.out.println("【死信队列】——消费者接收到信息 : " + messageStr);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
e.printStackTrace();//TODO 业务处理
//丢弃这条消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
测试:
http://localhost:8081/test/send?exchange=exchangeTest&key=queueTestKey&message=helloaaa
延迟队列
场景:三方支付,扫码支付调用上游的扫码接口,当扫码有效期过后去调用查询接口查询结果。实现方式:每当一笔扫码支付请求后,立即将此订单号放入延迟队列中,队列过期时间为二维码有效期,此队列没有设置消费者,过了有效期后消息会重新路由到指定的的队列,有消费者去执行订单查询。
RabbitMQ本身没有直接支持延迟队列功能,但是可以通过以下特性模拟出延迟队列的功能。 但是我们可以通过RabbitMQ的两个特性来曲线实现延迟队列:Time To Live(TTL) 和 Dead Letter Exchanges(DLX)
RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter(死信)。
配置文件:
<!--定义queue -->
<rabbit:queue name="queueTest" durable="true" auto-delete="false" exclusive="false" >
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="deadExchange" />
<entry key="x-dead-letter-routing-key" value="queueTestKey" />
<entry key="x-message-ttl">
<value type="java.lang.Long">10000</value>
</entry>
</rabbit:queue-arguments>
</rabbit:queue>
并且把消费者去掉
<rabbit:listener-container connection-factory="rabbitConnectionFactory" acknowledge="manual" prefetch="3" >
<!--<rabbit:listener queues="queueTest" ref="confirmListener" />-->
<rabbit:listener queues="deadQueue" ref="deadQueueListener"/>
</rabbit:listener-container>
发送消息:
http://localhost:8081/test/send?exchange=exchangeTest&key=queueTestKey&message=hello
结果: