返利app消息队列应用:基于RabbitMQ的异步佣金结算系统设计

返利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开发者团队,转载请注明出处!

相关推荐
ACP广源盛139246256734 小时前
GSV6155 @ACP#工业车规 DP1.4 重定时器 Retimer
大数据·人工智能·分布式·嵌入式硬件
ACP广源盛139246256734 小时前
GSV2221@ACP# DP1.4 MST 多流显示转换芯片国产工业多屏
大数据·人工智能·分布式·嵌入式硬件·spark
sukioe5 小时前
从 0 到 1 构建城市公共设施智能报修与派单系统:FastAPI + Vue3 + RabbitMQ + Redis Geo + AI 工作流实践
redis·rabbitmq·fastapi
ACP广源盛139246256735 小时前
GSV2231@ACP# 增强型 8K MST 多屏扩展芯片
大数据·人工智能·分布式·嵌入式硬件
虚心的百褶裙5 小时前
远程调用服务架构设计及zookeeper技术详解(下篇)
分布式·zookeeper·云原生
清晨曦月6 小时前
【从零构建分布式在线评测系统】二、Judge0服务器上的心跳程序
运维·服务器·分布式
万联WANFLOW19 小时前
SD-WAN 控制平面高可用怎么做?SDWAN 控制器挂了,全网会发生什么
运维·网络·分布式·架构
月落归舟1 天前
从单机到分布式:互联网系统架构演进全流程
分布式·系统架构
搬砖柯1 天前
系列12-接口压测怎么做成平台能力?自研 httpx 引擎、分布式 Worker 与 Locust 选型对照
分布式·测试工具·性能优化·开源·自动化·httpx
折哥的程序人生 · 物流技术专研1 天前
Java面试通关⑭:分布式理论与架构全集
分布式·微服务·校招·分布式事务·cap·java面试·最终一致性