mysql 重复下单_如何避免下重复订单(转)-程序员宅基地

技术标签: mysql 重复下单  

电子交易的一个很基本的问题,就是避免用户下重复订单。用户明明想买一次,结果一看下了两个单。如果没有及时发现,就会带来额外的物流成本和扯皮。对商家的信誉也不好看。

从技术上看,这是一个分布式一致性问题;但实际上,技术无法100%解决这类问题,得结合多种手段综合处理。这里就来说道说道。

为啥会下重了呢?

原因1:客户端bug

比如下单的按键在点按之后,在没有收到服务器请求之前,按键的状态没有设为已禁用状态,还可以被按。又或者,在触摸屏下,用户手指的点按可能被手机操作系统识别为多次点击。

嗯,谁能保证客户端不偶尔出个什么bug 呢。

原因2: 超时

用户的设备与服务器之间可能是不稳定的网路。这样一个下单请求过去,返回不一定回得来。超时最大的问题是: 从用户的角度,他无法确定下单的请求是还没到服务器,还是已经到了服务器但是返回丢失了。——用户无法区分到底这个单下了还是没下。

这样在等待一个超时后,UI可能会提示用户下单超时,请重复再试。

format,png

下单超时

原因3: 用户的App闪退/人工强退,之后重新打开重新下单

也许可以使用一些技术手段避免用户下重单,但是心急的用户可能会重启流程/重启App/重启手机。在这种强制的手段下,任何技术手段都会失效——用户压根就不让你的技术执行,你怎么玩?

在这些条件下,如何避免用户多下了一笔订单呢?

用幂等防止重复订单

在技术方面,这是一个分布式一致性的问题,即客户端和服务器端对某个订单是否成功/失败达成一致。防止重单的关键是使用一个由客户端生成的,可用于避免重复的key,俗称dedup key(deduplicate key之意)。这个key可以用任意可以保证全局唯一性的方式生成,比如uuid。客户端和服务器需要使用这个dedup key作为串联条件,一起解决去重问题。

客户端的流程

客户端需要实现这样一个下单界面。用户点击【确认下单】时,应该产生一个独一无二的dedup key,连定订单数据发送给服务器端。在服务器返回之前,该界面应该一直等待,直到服务器响应成功/失败或者超时发生(比如15秒后,收不到服务器响应)。如果超时发生,应该向用户提示是否重试下单或者退出该界面。当用户点击【重试】时,应该用刚刚生成的dedup key来再次发送下单请求——如果用户一直不退出这个流程,每次用户点击重试,都应该用这个dedup key来重试下单,直到服务器正常返回,或者用户放弃返回。

format,png

下单的客户端流程

后端数据表设计

后端在订单数据表中,需要增加dedup_key这列,并设置唯一约束。

create table order(

# ...

dedup_key varchar(60) not null comment 'key to pretend order duplication',

# ...

unique uniq_dedup_key(dedup_key)

);

下单的实现

在实现下单逻辑时,基于该dedup_key实现一个"create-or-get"语义的下单接口——简单说就是

如果带有指定dedup_key的订单已经存在,则直接返回;否则,用该dedup_key下单。

用伪代码表示大概是:

@Transactional

Order createOrder(Integer userId, String prodCode, Decimal amount, String dedupKey) {

try {

String orderId = createOrder(userId, prodCode, amount, deupKey); // insert a new order

Order order = getOrderById(orderId); // read order from db

order.setDuplicated(false);

return order;

} catch(UniqueKeyViolationException e) {

// if duplicated order has existed

Order order = getOrderByDedupKey(dedupKey);

order.setDuplicated(true);

return order;

} catch (Exception e) {

// hanlde other errors and rollback transaction ...

}

}

这时,这段下单代码总是能返回一个订单(除非发生一些DB挂了之类的错误),要么是新创建的,要么就是一个已经存在的单。注意,最好在订单里增加一个属性(比如例子中用“duplicated”)来表示这个订单是这次新生成的,还是因为幂等而直接返回的。这样前端可以有针对性的对这两种情况提示不同的文案。

