-
应用场景
用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
常用的实现方案:
1.mq延时队列
2.生成订单时写一条缓存, 监听缓存过期
3.使用spring + quartz定时任务, 生成订单时写一条定时任务, 执行时如果未支付,则进行处理
4.将订单过期时间信息写入mysql,轮询查询mysql (性能差, 时效性差!) -
利用Mq实现方案
1.死信队列
2.利用插件实现延时 -
死信队列的实现
注意: 死信队列写在前, 延时队列写在后! 血的教训!!
/**
- 消息队列相关配置
- - @author Lynn
- @date 2022/10/22 16:46
*/
@Configuration
@EnableRabbit
public class RabbitMqConfig {
/**
* 订单取消交换机
*/
@Bean
DirectExchange orderDirectExchange() {
return (DirectExchange) ExchangeBuilder.directExchange(MqEnum.ORDER_CANCEL.getExchange()).build();
}
/**
* 订单取消队列
*/
@Bean
Queue orderQueue() {
return QueueBuilder.durable(MqEnum.ORDER_CANCEL.getQueue()).build();
}
/**
* 订单取消路由
*/
@Bean
Binding bindingOrder() {
return BindingBuilder.bind(orderQueue()).to(orderDirectExchange()).with(MqEnum.ORDER_CANCEL.getRoute());
}
/**
* 订单延迟取消交换机
*/
@Bean
DirectExchange orderTtlDirectExchange() {
return (DirectExchange) ExchangeBuilder.directExchange(MqEnum.ORDER_CANCEL_TTL.getExchange()).build();
}
/**
* 订单延迟队列(死信队列)
*/
@Bean
Queue orderTtlQueue() {
return QueueBuilder.durable(MqEnum.ORDER_CANCEL_TTL.getQueue())
.deadLetterExchange(MqEnum.ORDER_CANCEL.getExchange())
.deadLetterRoutingKey(MqEnum.ORDER_CANCEL.getRoute())
.ttl(15 * 60 * 1000)
.build();
}
/**
* 订单延迟取消路由
*/
@Bean
Binding bindingOrderTtl() {
return BindingBuilder.bind(orderTtlQueue())
.to(orderTtlDirectExchange())
.with(MqEnum.ORDER_CANCEL_TTL.getRoute());
}
}
队列ttl过期时间设置15分钟, 不设置消费者. 15分钟过后, 消息转发至死信队列.
- 生产者
找了另一个生产者贴过来供参考
/**
* @author Lynn
* @date 2023/4/26 10:03
*/
@Component
public class AudioTransformMqProducer implements MqProducer {
/**
* 通知消费语音识别队列
*
*/
public void noticeAudioTransform(String contextId) {
produce(MqEnum.AUDIO_TRANSFORM.getExchange(), MqEnum.AUDIO_TRANSFORM.getRoute(), contextId);
}
}
- 消费者
/**
- 创建工单消费者
- - @author Lynn
- @date 2022/10/22 16:46
*/
@Component
@RabbitListener(queues = "queue.work.order.create")
public class WorkOrderCreateConsumer implements MqConsumer<ReserveOrderVo> {
@Resource(name = "haHaYangLaoOrderService")
private OrderService orderService;
@Override
public void handleMessage(ReserveOrderVo message) {
LogManager.getRuntimeLog().info("handleMessage:{}", JSON.toJSONString(message));
// 更新订单中的工单号
OrderDto build = OrderDto.builder().orderId(message.getOrderId()).workOrderId(message.getWorkOrderId()).build();
orderService.updateWorkOrderId(build);
}
}
用死信队列有一个弊端, 就是ttl时间适合队列绑定在一起的, 如果有不同的超时时间, 那么就需要创建多个队列. 所以此场景可以用插件的方式:
利用rabbit插件实现延时队列
- 利用插件
/**
* @date: 2022/02/09
* @description:
*/
@Configuration
@EnableRabbit
public class DeadLetterConfig {
@Bean
public Queue delayQueue() {
return new Queue(IOT_DELAY_QUEUE, true);
}
@Bean
CustomExchange customExchange() {
Map<String, Object> args = new HashMap<>(1);
// 这里使用直连方式的路由,如果想使用不同的路由行为,可以修改,如 topic
args.put("x-delayed-type", "direct");
return new CustomExchange(IOT_DELAY_EXCHANGE_NAME, IOT_DELAY_EXCHANGE_TYPE, true, false, args);
}
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(delayQueue()).to(customExchange()).with(IOT_DELAY_ROUTING_NAME).noargs();
}
}
- 生产者
/**
* @date: 2021/12/6
* @description:
*/
@Component
public class DeadLetterSender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send(String message, int delayTime) {
rabbitTemplate.convertAndSend(IOT_DELAY_EXCHANGE_NAME, IOT_DELAY_ROUTING_NAME, message,
new DelayMessagePostProcessor(delayTime));
}
}
- Processor
/**
* @date: 2022/6/22
* @description:
*/
public class DelayMessagePostProcessor implements MessagePostProcessor {
private Integer delayTime;
DelayMessagePostProcessor(Integer delayTime) {
this.delayTime = delayTime;
}
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDelay(delayTime);
return message;
}
}