springboot 整合rabbitmq延时队列实现支付结果阶梯通知
延时队列
最近在写支付项目,其中用到了支付结果异步通知,之前也接入过很多支付公司,大部分都是阶梯性通知,策略一般设定30秒、60秒、3分钟、6分钟、10分钟调度多次,我最终确定使用RabbitMq消息延迟+死信队列来实现。
实现思路:
图片来源与网络
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--spring-boot-starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
application.properties 配置RabbitMq
spring.rabbitmq.username=page
spring.rabbitmq.password=page
spring.rabbitmq.host=192.168.1.2
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
## 自定义消息发送规则
trade.query.rule={1:10,2:20,3:30}
代码实现
- 定义队列和交换机
@Configuration
@Slf4j
public class RabbitConfig {
@Resource
private MqProperties mqProperties;
@Bean
public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
@Bean
public Queue delayProcessQueue() {
Map<String, Object> params = new HashMap<>();
params.put("x-dead-letter-exchange", mqProperties.getExchangeName());
params.put("x-dead-letter-routing-key", mqProperties.getRoutingKey());
log.info("wypMqProperties={}", JsonUtils.toJSON(mqProperties));
return new Queue(mqProperties.getDelayQueue(), true, false, false, params);
}
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(mqProperties.getDelayExchange());
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(delayProcessQueue()).to(delayExchange()).with(mqProperties.getDelayRoutingKey());
}
@Bean
public Queue registerBookQueue() {
return new Queue(mqProperties.getQueueName(), true);
}
@Bean
public TopicExchange registerBookTopicExchange() {
return new TopicExchange(mqProperties.getExchangeName());
}
@Bean
public Binding registerBookBinding() {
return BindingBuilder.bind(registerBookQueue()).to(registerBookTopicExchange()).with(mqProperties.getRoutingKey());
}
}
-
定义MqProperties 队列属性
@Component @ConfigurationProperties("trade.queue") @Data public class MqProperties { private String delayQueue; private String delayExchange; private String delayRoutingKey; private String queueName; private String exchangeName; private String routingKey; } -
定义QueueData 消息实体
@Data @ToString public class QueueData implements Serializable { private boolean success; private String tradeId; private Integer current = 1; private Integer currentTime; private Integer ruleSize; //执行次数+1 public void next() { this.current++; } } -
定义消息发送
@Component
@Slf4j
public class MessageSender {
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private MqProperties mqProperties;
@Value("#{${trade.query.rule}}")
private Map<Integer, Integer> rules;
public void send(QueueData data) {
data.setRuleSize(rules.size());
Integer current = data.getCurrent();
Integer currentTime = rules.get(current) * 1000;
data.setCurrentTime(currentTime);
if (current.compareTo(rules.size()) > 0) {
log.info("规则结束");
return;
}
this.rabbitTemplate.convertAndSend(wypMqProperties.getDelayExchange(), wypMqProperties.getDelayRoutingKey(), data, message -> {
message.getMessageProperties().setExpiration(String.valueOf(currentTime));
return message;
});
}
}
rules: 自定义在application.properties中的消息发送规则,例如:{1:10,2:20,3:30} 分别是第一次10S后执行,第二次20S后执行,第三次30S后执行,具体规则可以根据业务自己实现,因为我配置中心使用的是apollo,使用这种方式可以动态调整发送策略。
-
定义消息消费
@Component @Slf4j public class TradeCustomer { @Resource private MessageSender messageSender; @RabbitListener(queues = {"${trade.queue.queueName}"}) public void listenerDelayQueue(QueueData data) { log.info("第[{}]次监听的消息[{}]]-[消费时间]-[{}]", data.getCurrent(), data.getCurrentTime(), LocalDateTime.now()); try { if (data.getCurrent().compareTo(data.getRuleSize()) <= 0) { log.info("第[{}]次处理业务", data.getCurrent()); //TODO 执行自己的业务..... } if (data.getCurrent().compareTo(data.getRuleSize()) < 0) { data.next(); messageSender.send(data); } else { log.info("[执行结束]", data.getTradeId()); } } catch (Exception e) { log.info("异常了={}",Throwables.getStackTraceAsString(e)); } } }总结
目前网上有很多类似的文档,也有很多解决方案,我根据自己实际应用情况写了这篇文章,如果大家有更好的方案或者想法,欢迎评论,最后不要纠结我的代码质量,能够帮助到你就好,谢谢大家支持。