公司的支付系统出了个大事故。一笔订单因为网络抖动,支付回调超时了,系统重试了扣款。结果扣了两笔——用户投诉,财务对账发现了差异,运营逐笔人工退款。查日志发现,不是网络不通,是"半通"——第一次扣款请求发出去了,银联处理成功了,但响应在回来的路上丢了。系统认为扣款失败,又发了一次。 这种"网络半通"是分布式系统里最危险的情况——操作已经执行了,但调用方不知道。不加重试怕丢,加多了怕重。今天聊聊怎么用业务流水号加状态机,让重试在"安全"的边界内进行。 问题的本质:操作不是天然幂等的 HTTP 的 POST 不是幂等的,数据库的 INSERT 不是幂等的,银行的扣款接口更不是幂等的。如果不做任何处理,同一个扣款请求发两次,钱就会扣两笔。 分布式系统里有太多场景需要重试——网络抖动、超时、服务暂时不可用。重试本身是合理的,问题在于你拿什么来判断"这次重试的请求,上一次有没有已经成功过了"。 答案就是业务流水号。不是数据库自增 ID,不是 UUID,而是一个由业务方生成、全链路唯一、可用来判断"这条请求我见没见过"的标识。 业务流水号:请求的唯一身份证 业务流水号的核心原则:由发起方生成,....
第三方接口总是超时?教你用Gateway重试+降级让服务稳如磐石
前言 之前我们对接了一个第三方支付接口,对方服务不太稳定,经常超时或者返回502。结果就是,用户支付失败率飙升,客服电话被打爆了。 刚开始我们只是在业务代码里加了重试逻辑,但效果不理想。后来在Gateway层统一处理,配合降级兜底,问题彻底解决了。今天就把这套方案分享给大家。 问题背景 在微服务架构中,我们经常需要调用第三方接口,比如支付、短信、物流等。这些接口往往存在以下问题: 服务不稳定:第三方服务经常超时或返回错误 网络抖动:网络质量差导致请求失败 突发流量:第三方服务扛不住突发流量 维护窗口:第三方服务定期维护,暂时不可用 这些问题会导致: 用户体验差,请求失败率高 业务中断,影响核心功能 客服压力大,投诉电话多 运维被动救火,疲于奔命 传统方案 vs 优化方案 传统方案:业务层重试 public PaymentResult pay(PaymentRequest request) { for (int i = 0; i < 3; i++) { try { return thirdPartyPayService.pay(request); } catch (Exce....
