news 2026/2/26 22:21:58

手抖点了两次付款,为什么没扣我两笔钱?聊聊接口幂等性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手抖点了两次付款,为什么没扣我两笔钱?聊聊接口幂等性

【幂等性】分布式系统基石:深入解析幂等性设计与实现方案


人生没有太晚的开始

文章目录

  • 【幂等性】分布式系统基石:深入解析幂等性设计与实现方案
  • 前言
  • 一、概念定义——什么是幂等?
  • 二、保证幂等解决方案
    • 1.前端置灰按钮(不能保证幂等)
    • 2.数据库唯一索引(Unique Key)
    • 3.乐观锁
    • 4.防重Token令牌
    • 5.分布式锁
    • 6.状态机幂等(Status Machine)—— 业务层面的优雅
  • 总结与最佳实践

前言

  • 用户在手机弱网环境下买东西,点击“立即支付”,界面转圈圈没反应。用户心急又点了一次。

  • 浏览器发出了两次请求,如果后端没有幂等处理,用户就会被扣两次款。这在生产环境中是 P0 级事故。

  • 为了解决这个问题,我们需要引入“幂等性”。


一、概念定义——什么是幂等?

定义:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。

生活中的例子:

  • 电梯按钮(幂等): 你按一次“10楼”,电梯去10楼;你狂按一百次,电梯还是去10楼,不会飞出去。

  • 取款(非幂等): 你取100块,卡里少100;你连取两次,卡里少200。

HTTP 语义中的幂等:

  • GET(查询):天然幂等。

  • PUT(更新):通常是幂等的(把 A 改成 1,改多少次 A 都是 1)。

  • POST(新增):非幂等(每次调用都会创建新资源),这是我们要防范的重点。


二、保证幂等解决方案

1.前端置灰按钮(不能保证幂等)

当用户点击了一次扣款按钮后,前端将按钮置成灰色,防止用户提交两次。
问题

  • 可以通过postman等工具重复发送请求
  • 当网络波动时,通常会有重试机制导致重复发送

2.数据库唯一索引(Unique Key)

这是博主在项目中使用的一种方式,通过设置了唯一索引,就算多个重复请求,数据库会抛出异常,保证了幂等性。
适用:插入型操作(防重),例如插入一条新订单,不会插入两条。
缺点: 依赖数据库,分库分表时需要注意路由。

3.乐观锁

原理: update t_goods set count = count - 1, version = version + 1 where id = 1 and version = 1。
核心: 带上版本号。如果别人改过了,版本号变了,你的 SQL 就不生效。
适用: 更新库存、扣款。

4.防重Token令牌

原理

  • 进入页面前,先请求后端拿一个 Token。后端从redis拿一个Token返回。

  • 前端提交表单时带上这个 Token。

  • 后端执行 Redis.del(Token)。如果删除成功(返回1),说明是第一次,进入业务流程;如果删除失败(返回0),说明已经提交过了。

核心:在业务流程开始前进行了幂等判断,而不是像第一种数据库唯一索引那种方法(在业务中抛异常来确保幂等)。

5.分布式锁

原理:类似于防重Token令牌,也是在业务进行前进行幂等处理,业务执行前先抢锁(key=业务唯一ID),执行完释放锁。
注意点: 锁要设过期时间(防死锁);还要考虑一下看门狗机制的引入(防止业务流程还没有完成就释放了锁)

6.状态机幂等(Status Machine)—— 业务层面的优雅

这个例子有点像乐观锁,这个方案的核心在于:流程不可逆,利用当前状态做天然屏障。

生活中的例子
快递状态:待发货 -> 已发货 -> 已签收。

如果现在的状态是 已发货,你发来一个指令说“把状态改为已发货”,系统执行也没事(结果不变)。

如果现在的状态是 已签收,你发来一个指令说“把状态改为已发货”,系统直接拒绝,因为流程不能倒着走。

技术实现细节
这是最优雅、性能最好的方案,因为它不依赖 Redis,直接利用业务数据库的行锁。

SQL 的魔法: 不要先 SELECT 查状态,再 Java 判断,再 UPDATE(这会有并发缝隙)。 要直接把条件写在 UPDATE 语句里。

UPDATE ordersSETstatus='PAID'WHEREid=123ANDstatus='PENDING';

只能由PENDING变为PAID,不可能倒过来。


总结与最佳实践

  • 不要过度设计: 不是所有接口都要幂等,查询接口不需要,QPS 很低且允许少量重复的后台接口也没必要搞太复杂。

  • 唯一 ID 是关键: 无论是哪种方案,都需要一个全局唯一的 ID来标识“这是同一个请求”。

  • 推荐组合: 这里的“银弹”通常是 Token 机制(防误触) + 数据库唯一索引(兜底)。

以上就是幂等的内容了,有任何不足欢迎大佬指正。如果文章对您有所帮助,请务必点赞,收藏,您的支持就是我的最大动力!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/25 1:38:28

CodeGeeX2企业级部署实战:从环境搭建到性能调优的全链路指南

CodeGeeX2企业级部署实战:从环境搭建到性能调优的全链路指南 【免费下载链接】CodeGeeX2 CodeGeeX2: A More Powerful Multilingual Code Generation Model 项目地址: https://gitcode.com/gh_mirrors/co/CodeGeeX2 CodeGeeX2作为新一代多语言代码生成模型&a…

作者头像 李华
网站建设 2026/2/21 6:42:36

ERNIE 4.5-21B终极部署指南:如何用210亿参数快速构建企业AI应用

ERNIE 4.5-21B终极部署指南:如何用210亿参数快速构建企业AI应用 【免费下载链接】ERNIE-4.5-21B-A3B-PT 项目地址: https://ai.gitcode.com/hf_mirrors/baidu/ERNIE-4.5-21B-A3B-PT 还在为AI部署的高成本和高门槛发愁吗?🤔 ERNIE 4.5…

作者头像 李华
网站建设 2026/2/25 2:25:30

Java多线程——线程池,全方面解答,小白收藏这篇也足够了

线程池 1.1 什么是线程池 线程池是一种多线程管理机制,通过池化技术来重用现有线程而不是创建新的线程,从而降低线程创建和销毁的开销。线程池通过工作队列和线程管理来实现高效的任务执行。 1.2 为什么使用线程池 一个线程大约占用的内存为1M 解决频繁…

作者头像 李华
网站建设 2026/2/23 6:25:18

Java——数组,小白到精通,收藏这篇就够了

目录 一、认识数组 1、数组的概念 2、数组的类型 3、数组在JVM是如何存储 二、一维数组 1、一维数组的定义 1、动态初始化: 2、静态初始化: 2、一维数组的使用 [1、数组中元素的使用](about:blank#%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%…

作者头像 李华
网站建设 2026/2/23 16:25:15

链动 2+1 模式拆解:如何3 个月卖光 2000 吨白酒?

老周(化名)出身茅台镇酿酒世家,手握祖辈传承的原配方技术,满怀信心打造了自己的白酒品牌,斥巨资生产了 2000 吨优质酱香酒。本以为凭借 “茅台镇核心产区 古法工艺” 的优势能打开市场,没想到现实给了他沉…

作者头像 李华
网站建设 2026/2/25 9:51:25

3大精准流量控制策略:Apache APISIX限流技术的完整实战指南

在微服务架构中,API限流是保护后端服务免受恶意攻击和突发流量冲击的关键防线。没有合适的限流机制,你的系统可能面临网络攻击、资源耗尽和服务质量下降等严重问题。Apache APISIX作为高性能API网关,提供了一套完整的限流解决方案&#xff0c…

作者头像 李华