业务场景
在分布式项目中,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次重试均失败后,自动记录失败元数据到数据库;
-
落地方式:
-
开发可视化查询页面,支持按时间/状态/失败原因筛选失败记录;
-
配置告警(钉钉/企微/邮件),当失败记录数超过阈值(如3条)时推送告警;
-
人工在页面查看失败详情(数据体、失败原因),修正问题后(如B系统恢复、参数修正),手动点击"重新推送"按钮调用上述
pushToBSystem方法重试。
进一步优化:这里还可以加一个兜底逻辑就是设置一个定时任务 如每小时扫描推送失败表,重新推送,如果成功了修改状态即可。这个看大家需求场景。
三、业务总结
-
设置重试:避免因为短暂网络波动导致业务数据没有被记录;
-
失败可追溯:无论重试多少次失败,都能记录完整元数据,避免数据丢失;
-
日志清晰:每一步重试/失败都有明确日志,便于问题定位。
-
设置报警提醒等:设置提醒可以让我们更关注业务
四、注意事项
在如上的案例中一定要考虑 幂等性的问题,避免消费方重复消费的问题:如库存系统必须实现"扣减接口幂等性"------以订单号ORD20240520001作为唯一键,若重复接收同一订单的扣减请求,直接返回"处理成功",避免重复扣减导致库存异常。
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!