如何避免下重复订单

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

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

为啥会下重了呢?

  • 原因1:客户端bug

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

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

  • 原因2: 超时

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

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

下单超时

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

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

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

用幂等防止重复订单

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

客户端的流程

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

下单的客户端流程

后端数据表设计

后端在订单数据表中,需要增加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 orderOrder order = getOrderById(orderId); // read order from dborder.setDuplicated(false);return order;} catch(UniqueKeyViolationException e) {// if duplicated order has existedOrder order = getOrderByDedupKey(dedupKey);order.setDuplicated(true);return order;} catch (Exception e) {// hanlde other errors and rollback transaction ...}
}

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

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

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

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

 产品代码产品数量金额dedup key
未确认订单1AAA11000xxx-yyy-zzz
未确认订单2BBB2500.00Aaa-bbb-ccc
...    

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

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

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

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

通知

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

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

如果还拦不住……

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

这么麻烦,有必要吗?

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

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

  • “下单+支付”。用户购买理财往往是“下单+支付”一起执行,不可以单独下单/单独支付
  • 用户的入金可能很大。例如数万,数十万
  • 准确性丢失。如果一旦下重了,有可能影响用户的投资资金配置的准确性。
  • 撤销难。部分理财产品存在下单不可撤销的问题;或者即便撤销,资金也无法立刻回款。等到回款,可能这个购入机会就错过去了。例如对于基金交易,错过1个交易日,价格就会发生变动。

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

结论

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

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

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


链接:https://www.jianshu.com/p/e618cc818432

 

其它相对简单但并不完美的解决办法

比如客户端与服务端没有用一个唯一标识来做请求的标识符,这是要怎么来解决重复下单问题呢?

1 客户端在用户点击按钮发起请求后,按钮要置为不可用

2 在服务端处理过程中,要加锁,如果是单实例的加本地缓存锁即可,多个实例最好加分布式锁

