后端实操笔记:从日常排查到规范落地,我是如何解决接口幂等性问题的

81 阅读6分钟

在后端开发中,接口幂等性问题不算罕见,但真正引起重视往往是在出现数据异常之后。我负责的订单系统上线半年后,运维同事在每周例行的日志审计中,发现支付回调接口存在少量重复请求记录,对应的数据库里出现了12笔重复订单。虽然数量不多,涉及金额也不大,但这类问题如果不及时处理,在大促等高并发场景下很可能引发批量异常。更关键的是,排查后发现,问题根源并非支付网关的恶意重试,而是我们的接口本身缺乏完整的幂等性防护机制。

接到运维反馈后,我先梳理了支付回调接口的业务流程:用户支付完成后,支付网关会向我们的回调接口推送支付结果,接口接收数据后做校验,然后更新订单状态并写入支付记录。从流程上看,似乎没什么问题,但查看接口日志和数据库记录后,很快定位了关键细节:有12笔重复订单对应的回调请求,时间间隔都在500ms以内,且请求参数完全一致。进一步跟支付网关技术人员确认得知,他们的系统有超时重试机制,当接口响应超过500ms时会自动重试,而我们的接口在高峰期偶尔会因为数据库锁等待导致响应延迟,从而触发了重试。

这个结论让我意识到,之前的开发确实存在疏漏。当时为了赶上线进度,只在接口里做了基础的参数格式校验,完全没考虑幂等性问题。虽然平时流量不大时问题不明显,但隐患一直存在。接下来的核心任务,就是选择一套合适的幂等性解决方案,既要解决当前问题,又要兼顾系统性能和后续可维护性。

结合团队的技术栈和系统现状,我们初步筛选了三种常见的解决方案,并且分别做了测试验证。第一种是“数据库唯一索引”方案,这也是最基础的做法——给订单号字段添加唯一索引,当重复请求过来时,数据库会抛出主键冲突异常,我们在代码里捕获异常后返回成功。这种方案的优势是开发成本极低,运维同事配合加索引只需要几分钟,但测试时发现一个问题:如果重复请求频率过高,大量的主键冲突异常会增加数据库日志开销,而且异常处理逻辑不够优雅。

第二种方案是“Redis分布式锁”,这是高并发场景下的常用方案。具体思路是,收到回调请求后,先以订单号为key向Redis发起加锁请求,加锁成功再执行后续业务逻辑,执行完成后释放锁。为了避免死锁,还需要给锁设置过期时间。这个方案的防护效果很好,但也有明显缺点:我们的Redis集群主要用于缓存热点数据,之前曾因为网络波动出现过短暂不可用的情况,引入分布式锁会增加系统的依赖风险;而且加锁、解锁的代码需要考虑各种异常场景,比如锁超时、释放别人的锁等,开发和测试成本都比较高。

第三种方案是“WAF请求去重+接口状态校验”,这个方案是结合我们现有的技术储备想到的。之前为了做基础的安全防护,服务器上已经部署了雷池WAF,它自带“请求去重”功能,可以通过配置拦截短时间内的重复请求。我们的思路是,先用WAF做第一层过滤,拦截大部分重复请求,再在接口层做第二层校验,形成双重防护。这样既能降低接口的处理压力,又能避免单一方案的局限性。

确定方案后,落地过程比预期更顺利。第一步是配置WAF:登录WAF控制台,找到“请求控制”模块,选择“按参数去重”策略,指定“orderNo”作为去重参数,设置10秒的时间窗口——也就是说,10秒内针对同一个订单号的重复请求会被直接拦截。这个配置不需要写代码,运维同事按照文档操作,20分钟就完成了。第二步是修改接口代码,增加状态校验逻辑:收到请求后,先根据订单号查询数据库,如果订单已经存在且状态为“已支付”,就直接返回成功响应,不执行后续的更新和插入操作;如果订单不存在或者状态未支付,再执行正常的业务流程。这部分代码改动很小,只增加了四五行查询和判断逻辑,加上测试总共花了1个小时。

为了验证效果,我们用JMeter做了针对性压测:模拟支付网关在1秒内对同一个订单号发起5次回调请求,持续10分钟。压测结果显示,WAF成功拦截了其中80%的重复请求,剩下的20%请求在接口层被状态校验拦截,数据库里只生成了1条订单记录和1条支付记录,完全符合预期。而且接口的平均响应时间只增加了15ms,对系统性能几乎没有影响。

879b0f3f4676327d6a3d099e9b7781eb.png

47c2bd8936bcb50c18e6981b10ba8a07.png

方案上线后,我们持续监控了一个月,期间经历了一次小型促销活动,支付回调接口的请求量达到平时的3倍,但没有再出现一笔重复订单。这次经历也让我们团队意识到,接口幂等性防护不应该是“事后补救”,而应该是“事前规划”。随后,我牵头整理了《接口幂等性设计规范》,明确了不同场景下的解决方案:对于支付、订单等核心接口,采用“WAF去重+接口校验+唯一索引”的三重防护;对于查询类接口,因为本身天然幂等,只需做基础的参数校验;对于新增类接口,如果没有唯一标识,采用“令牌桶”方案。

c32ee0bc906c26f7f530f8f6839a3398.png

这里给同行提两个实际开发中的小建议:一是做幂等性设计时,要结合业务场景选择方案,不要盲目追求复杂的分布式方案,适合自己系统的才是最好的;二是尽量利用现有工具降低开发成本,比如我们用到的WAF,原本是做安全防护的,没想到在幂等性防护上也能发挥作用。如果大家在实际开发中遇到过不同的幂等性问题,或者有更轻量的解决方案,欢迎在评论区交流,互相借鉴学习。