背景
目前很多业务都需要一些延后的处理方案,比如说:订单30分钟后过期自动关闭,发出红包24小时后未领取完毕需要退款等等,需要一定时间后进行一个结算或是其他的操作,就可以用到我记录的这个方案RabbitMQ的延时交换机,非常简单且好用。
准备
怎么安装MQ就不说了网上帖子一大堆
怎么安装延时交换机插件:
安装MQ延时交换机插件
使用场景
首先写一些枚举,因为到处都要用,统一收束起来写成枚举
public class RabbitMQConstant {
//交换机类型
public static final String DELAY_EXCHANGE_NAME = "delay_exchange";
//队列
public static final String RED_PACKET_DELAY_QUEUE_NAME = "red_packet_delay_queue";
//路由
public static final String RED_PACKET_DELAY_QUEUE_ROUTING_KEY = "red_packet_delay_queue_key";
}
然后标记一下配置,告诉系统谁是谁,预备干什么
@Configuration
public class RabbitMQConfig {
//声名队列
@Bean(RED_PACKET_DELAY_QUEUE_NAME)
Queue redPacketDelayQueue() {return QueueBuilder.durable(RED_PACKET_DELAY_QUEUE_NAME).build();}
//声名交换机
@Bean(DELAY_EXCHANGE_NAME)
CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
//绑定队列和交换机 指定路由键
@Bean
Binding redPacketDelayQueueBinding(@Qualifier(RED_PACKET_DELAY_QUEUE_NAME) Queue orderDelayQueue, @Qualifier(DELAY_EXCHANGE_NAME) CustomExchange delayExchange) {
return BindingBuilder.bind(orderDelayQueue).to(delayExchange).with(RED_PACKET_DELAY_QUEUE_ROUTING_KEY).noargs();
}
}
然后就是老生常谈的生产消费者了
生产者 用于提交消息到 MQ中
/**
*
* @param sendContent 消息
* @param messageId 唯一id
* @param exchangeName 交换机
* @param key 路由键
* @param delayTime 延迟时间(秒)
*/
public void publishRedPacket(String sendContent, String messageId, String exchangeName, String key, Integer delayTime) {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
assert correlationData != null;
String message_Id = correlationData.getId();
//返回成功,表示消息被正常投递到交换机
if (ack) {
log.info("XXX递到交换机成功,messageId:{}", message_Id);
} else {
String mes="交换机不可达,口令为:+"+message_Id+" 原因:"+cause;
LogMgr.error(log, new BizException(mes));
}
});
// 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值,进行对应的错误处理
CorrelationData correlationData = new CorrelationData(messageId);
rabbitTemplate.convertAndSend(exchangeName, key, sendContent, message -> {
// 设置延迟时间
message.getMessageProperties().setDelay(delayTime*1000);//ms单位
return message;
}, correlationData);
}
消费者获取队列中的消息然后消费
//消费者 指定队列获取信息消费
@RabbitListener(queues = RED_PACKET_DELAY_QUEUE_NAME)
@SneakyThrows
public void dealExpireOrder(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag, String orderInfo) {
log.debug("收到XXXX信息:" + orderInfo);
try {
//然后进行你的逻辑(例如:订单改变状态,红包退回)
);
} catch (Exception e) {
//消息重新入队
LogMgr.error(log, new BizException(orderInfo + "处理失败" + e.getMessage()));
} finally {
channel.basicAck(tag, true);
}
}
最后贴一个调用方式
// 发消息
try {
//过期时间 创建一个时间 这个时间可以是XXXS 后需要做什么事儿
String duration = jsonMap.get("duration").toString();
//然后直接调用生产者 发送消息 再XXXs后进行消费 消费者放你们的逻辑
mqProducer.publishRedPacket(JSON.toJSONString(redPacketSend), //消息
String.valueOf(redPacketSend.getCommand()), //唯一ID
DELAY_EXCHANGE_NAME, //队列
RED_PACKET_DELAY_QUEUE_ROUTING_KEY, //路由键
Integer.valueOf(duration)); //过期时间
} catch (Exception e) {
log.info("投递消息异常:{}", e.getMessage());
}
这只是一个简单的方案,这种方案也可以替代一些定时任务,只需要再消费的时候再次调用生产者投递到队列这样就能一直循环下去。