返利app消息队列应用:基于RabbitMQ的异步佣金结算系统设计
大家好,我是省赚客APP研发者阿可!在省赚客APP(juwatech.cn)中,用户完成电商订单后,平台需根据联盟回调数据进行佣金结算。由于回调频率高、第三方接口不稳定、数据库写入耗时等因素,若采用同步处理极易造成线程阻塞与系统雪崩。为此,我们引入 RabbitMQ 构建异步佣金结算系统,实现削峰填谷、失败重试与最终一致性。本文将从消息模型、生产消费逻辑到可靠性保障,结合核心代码详解实现细节。
消息模型设计:事件驱动架构
我们将佣金结算拆解为两个核心事件:
CommissionPendingEvent:订单确认有效,待结算;CommissionRetryEvent:结算失败,需延时重试。
对应 RabbitMQ 拓扑如下:
java
@Configuration
public class RabbitMqConfig {
public static final String COMMISSION_EXCHANGE = "juwatech.commission.exchange";
public static final String PENDING_QUEUE = "juwatech.commission.pending.queue";
public static final String RETRY_QUEUE = "juwatech.commission.retry.queue";
public static final String DLX_EXCHANGE = "juwatech.commission.dlx.exchange";
@Bean
public DirectExchange commissionExchange() {
return new DirectExchange(COMMISSION_EXCHANGE, true, false);
}
@Bean
public Queue pendingQueue() {
return QueueBuilder.durable(PENDING_QUEUE)
.deadLetterExchange(DLX_EXCHANGE)
.deadLetterRoutingKey("retry")
.ttl(30000) // 30秒未消费则进入重试队列
.build();
}
@Bean
public Queue retryQueue() {
return QueueBuilder.durable(RETRY_QUEUE)
.deadLetterExchange(COMMISSION_EXCHANGE)
.deadLetterRoutingKey("pending")
.ttl(600000) // 10分钟延时重试
.build();
}
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange(DLX_EXCHANGE, true, false);
}
@Bean
public Binding pendingBinding() {
return BindingBuilder.bind(pendingQueue()).to(commissionExchange()).with("pending");
}
@Bean
public Binding retryBinding() {
return BindingBuilder.bind(retryQueue()).to(dlxExchange()).with("retry");
}
}
消息生产:幂等投递保障
在回调网关中,仅当订单首次接收时投递消息:
java
@Service
public class CommissionEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private CommissionRecordMapper commissionRecordMapper;
public void publishIfAbsent(String tradeId, CommissionPendingEvent event) {
// 幂等检查:防止重复投递
if (commissionRecordMapper.existsByTradeId(tradeId)) {
return;
}
// 插入"待处理"记录 + 发送消息(本地事务)
transactionTemplate.execute(status -> {
commissionRecordMapper.insertAsPending(tradeId, event.getUserId(), event.getAmount());
rabbitTemplate.convertAndSend(
RabbitMqConfig.COMMISSION_EXCHANGE,
"pending",
event,
message -> {
message.getMessageProperties().setMessageId(tradeId); // 用于去重
return message;
}
);
return null;
});
}
}
消息消费:可靠处理与异常重试
消费者启用手动 ACK,确保处理成功才确认:
java
@Component
@RabbitListener(queues = RabbitMqConfig.PENDING_QUEUE)
public class CommissionConsumer {
@Autowired
private AccountService accountService;
@Autowired
private CommissionRecordMapper commissionRecordMapper;
@RabbitHandler
public void handle(CommissionPendingEvent event, Channel channel, Message message) throws IOException {
try {
// 再次幂等校验(防重复消费)
if (commissionRecordMapper.isProcessed(event.getTradeId())) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}
// 执行佣金发放
accountService.credit(event.getUserId(), event.getAmount(), "返利入账");
// 更新状态为成功
commissionRecordMapper.markAsSuccess(event.getTradeId());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (InsufficientBalanceException | UserNotFoundException e) {
// 业务错误:直接拒绝,不重试
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
log.error("Commission failed with unrecoverable error", e);
} catch (Exception e) {
// 系统异常:拒绝并重回队列(最多3次后进入DLQ)
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
log.warn("Commission processing failed, will retry", e);
}
}
}
死信队列监控与人工干预
当消息重试超过阈值(如3次),进入死信队列,由运维后台处理:
java
@RabbitListener(queues = "juwatech.commission.dlq")
public void handleDeadLetter(CommissionPendingEvent event) {
// 记录至DB,供人工审核
deadLetterLogMapper.insert(
DeadLetterLog.builder()
.tradeId(event.getTradeId())
.payload(JsonUtil.toJson(event))
.reason("Max retries exceeded")
.build()
);
}
消息追踪与监控
通过 Micrometer 上报消费延迟与失败率:
java
private final Counter consumeFailureCounter;
private final Timer consumeTimer;
public void handle(...) {
Timer.Sample sample = consumeTimer.start();
try {
// ...处理逻辑
} catch (Exception e) {
consumeFailureCounter.increment();
throw e;
} finally {
sample.stop();
}
}
Prometheus 抓取指标后,在 Grafana 中配置告警:
rate(juwatech_commission_consume_failure_total[5m]) > 0.05
连接与资源管理
使用 Spring Boot 自动配置连接池,避免资源泄漏:
yaml
spring:
rabbitmq:
host: rabbitmq.juwatech.cn
port: 5672
username: juwatech
password: ${RABBITMQ_PASSWORD}
listener:
simple:
acknowledge-mode: manual
concurrency: 5
max-concurrency: 20
prefetch: 10
本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!