如何防止重复下单

news2025/1/20 12:05:18

一、问题背景

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

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

二、如何避免重复下单

前端页面也可直接防止用户重复提交表单,但网络错误会导致重传,很多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,订单服务直接返回订单创建成功。

三、解决 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 问题,确保更新订单服务的幂等性

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

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


文章出自:http://www.whnw.com.cn/news/1706239.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/469251.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法基础第一章

算法基础 第一章:基础算法1、排序2、二分查找3、大数据量的加法和减法(高精度加减法)3.1、加法3.2、减法 4、前缀和4.1、一维前缀和4.2、二维前缀和 5、差分5.1、一维差分5.2、二维差分 6、双指针7、位运算7.1、lowbit的应用 8、离散化9、区间…

基于c与数据库实现的电子词典

1.搭建客户端与服务器,客户端使用多线程方式,主线程跑发送信息给服务器功能,分支线程跑循环接收服务器信息功能 客户端代码: //TCP客户端的搭建 int tcp_kehuduan() {//创建字节流套接字cfd socket(AF_INET,SOCK_STREAM,0);if(c…

【TCP服务器的演变过程】C++使用libevent库开发服务器程序

C使用libevent库开发服务器程序 一、引言二、libevent简介三、Libevent库的封装层级3.1、reactor对象封装struct event_base3.2、事件对象struct event3.3、struct bufferevent对象3.4、evconnlistener对象3.5、事件循环3.6、事件处理 四、完整示例代码小结 一、引言 手把手教…

病毒的遗传分析

噬菌体的生活周期 烈性噬菌体 噬菌体吸附到宿主细胞上尾丝鞘收缩,中轴刺穿宿主细胞头部的DNA被送入宿主细胞在数分钟内,所有的细菌核算和蛋白质合成都被抑制 噬菌体大分子合成(DNA,蛋白外壳),细菌的核算被降解 噬菌体组装:DNA被包到头部,组装尾部,装上尾丝 宿主细胞破裂:约20…

新品首发丨计讯物联5G水利遥测终端机TY511-B3,推动5G+智慧水利再进化

当前,水利在全面感知、全面互联、智慧应用等方面较其他行业存在明显差距。基于物联网行业通信技术的不断突破和水利行业对高速移动通信的强烈需求,5G移动通信以高速率、大带宽、低时延、广连接等特点让水利数据快速高效地传输成为可能,逐渐成…

如何优雅的写个try catch的方式!

软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块,不仅有大量的冗余代码,而…

流程图拖拽视觉编程-流程编辑器2

目录 一、简介 二、自定义图元 2.1图元设计 2.2 端口QNEPort 2.3节点块QNEBlock 一、简介 前期文章: 流程图拖拽视觉编程--概述_Jason~shen的博客-CSDN博客 流程图拖拽视觉编程-流程编辑器_Jason~shen的博客-CSDN博客 本期内容: 本期将继续介绍…

【Leetcode】236.二叉树的最近公共祖先

二叉树的最近公共祖先 题目思路1思路2代码 题目 思路1 给我们两个二叉树中的节点 找出里面的最近公共祖先 首先我们要分析p q 两点有哪些位置关系 p q在根节点的两侧 此时最近公共祖先就是根节点 在根节点一侧 此时两个节点都在根节点左侧 此时可以递归二叉树 让root.left…

内存取证小练习-基础训练

这是题目和wolatility2.6的链接 链接:https://pan.baidu.com/s/1wNYJOjLoXMKqbGgpKOE2tg?pwdybww 提取码:ybww --来自百度网盘超级会员V4的分享 压缩包很小,题目也比较简单基础,可以供入门使用 1:Which volatility…

使用auto-gpt来写一篇技术文章(如何部署autogpt+遇到的问题+如何使用)

文章目录 前言一、autogpt本地部署1.clone代码2.启动虚拟环境3.运行项目 二、使用aotogpt生成文章1.人设描述2.设置目标3.文章的生成过程4.文章的生成内容 总结 前言 最近AI技术的发展非常迅猛,尤其是和GPT相关的技术,备受瞩目。近日,Autogp…

