分布式系统推送失败补偿场景【解决方案】

业务场景

在分布式项目中,A 系统向 B 系统推送数据 / 请求失败是高频场景,核心补偿思路是 "先保障消息不丢、再分层重试、最后人工兜底",以下是落地性极强的分层补偿方案,附具体实现逻辑和适用场景:

商场系统中,用户完成订单支付(如线上APP下单、线下收银台结算)后,需触发两个关键动作:一是订单系统(A系统)生成"已支付"订单记录,二是立即向库存系统(B系统)推送订单信息,触发库存扣减。若此推送失败,会导致"超卖""库存与订单不符"等严重业务问题,因此必须建立可靠的补偿机制。


推送失败的典型原因(商场系统场景适配)

  • 网络层面:高峰期网络抖动,订单系统与库存系统间的RPC调用超时;
  • 服务层面:库存系统因瞬时高并发(如促销活动)触发限流,拒绝接收新请求;
  • 业务层面:推送数据格式错误(如商品ID多写一位)、库存系统临时维护导致服务不可用;

一、核心重试代码(简化版,纯Java原生实现,重试3次)

下面是我自己写的简单的Java逻辑,大家学习解决问题的思想即可:

typescript 复制代码
/**
 * A系统推送B系统的核心方法(含3次重试逻辑)
 * @param data 推送的业务数据
 * @return 推送是否成功
 */
public boolean pushToBSystem(Object data) {
    // 重试次数:总共尝试3次(初始0,失败后重试2次)
    int maxRetry = 3;
    int currentRetry = 0;
    // 重试间隔(固定1秒,简化退避策略)
    long retryInterval = 1000L;

    while (currentRetry < maxRetry) {
        try {
            // 核心逻辑:调用B系统接口(替换为实际的HTTP/RPC调用)
            boolean pushResult = callBSystemApi(data);
            
            if (pushResult) {
                // 推送成功,直接返回
                return true;
            } else {
                // 业务层面失败(如B系统返回false),不重试,直接记录失败
                throw new RuntimeException("B系统业务处理失败,非技术故障");
            }
        } catch (Exception e) {
            currentRetry++;
            // 达到最大重试次数,记录失败并返回
            if (currentRetry >= maxRetry) {
                recordFailedPush(data, e); // 记录失败元数据到数据库
                log.error("推送B系统失败,已达最大重试次数{},失败原因:{}", maxRetry, e.getMessage());
                return false;
            }
            // 未达最大次数,等待后重试
            log.warn("推送B系统第{}次失败,{}毫秒后重试,失败原因:{}", currentRetry, retryInterval, e.getMessage());
            try {
                Thread.sleep(retryInterval);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                log.error("重试等待被中断", ie);
                return false;
            }
        }
    }
    return false;
}

// 实际调用B系统的接口(示例)
private boolean callBSystemApi(Object data) {
    // 替换为真实的HTTP/RPC调用逻辑,如RestTemplate、Feign、Dubbo等
    // 示例:
    /*
    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<Boolean> response = restTemplate.postForEntity(
        "http://B系统地址/api/receive", 
        data, 
        Boolean.class
    );
    return response.getStatusCode().is2xxSuccessful() && response.getBody();
    */
    return false; // 模拟失败,实际替换为真实逻辑
}

// 记录失败推送的元数据(核心:保障失败可追溯)
private void recordFailedPush(Object data, Exception e) {
    // 1. 生成唯一请求ID(如雪花ID)
    String requestId = SnowflakeIdGenerator.generateId();
    // 2. 序列化数据体
    String dataJson = JSONObject.toJSONString(data);
    // 3. 记录到数据库(核心字段:requestId、dataJson、失败时间、失败原因、重试次数=3、状态=人工处理)
    // 示例SQL:INSERT INTO push_fail_record (request_id, data_json, fail_time, fail_reason, retry_count, status) 
    // VALUES (?, ?, NOW(), ?, 3, '人工处理');
    log.info("已记录失败推送,requestId:{}", requestId);
}

二、简化后的补偿方案(移除异步重试,仅保留核心层)

1. 即时重试(核心自动补偿)

  • 逻辑:Java实现3次固定重试,每次间隔1秒,仅针对技术故障(网络超时、连接异常、B系统服务不可用等)重试;

  • 适用场景:解决网络抖动、B系统瞬时过载等临时性问题;

  • 关键约束:B系统接口必须实现幂等(基于requestId去重),避免重复推送导致数据错乱。

2. 人工介入补偿(最终兜底)

  • 触发条件:3次重试均失败后,自动记录失败元数据到数据库;

  • 落地方式:

  1. 开发可视化查询页面,支持按时间/状态/失败原因筛选失败记录;

  2. 配置告警(钉钉/企微/邮件),当失败记录数超过阈值(如3条)时推送告警;

  3. 人工在页面查看失败详情(数据体、失败原因),修正问题后(如B系统恢复、参数修正),手动点击"重新推送"按钮调用上述pushToBSystem方法重试。

进一步优化:这里还可以加一个兜底逻辑就是设置一个定时任务 如每小时扫描推送失败表,重新推送,如果成功了修改状态即可。这个看大家需求场景。


三、业务总结

  1. 设置重试:避免因为短暂网络波动导致业务数据没有被记录;

  2. 失败可追溯:无论重试多少次失败,都能记录完整元数据,避免数据丢失;

  3. 日志清晰:每一步重试/失败都有明确日志,便于问题定位。

  4. 设置报警提醒等:设置提醒可以让我们更关注业务

四、注意事项

在如上的案例中一定要考虑 幂等性的问题,避免消费方重复消费的问题:如库存系统必须实现"扣减接口幂等性"------以订单号ORD20240520001作为唯一键,若重复接收同一订单的扣减请求,直接返回"处理成功",避免重复扣减导致库存异常。


📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!

相关推荐
盖世英雄酱581369 小时前
Java 组长年终总结:靠 AI 提效 50%,25 年搞副业只赚 4k?
后端·程序员·trae
+VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
code bean10 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn089510 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑10 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
颜淡慕潇12 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构
Victor35612 小时前
Hibernate(28)Hibernate的级联操作是什么?
后端
Victor35612 小时前
Hibernate(27)Hibernate的查询策略是什么?
后端
飞鸟真人12 小时前
Redis面试常见问题详解
数据库·redis·面试
superman超哥12 小时前
Rust 内部可变性模式:突破借用规则的受控机制
开发语言·后端·rust·rust内部可变性·借用规则·受控机制