RabbitMQ-----死信队列
1.什么是TTL?
a. time to live 消息存活时间 b. 如果消息在存活时间内未被消费,则会被清除 c. RabbitMQ支持两种ttl设置 -单独消息进行配置ttl -整个队列进行配置ttl(居多)
2.什么是rabbitmq的死信队列?
没有被及时消费的消息存放的队列
3.什么是rabbitmq的死信交换机?
Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机
4.消息有哪几种情况成为死信?
a. 消费者拒收消息(basic.reject/ basic.nack),并且没有重新入队 requeue=false b. 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live) c. 队列的消息长度达到极限
结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
5.RabbitMQ管控台消息TTL测试
a. 队列过期时间使用参数,对整个队列消息统一过期 x-message-ttl 单位ms(毫秒)
b. 消息过期时间使用参数(如果队列头部消息未过期,队列中级消息已经过期,则消息会还在队列里面) expiration 单位ms(毫秒)
c. 两者都配置的话,时间短的先触发
6.如图
7.什么是延迟队列?
种带有延迟功能的消息队列,Producer 将消息发送到消息队列 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息
8.使用场景
1. 通过消息触发一些定时任务,比如在某一固定时间点向用户发送提醒消息 b. 用户登录之后5分钟给用户做分类推送、用户多少天未登录给用户做召回推送; c. 消息生产和消费有时间窗口要求:比如在天猫电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。这条消息将会在30分钟以后投递给消费者,
消费者收到此消息后需要判断对应的订单是否已完成支付。 如支付未完成,则关闭订单。如已完成支付则忽略
9.业界的一些实现方式
a. 定时任务高精度轮训
b. 采用RocketMQ自带延迟消息功能
c. RabbitMQ本身是不支持延迟队列的,怎么办?
结合死信队列的特性,就可以做到延迟消息
10.代码
场景:
客户提交商品订单后,需要在30分钟内完成支付,如未完成,则发送消息提醒订单失败
a. rabbitmq配置类代码,配置普通/死信队列和交换机
import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * Rabbitmq配置类 * 这里普通队列也可以叫它延时队列,是没有配置消费者(Listener)去监听的 * * */ @Configuration public class RabbitmqConfig { public static final String LOCK_MERCHANT_DEAD_EXCHANGE = "lock_merchant_dead_exchange"; public static final String LOCK_MERCHANT_DEAD_QUEUE = "lock_merchant_dead_queue"; public static final String LOCK_MERCHANT_DEAD_ROUTING_KEY = "lock_merchant_dead_routing_key"; public static final String NEW_MERCHANT_EXCHANGE = "new_merchant_exchange"; public static final String NEW_MERCHANT_QUEUE= "new_merchant_queue"; public static final String NEW_MERCHANT_ROUTING_KEY = "new_merchant.#"; /** * 死信交换机(topic模式) * * */ @Bean public Exchange lockMerchantDeadExchange(){ //durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失, // 如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中, // 当rabbitmq重启之后会读取该数据库 return ExchangeBuilder.topicExchange(LOCK_MERCHANT_DEAD_EXCHANGE).durable(true).build(); } /** * 死信队列 * * */ @Bean public Queue lockMerchantDeadQueue() { return QueueBuilder.durable(LOCK_MERCHANT_DEAD_QUEUE).build(); } /** * 绑定死信交换机和死信队列 * 这里不加@Qualifier的话会报错:there is more than one bean of “xxx” type * 因为死信交换机和普通交换机都配置了Exchange, 无法区分哪种作为参数 * Queue同理 * * */ @Bean public Binding lockMerchantDeadBinding(@Qualifier("lockMerchantDeadExchange") Exchange exchange, @Qualifier("lockMerchantDeadQueue") Queue queue){ return BindingBuilder.bind(queue).to(exchange).with(LOCK_MERCHANT_DEAD_ROUTING_KEY).noargs(); } /** * 普通交换机(topic模式) * * */ @Bean public Exchange newMerchantExchange(){ //durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失, // 如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中, // 当rabbitmq重启之后会读取该数据库 return ExchangeBuilder.topicExchange(NEW_MERCHANT_EXCHANGE).durable(true).build(); } /** * 普通队列 * * */ @Bean public Queue newMerchantQueue() { Map<String, Object> args = new HashMap<>(); //消息过期后,进入死信交换机 args.put("x-dead-letter-exchange", LOCK_MERCHANT_DEAD_EXCHANGE); //消息过期后,进入死信交换机的路由键 args.put("x-dead-letter-routing-key", LOCK_MERCHANT_DEAD_ROUTING_KEY); //消息过期时间 单位:毫秒 消息过期后,会从普通队列转入死信队列 //这里方便测试设置10秒后消息过期 args.put("x-message-ttl",10000); return QueueBuilder.durable(NEW_MERCHANT_QUEUE).withArguments(args).build(); } /** * 绑定普通交换机和普通队列 * * */ @Bean public Binding newMerchantBinding(){ return new Binding(NEW_MERCHANT_QUEUE, Binding.DestinationType.QUEUE, NEW_MERCHANT_EXCHANGE, NEW_MERCHANT_ROUTING_KEY, null); } }
b. 监听死信队列代码
import com.rabbitmq.client.Channel; import com.theng.shopuser.config.RabbitmqConfig; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; /** * 消费者监听死信队列 * */ @Component @RabbitListener(queues = RabbitmqConfig.LOCK_MERCHANT_DEAD_QUEUE) public class OrderMQListener { /** * body: 接收convertAndSend(String exchange, String routingKey, Object object)的object消息 * * */ @RabbitHandler public void messageHandler(String body, Message message, Channel channel) throws IOException { long msgTag = message.getMessageProperties().getDeliveryTag(); System.out.println("body: " + body); System.out.println("msgTag: " + msgTag); System.out.println("message: " + message.toString()); //30分钟后,从body中获取买家信息再从数据库查询抢购到的商品订单是否处理 TODO //如果没有处理,则向商家发送提醒消息 TODO //告诉broker(消息队列服务器实体),消息已经被确认 channel.basicAck(msgTag, false); //告诉broker,消息拒绝确认(可以拒绝多条,把比当前msgTag值小的也拒绝) // channel.basicNack(msgTag, false, true); //告诉broker,消息拒绝确认(只能拒绝当前msgTag的这条) // channel.basicReject(msgTag, true); } }
c. application.ym配置文件
spring: rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: 123456 #虚拟主机 可在http://localhost:15672管理平台进行配置 virtual-host: /dev #开启消息二次确认ConfirmCallback配置 publisher-confirms: true #开启ReturnCallback配置 publisher-returns: true #修改交换机改投消息递到队列失败策略 #true:交换机处理消息到队列失败,则返回给生产者 #和publisher-returns配合使用 template: mandatory: true #消息手工确认ack listener: simple: acknowledge-mode: manual
d. 控制器代码
@RestController @RequestMapping("/user-info") public class UserInfoController { @Autowired public RedisTemplate redisTemplate; //消息生产者 @GetMapping("/send") public Object testSend(){ //object可存储买家信息 rabbitTemplate.convertAndSend(RabbitmqConfig.NEW_MERCHANT_EXCHANGE, "new_merchant.create", "买家抢购成功,请及时处理订单!"); Map<String, Object> map = new HashMap<>(); map.put("code", 0); map.put("msg", "买家抢购成功,请在30分钟内提交订单!"); return "success"; } }
结果:
生产者发送消息10秒后,消息会进入死信交换机,通过死信队列将订单过期消息发送给消费者