部分代码如下:

 var isdoing = Gd.CacheApi.UserCapital.GetUserConsumeLockCache(userId);if (isdoing < 1){//加锁Gd.CacheApi.UserCapital.SetUserConsumeLockCache(userId);var result = UserConsume(userId, consumeTypeId, consumeMoney, isAnonymous, wishContent, consumeNums, isShowWish, toUserName);call.Result = result;if (result.ResultCode != 0){call.Error = result.ResultMsg;}else{call.Error = "";}//移除锁Gd.CacheApi.UserCapital.RemoveUserConsumeLockCache(userId);}

此种方法可以有效减少出现重复单,但并不完美,相对完美的还是上面的方案!

--- end ---


 


---------------------
作者:Jack2013tong
来源:CSDN
原文:https://blog.csdn.net/huwei2003/article/details/105712692
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

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

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

相关文章

图像分类学习笔记

1.计算机认识图像的方式&#xff1a;都是数字。例如一个 128X128 的3通道的图片 是由 128X128X3个数字 组成的。 2.面临的难点&#xff1a;一幅图可以说明。 3.分类器 A&#xff1a;Nearest Neighbor Classifier&#xff1a;与CNN无关&#xff0c;但是可以帮助我们理解一下分类…

知物由学 | 干货!一文了解安卓APP逆向分析与保护机制

“知物由学”是网易云易盾打造的一个品牌栏目&#xff0c;词语出自汉王充《论衡实知》。人&#xff0c;能力有高下之分&#xff0c;学习才知道事物的道理&#xff0c;而后才有智慧&#xff0c;不去求问就不会知道。“知物由学”希望通过一篇篇技术干货、趋势解读、人物思考和沉…

[转]以终为始,详细分析高考志愿该怎么填

为什么写这篇文章&#xff1f; 之所以写本文&#xff0c;是因为我自己有用处。 我简要介绍&#xff0c;长话短说。我从一个普通的211本科毕业&#xff0c;已经接受社会"毒打"多年&#xff0c;回想起高考填志愿&#xff0c;依然会觉得有些许遗憾。我在贵州省的一个小县…

ASP.NET Core 中的重定向

前言在《如何使用ASP.NET Core Web API实现短链接服务》中&#xff0c;我们使用了Redirect方法返回跳转状态码:[HttpGet("{shortUrl}")] public IActionResult GetUrl(string shortUrl) {var hashids new Hashids("公众号My IO", minHashLength: 6);var i…

客户端应用试用限制设计

1.概要最近接到公司安排的任务给客户端设计一个“试用30天”的一个需求&#xff0c;其功能主要是为了防止客户拿到产品之后不支付尾款继续使用。众所周知靠纯软件想防“盗版”&#xff0c;“限制试用”等做法是行业难题。只要价值足够高一定有人会破解绕过你的所有防线达到免费…

【开发工具之Spring Tool Suite】6、用Spring Tool Suite简化你的开发

如果你是一个喜欢用spring的人&#xff0c;你可能会在欣赏spring的强大功能外&#xff0c;对其各样的配置比较郁闷&#xff0c;尤其是相差较大的版本在配置文件方面会存在差异&#xff0c;当然你可以去花不少的时间去网上查找相关的资料&#xff0c;当你准备使用更高版本spring…

康威定律,作为架构师还不会灵活运用?

Soft skills are always hard than hard skills. 软技能比硬技能难。 老板听说最近流行“微服务”&#xff0c;问架构师咱们的系统要不要来一套&#xff1f;老板又听说最近流行“中台系统”&#xff0c;问架构师咱们要不要搞起来&#xff1f;其实&#xff0c;这些问题不用老板问…

WebView2 通过 PuppeteerSharp 实现RPA获取壁纸 (案例版)

此案例是《.Net WebView2 项目&#xff0c;实现 嵌入 WEB 页面 Chromium内核》文的续集。主要是针对WebView2的一些微软自己封装的不熟悉的API&#xff0c;有一些人已经对 PuppeteerSharp很熟悉了&#xff0c;那么&#xff0c;直接用 PuppeteerSharp的话&#xff0c;那就降低了…

[转]2022 年 Java 行业分析报告

你好&#xff0c;我是看山。 前段时间介绍了从 Java8 到 Java17 每个版本比较有特点的新特性&#xff08;收录在 从小工到专家的 Java 进阶之旅 专栏&#xff09;&#xff0c;今天看到 JRebel 发布了《2022 年 Java 发展趋势和分析》&#xff0c;于是借此分析一下 Java 行业的现…

Mysql 数据库学习笔记03 存储过程

一、存储过程&#xff1a;如下 通过 out 、inout 将结果输出&#xff0c;可以输出多个值。 * 调用存储过程&#xff1a; call 存储名称&#xff08;参数1&#xff0c;参数2&#xff0c;...&#xff09;; 如指定参数不符合要求&#xff0c;返回 Empty Set * 查询存储过…

vue+vuecli+webapck2实现多页面应用

准备工作 在本地用vue-cli新建一个项目&#xff0c;首先安装vue-cil&#xff0c;命令&#xff1a; npm install -g vue-cli 新建一个vue项目,创建一个基于"webpack"的项目,项目名为vuedemo&#xff1a; vue init webpack vuedemo 这里有一个地方需要改一下&#xff0…

一文把Docker、Kubernetes搞懂:什么是Docker?什么是Kubernetes?Docker和Kubernetes有什么关系和区别?通俗解释Docker、Kubernetes

一、Docker解决的问题 1、统一标准 ● 应用构建 ○ Java、C、JavaScript——编程各异 ○ 打成软件包 ○ .exe&#xff08;类似Windows&#xff0c;最终也只是生产exe执行&#xff09; ○ 使用docker build … 打包成 镜像——这就类似于exe ● 应用分享 ○ 所有软件的镜像放到一…

程序员双手飞快敲键盘的时候是在敲代码吗?

当你看到一个程序员的两只手在键盘上上下翻飞&#xff0c;行云流水的时候&#xff0c;多半不是在敲击代码大概率是在跟产品经理撕逼讨论需求另一种可能就是在跟测试打口水仗10%几率是在论坛码字摸鱼或者和人家开喷了。1%几率是在跟MM聊天可以手速飞快而不需要停下思考的代码&am…

几分钟上线一个网站 真是神器

1、ToolJet 简介 ToolJet 是一个开源的低代码框架&#xff0c;可以快速构建和部署内部工具&#xff0c;而无需工程团队付出太多努力。您可以连接到您的数据源&#xff0c;例如数据库&#xff08;如 PostgreSQL、MongoDB、Elasticsearch 等&#xff09;、API 端点&#xff08;To…

移植Python3到TQ2440(一)

平台 硬件&#xff1a;TQ2440 64MB内存 256MB NandFlashbootloader&#xff1a;U-Boot 2015.04kernel&#xff1a;linux-4.9Python: Python-3.6.0工具链&#xff1a;arm-none-linux-gnueabi-gcc 4.8.3概述 现在树莓派很火&#xff0c;在树莓派上面用户可以通过Python来控制板…

WinForm(六)组合布局属性

WinForm是基于控件的&#xff0c;不像codemark的架构&#xff0c;可以非常灵活的用mark来布局&#xff0c;它只能用控件布局属性和窗口控件来完成对UI的布局。容器控件有一组控件叫容器控件&#xff0c;对布局特别有作用&#xff0c;它们分别是&#xff1a;TableLayoutPanel&am…

Qt 网络编程

QT 网络编程 TCP 编程 模块引入 QT network 头文件 #include <QTcpServer> // TCP服务器端使用 #include <QTcpSocket> // TCP服务器和客户端都使用 编程流程 服务端 1&#xff09;实例化 QTcpServer 对象 -----------------------------> socket 2&#x…

日利率

2019独角兽企业重金招聘Python工程师标准>>> 利率计算 转载于:https://my.oschina.net/u/3342652/blog/1649028

Kafka 安装详解

注意&#xff1a;确保有JDK1.8版本及以上 官方文档&#xff1a;https://kafka.apache.org/quickstart 清华镜像下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/ 首先下载安装包&#xff0c;在linux及Windows都可以使用。 1. Centos 安装部署 1.1 下载 将下…

【Maui正式版】创建可跨平台的Maui程序,以及有关依赖注入、MVVM双向绑定的实现和演示...

前言&#xff1a;Maui终于在2022年8月9日推送出来了。今儿就迫不及待来把玩一下先。A、我本地已有VS2022&#xff0c;不过版本比较老&#xff0c;此处选择更新。工具 -> 获取功能和更新里面&#xff0c;可以获取到新版本更新。B、最新版本是17.3.0&#xff0c;我本地只有17.…