技术搞定幂等就足够了吗?

上面的流程没有考虑一种情况,就是用户中途强制退出客户端,或者直接点击【返回】回到产品页,重新走下单流程。这个时候客户端就无法判断用户到底是想重新下单,还是想第二次下单。此时,可以从产品设计上考虑一下。

比如,在客户端缓存一个表,记录所有没有确认结果的订单。

产品代码产品数量金额dedup key

未确认订单1

AAA

1

1000

xxx-yyy-zzz

未确认订单2

BBB

2

500.00

Aaa-bbb-ccc

...

通过这个表,我们可以猜一下用户的意图。比如,如果用户重新提交了一笔订单,其产品代码、金额与表中记录的某条完全一致,就可以提示一下用户:

format,png

提示一下用户是不是下重了

如果用户想重试,可以继续用表中对应记录的dedup key重新发起下单。

这样不是绝对准确的,仅仅是尽量的减少用户误操作的可能性。当然,在产品设计上可以能出于用户交互简化,不一定真的会这样做。这就需要其他机制来配合,比如“通知”。

通知

一旦服务器下单成功,可以通过某种通知机制(如APNS、Websocket)主动将订单推送至客户端,强行让客户端重新拉取最新的订单信息,并配合“未确认订单”表,以通知Badge/弹框等方式提示用户刚刚一笔状态未知的订单成功/失败了。

另外一种手段就是,服务器端实时扫描用户的下单数据,一旦发现可能的重单,就立刻通知客服主动联系用户,及时处理问题。

如果还拦不住……

经过层层阻拦,可能还是会有用户误操作,直到收到两份商品才发现下重了。此时就得依靠运营/客服的支持了。提供用户申诉的手段,让用户提出哪些订单是重复的,并且由销售系统店家、商品提供者和买家三方共同根据用户操作的记录来协商如何处理。我们需要让技术帮助让这种人工处理的几率尽量小。因为每次处理都会耗费较大的人工成本,和一些运营费用(比如赔款、小礼品等等)。

这么麻烦,有必要吗?

这要分业务场景,对于很多电商来讲可能不是必要的。因为从用户下单到订单被审核处理进入到发货阶段需要一定的时间(可能是半小时~1小时),并且一定是支付成功后才会开始进行下一步流程。在这个时间段,用户大概率能从网络错误中恢复过来,自行区分是否下重了。配合客服主动提示,会极大的降低出问题的概率。

但是对于理财服务来说,这种去重就非常必要了。因为

“下单+支付”。用户购买理财往往是“下单+支付”一起执行,不可以单独下单/单独支付

用户的入金可能很大。例如数万,数十万

准确性丢失。如果一旦下重了,有可能影响用户的投资资金配置的准确性。

撤销难。部分理财产品存在下单不可撤销的问题;或者即便撤销,资金也无法立刻回款。等到回款,可能这个购入机会就错过去了。例如对于基金交易,错过1个交易日,价格就会发生变动。

基于这些特性,在理财产品中,就要竭尽全力的去重。

结论

以上所讲是处理重复订单问题的一般方法。你可以注意到,无论多么好的技术,也不可能100%的拦截所有的可能性,必须依靠技术+产品设计+运营支持的综合手段才能解决这类问题。

另外,本文还没涉及到关于订单支付(支付也可能重复哦)带来的进一步的复杂性,也没有讨论在高并发情况下的性能优化,仅仅讨论下单本身的问题。所以可以想象一下现实中的交易业务比这里的说的要复杂得多。

本文介绍的原理也不仅仅适用于防止下重复订单,而是可以应用到任何需要“创建一个不应该重复资源”的场景,比如“向用户发一条通知”,“触发一次不能重复的批处理任务“……

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34055902/article/details/113293996

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法