如何避免下重復訂單

電子交易的一個很基本的問題,就是避免用戶下重復訂單。用戶明明想買一次,結果一看下了兩個單。如果沒有及時發現,就會帶來額外的物流成本和扯皮。對商家的信譽也不好看。

從技術上看,這是一個分布式一致性問題;但實際上,技術無法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.pswp.cn/news/283838.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/283838.shtml
英文地址,請注明出處:http://en.pswp.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…

C#IO

System.IO 命名空間包含允許讀寫文件和數據流的類型以及提供基本文件和目錄支持的類型。string url "C:\chisp.log";if (System.IO.File.Exists(url)){Response.Write("文件存在");}else{ Response.Write("文件不存在"); }System.IO.File.Exist…

Lind.DDD.Manager里的3,7,15,31,63,127,255,511,1023,2047

回到目錄 進制 我是一個程序猿&#xff0c;我喜歡簡單的數字&#xff0c;十進制如何&#xff0c;數字太多&#xff0c;有10種數字組成&#xff0c;但由于它廣為人知&#xff0c;所有使用最為廣泛&#xff0c;人們的慣性思維培養了十進制&#xff0c;并說它是最容易被計算的數字…

3.20學習內容,字符串與列表

一、字符串類型&#xff1a; 作用&#xff1a;名字&#xff0c;性別&#xff0c;國籍&#xff0c;地址等描述信息 定義&#xff1a;在單引號\雙引號\三引號內&#xff0c;由一串字符組成。 需要掌握的方法&#xff1a; 1、strip 去除指定字符lstrip 去除左邊指定字符rstri…

客戶端應用試用限制設計

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;這些問題不用老板問…

使用onclick跳轉到其他頁面。使用button跳轉到指定url

1. οnclick"javascript:window.location.hrefaa.htm" 2. οnclick"locationURL"3,。 οnclick"window.location.href?id11"轉載于:https://www.cnblogs.com/wujixing/p/5856087.html

Avalonia Beta 1對WPF做了很多改進

\看新聞很累&#xff1f;看技術新聞更累&#xff1f;試試下載InfoQ手機客戶端&#xff0c;每天上下班路上聽新聞&#xff0c;有趣還有料&#xff01;\\\Avalonia將自己定義為“基于WPF&#xff08;使用XAML、數據綁定以及lookless控件等&#xff09;的跨平臺.NET UI框架。”在第…

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 * 查詢存儲過…

android 代碼混淆模板

#指定代碼的壓縮級別 -optimizationpasses 5 #包明不混合大小寫 -dontusemixedcaseclassnames #不去忽略非公共的庫類 -dontskipnonpubliclibraryclasses#優化 不優化輸入的類文件 -dontoptimize#預校驗 -dontpreverify#混淆時是否記錄日志 -verbose# 混淆時所采用的算法 -opt…

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 ● 應用分享 ○ 所有軟件的鏡像放到一…

Python-高階函數

#encodingUTF-8import sys # 高階函數高階函數實際上是參數可接受函數的函數即參數為函數的函數 # map()map()接收兩個參數&#xff0c;一個是函數&#xff0c;一個是序列&#xff0c;將此函數分別作用于該序列的每個元素&#xff0c;返回處理后的序列結果def c2(x): return x…

程序員雙手飛快敲鍵盤的時候是在敲代碼嗎?

當你看到一個程序員的兩只手在鍵盤上上下翻飛&#xff0c;行云流水的時候&#xff0c;多半不是在敲擊代碼大概率是在跟產品經理撕逼討論需求另一種可能就是在跟測試打口水仗10%幾率是在論壇碼字摸魚或者和人家開噴了。1%幾率是在跟MM聊天可以手速飛快而不需要停下思考的代碼&am…