大厂都怎么防止重复下单?

1.问题背景

最简单的:DB事务。如创建订单时,同时往订单表、订单商品表插数据,这些Insert须在同一事务执行。

Order服务调用Pay服务,刚好网络超时,然后Order服务开始重试机制,于是Pay服务对同一支付请求,就接收到了两次,而且因为轮询负载均衡算法,落在了不同业务节点!所以一个分布式系统接口,须保证幂等性。

2.如何避免重复下单?

前端页面也可直接防止用户重复提交表单,但网络错误会导致重传,很多RPC框架、网关都有自动重试机制,所以重复请求在前端侧无法完全避免!问题最后还是如何保证服务接口的幂等性。

2.1 如何判断请求是重复的?

  • 插入订单前,先查一下订单表,有无重复订单? 不优:难以用SQL条件定义到底什么是“重复订单”
  • 订单的用户、商品、价格一样就是重复订单? 万一这用户就是连续下了俩一模一样订单呢?

所以保证幂等性要做到:

2.1.1 每个请求须有唯一标识

比如订单支付请求,得包含订单id,一个订单id最多只能成功支付一次。

2.1.2 每次处理完请求后,须有记录标识该请求已被处理

在MySQL中记录一个状态字段。如支付之前记录一条这个订单的支付流水。

2.1.3 每次接收请求时,判断之前是否处理过

若有一个订单已支付,就肯定已有一条支付流水。若重复发送这个请求,则此时先插入支付流水,发现orderId已存在,唯一约束生效,报错重复Key。就不会再重复扣款。

在往DB插记录时,一般不提供主键,而由DB在插入时自动生成。这样重复的请求就会导致插入重复的数据。MySQL的主键自带唯一性约束,若在一条INSERT语句提供主键,且该主键值在表中已存在,则该条INSERT会执行失败。因此可利用DB的“主键唯一约束”,在插数据时带上主键,以此实现创建订单接口的幂等性。

给Order服务添加一个“orderId生成”的接口,无参,返回值就是一个【全局唯一】订单号。在用户进入创建订单页面时,前端页面先调用该orderId生成接口得到一个订单号,在用户提交订单时,在创建订单的请求中携带该订单号。

该订单号其实就是订单表的主键,于是,重复请求中带的都是同一订单号。订单服务在订单表中插入数据的时候,执行的这些重复INSERT语句中的主键,也都是同一个订单号。而DB唯一约束保证,只有一次INSERT执行成功。

实际要结合业务,如使用Redis,用orderId作为唯一K。只有成功插入这个支付流水,才可执行扣款。

要求是支付一个订单,须插入一条支付流水,order_id建立一个唯一键。你在支付一个订单前,先插入一条支付流水,order_id就已经传过去了。就能写一个标识到Redis中,set order_id payed,当重复请求过来时,先查Redis的order_id对应的value,若为payed说明已支付,就别再重复支付!

然后再重复支付订单时,写尝试插入一条支付流水,DB会报唯一键冲突,整个事务回滚。保存一个是否处理过的标识也可以,服务的不同实例可以一起操作Redis。

若因重复订单导致插入 t_order 失败,则Order服务不要把该错误返给前端页面。否则,就可能出现用户点击创建订单按钮后,页面提示创建订单失败,而实际上订单创建成功了。

正确做法:这种case,订单服务直接返回订单创建成功。

3.解决ABA

3.1 什么是ABA

如订单支付后,seller要发货,发货完成后要填个快递单号。假设seller填个666,刚填完,发现填错了,赶紧再修改成888。对订单服务,这就是2个更新订单的请求。系统异常时666请求到了,单号更成666,接着888请求到了,单号又更新成888,但是666更新成功的响应丢了,调用方没收到成功响应,自动重试,再次发起666请求,单号又被更新成666了,这数据显然就错了!

3.2 解决方案

订单主表增加version列。每次查询订单时,版本号要随着订单数据返回给页面。页面在更新数据的请求中,把这个版本号作为更新请求的参数,带回给订单更新接口。

订单服务在更新数据的时候,需要比较订单的版本号是否和消息中的一致:

  • 不一致拒绝更新数据
  • 一致还需再更新数据的同时,将version+1。“比较版本号、更新数据和版本号+1”的过程须在同一事务执行
UPDATE orders set tracking_number =666,
version = version +1
WHERE version =8;

在这条SQL的WHERE条件中,version值需要页面在更新的时候通过请求传进来。

通过该版本号,就能保证,从我打开这条订单记录开始,一直到我更新这条订单记录成功,期间没有其他人修改过该订单数据。若有,则DB中的version就会改变,那我的更新操作就会执行失败。我就只能重新查询新版本的订单数据,再尝试更新。

有了这个版本号,前文的ABA即有两个case:

  •  把运单号更新为666成功,更新为888的请求带着旧版本号,就更新失败,页面提示用户更新888失败
  • 666更新成功后,888带着新版本号,888更新成功。这时即使重试的666请求再来,因为它和上一条666请求带相同版本号,上一条请求更新成功后,这个版本号已经变了,所以重试请求的更新必然失败

无论哪种情况,DB中的数据与页面上给用户的反馈都是一致的。这就实现了幂等更新且避免ABA。

4.总结

  • 创建订单服务,可通过预生成订单号,然后利用DB的订单号唯一约束,避免重复写入订单,实现创建订单服务的幂等性
  • 更新订单服务,通过一个版本号机制,每次更新数据前校验版本号,更新数据同时自增版本号,这样的方式,来解决ABA问题,确保更新订单服务的幂等性

两种幂等的实现方法,就可以保证,无论请求是不是重复,订单表中的数据都是正确的。

实现订单幂等的方法,完全可以套用在其他需要实现幂等的服务中,只需要这个服务操作的数据保存在数据库中,并且有一张带有主键的数据表即可。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/252233.html<

(0)
管理的头像管理
上一篇2025-04-29 08:31
下一篇 2025-04-29 08:32

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注