nvm安装管理npm

1.根据http://t.csdn.cn/mRwCQ这个教程完成安装 2.使用nvm install [指定版本号],下载了16.15.1,和10.15.1版本 2.5 这里其实出了一点问题,我在下载16.15.1时,因为墙的问题其实是下载出错,报了一个error的&#xff0c…

手推A Unified Solution to Constrained Bidding in Online Display Advertising论文

A Unified Solution to Constrained Bidding in Online Display Advertising:一种对在线展示广告约束出价问题的通用解决方案 未开放但是可以搜到 NeuralAuction: 电商广告中的端到端机制优化方法 https://arxiv.org/abs/2106.03593 一种使用真负样本的在线延迟反…

JVM,关于JVM基础的知识,你确定不了解一下吗?

目录 一.JVM的概念 什么是JVM? 二.JVM的运行流程 1.class文件如何被JVM加载并运行 2.JVM运行时数据包括哪些区域(M) 三.类加载的过程(M) 四.双亲委派模型 1.双亲委派模型分析 2.JAVA中有哪些类加载器&#xf…

Maven私服搭建

为什么要搭建私服 通常在maven项目的pom.xml文件中引入了某个依赖包之后&#xff0c;maven首先会去本地仓库去搜索&#xff0c;本地仓库搜索不到会去maven的配置文件settings.xml中配置的maven镜像地址去找&#xff0c;比如&#xff1a; <mirrors><!-- mirror| Specif…

动态规划 --- 电线布设

动态规划 — 电线布设 题目描述 说是话&#xff0c;刚看到也是很懵逼&#xff0c;不想交子集是什么&#xff1f;乱七八糟的连线。 其实仔细想想后&#xff0c;觉得题目应该是说给定了这些点的连接端点&#xff0c;然后从他给的连线中选择出不想交的且条数最多的连线&#x…

Flink自定义函数之标量函数(UDF函数)

1.背景 flink本身给我们提供了大量的内置函数&#xff0c;已经能满足我们绝大部分的需求&#xff0c;但是如果确实是碰到了一些特殊的场景&#xff0c;无法满足我们的需求的时候&#xff0c;我们可以使用自定义函数来解决。 自定义函数大致可以分为标量函数&#xff08;UDF函…

Linux-安装Python2.7

一、简介 正常情况下&#xff0c;使用sudo apt install python来安装就好了。如果发现这个指令报错了&#xff0c;此时就需要手动安装Python2.7了。例如报错如下&#xff1a; 二、实操 1.下载Python2.7的相关源码&#xff08;以2.7.18为例&#xff09; 下载地址如下&#xff1a…

Linux第四章

文章目录 前言一、快捷键小技巧二、软件安装三、systemctl控制软件启动关闭四、软链接五、日期和时区六、ip地址和主机名七、配置linux固定ip地址八、网络请求和下载九、端口十、进程管理十一、主机状态监控十二、环境变量十三、linux文件的上传和下载十四、压缩和解压总结 前言…

第五章 使用RAID与LVM磁盘阵列技术

第五章 使用RAID与LVM磁盘阵列技术 一、RAID磁盘冗余阵列 1、部署磁盘阵列 &#xff08;1&#xff09;、RAID0、1、5、10方案技术对比 RAID级别最少硬盘可用容量读写性能安全性特点02nn低追求最大容量和速度&#xff0c;任何一块盘损坏&#xff0c;数据全部异常。12n/2n高追…

魔兽世界服务端用户注册以及网页的搭建教程

魔兽世界服务端用户注册以及网页的搭建教程 大家好我是艾西&#xff0c;上一章我们讲解了怎么编译一个魔兽的服务端以及安装最后进到我们自己的游戏。那么在平时娱乐的同时肯定是需要和朋友们一起玩游戏才会更有意思&#xff0c;那么今天艾西教大家怎么搭建用户注册页面以及网…