如何避免下重復訂單(轉)


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

  從技術上看,這是一個分布式一致性問題;但實際上,技術無法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 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
...        

  通過這個表,我們可以一下用戶的意圖。比如,如果用戶重新提交了一筆訂單,其產品代碼、金額與表中記錄的某條完全一致,就可以提示一下用戶:

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

  如果用戶想重試,可以繼續用表中對應記錄的dedup key重新發起下單。

  這樣不是絕對准確的,僅僅是盡量的減少用戶誤操作的可能性。當然,在產品設計上可以能出於用戶交互簡化,不一定真的會這樣做。這就需要其他機制來配合,比如“通知”。

通知

  一旦服務器下單成功,可以通過某種通知機制(如APNS、Websocket)主動將訂單推送至客戶端,強行讓客戶端重新拉取最新的訂單信息,並配合“未確認訂單”表,以通知Badge/彈框等方式提示用戶剛剛一筆狀態未知的訂單成功/失敗了。

  另外一種手段就是,服務器端實時掃描用戶的下單數據,一旦發現可能的重單,就立刻通知客服主動聯系用戶,及時處理問題。

如果還攔不住……

  經過層層阻攔,可能還是會有用戶誤操作,直到收到兩份商品才發現下重了。此時就得依靠運營/客服的支持了。提供用戶申訴的手段,讓用戶提出哪些訂單是重復的,並且由銷售系統店家、商品提供者和買家三方共同根據用戶操作的記錄來協商如何處理。我們需要讓技術幫助讓這種人工處理的幾率盡量小。因為每次處理都會耗費較大的人工成本,和一些運營費用(比如賠款、小禮品等等)。

這么麻煩,有必要嗎?

 這要分業務場景,對於很多電商來講可能不是必要的。因為從用戶下單到訂單被審核處理進入到發貨階段需要一定的時間(可能是半小時~1小時),並且一定是支付成功后才會開始進行下一步流程。在這個時間段,用戶大概率能從網絡錯誤中恢復過來,自行區分是否下重了。配合客服主動提示,會極大的降低出問題的概率。

 但是對於理財服務來說,這種去重就非常必要了。因為

  • “下單+支付”。用戶購買理財往往是“下單+支付”一起執行,不可以單獨下單/單獨支付
  • 用戶的入金可能很大。例如數萬,數十萬
  • 准確性丟失。如果一旦下重了,有可能影響用戶的投資資金配置的准確性。
  • 撤銷難。部分理財產品存在下單不可撤銷的問題;或者即便撤銷,資金也無法立刻回款。等到回款,可能這個購入機會就錯過去了。例如對於基金交易,錯過1個交易日,價格就會發生變動。

 基於這些特性,在理財產品中,就要竭盡全力的去重。

結論

  以上所講是處理重復訂單問題的一般方法。你可以注意到,無論多么好的技術,也不可能100%的攔截所有的可能性,必須依靠技術+產品設計+運營支持的綜合手段才能解決這類問題。

  另外,本文還沒涉及到關於訂單支付(支付也可能重復哦)帶來的進一步的復雜性,也沒有討論在高並發情況下的性能優化,僅僅討論下單本身的問題。所以可以想象一下現實中的交易業務比這里的說的要復雜得多。

  本文介紹的原理也不僅僅適用於防止下重復訂單,而是可以應用到任何需要“創建一個不應該重復資源”的場景,比如“向用戶發一條通知”,“觸發一次不能重復的批處理任務“……



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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM