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

相关推荐
2501_9418824810 小时前
互联网分布式系统中的性能优化工程实践与多语言示例随笔分享
kafka·rabbitmq
2501_9418714511 小时前
从接口限流到全链路流控的互联网工程语法构建与多语言实践分享
kafka·rabbitmq
马达加斯加D12 小时前
系统设计 --- 使用消息队列解决分布式事务
分布式
2501_9418656313 小时前
从微服务链路追踪到全链路可观测的互联网工程语法实践与多语言探索
rabbitmq·memcached
遇见火星13 小时前
RabbitMQ 高可用:HAProxy 负载均衡实战指南
分布式·消息队列·rabbitmq·负载均衡·haproxy
2501_9418043214 小时前
在东京智能地铁场景中构建实时列车调度与高并发乘客流数据分析平台的工程设计实践经验分享
rabbitmq·memcached
Blossom.11814 小时前
基于多智能体协作的自动化数据分析系统实践:从单点工具到全流程智能
运维·人工智能·分布式·智能手机·自动化·prompt·边缘计算
回家路上绕了弯15 小时前
MDC日志链路追踪实战:让分布式系统问题排查更高效
分布式·后端
qq_124987075315 小时前
基于Hadoop的黑龙江旅游景点推荐系统的设计与实现(源码+论文+部署+安装)
大数据·hadoop·分布式·python·信息可视化
2501_9418072615 小时前
从任务调度到分布式作业管理的互联网工程语法实践与多语言探索
eureka·rabbitmq