如何避免下重復(fù)訂單

電子交易的一個(gè)很基本的問題囚戚,就是避免用戶下重復(fù)訂單。用戶明明想買一次轧简,結(jié)果一看下了兩個(gè)單驰坊。如果沒有及時(shí)發(fā)現(xiàn),就會帶來額外的物流成本和扯皮哮独。對商家的信譽(yù)也不好看拳芙。

從技術(shù)上看,這是一個(gè)分布式一致性問題皮璧;但實(shí)際上舟扎,技術(shù)無法100%解決這類問題,得結(jié)合多種手段綜合處理恶导。這里就來說道說道浆竭。

為啥會下重了呢?

  • 原因1:客戶端bug

比如下單的按鍵在點(diǎn)按之后惨寿,在沒有收到服務(wù)器請求之前邦泄,按鍵的狀態(tài)沒有設(shè)為已禁用狀態(tài),還可以被按裂垦。又或者顺囊,在觸摸屏下,用戶手指的點(diǎn)按可能被手機(jī)操作系統(tǒng)識別為多次點(diǎn)擊蕉拢。

嗯特碳,誰能保證客戶端不偶爾出個(gè)什么bug 呢。

  • 原因2: 超時(shí)

用戶的設(shè)備與服務(wù)器之間可能是不穩(wěn)定的網(wǎng)路晕换。這樣一個(gè)下單請求過去午乓,返回不一定回得來。超時(shí)最大的問題是: 從用戶的角度闸准,他無法確定下單的請求是還沒到服務(wù)器益愈,還是已經(jīng)到了服務(wù)器但是返回丟失了∫募遥——用戶無法區(qū)分到底這個(gè)單下了還是沒下蒸其。

這樣在等待一個(gè)超時(shí)后,UI可能會提示用戶下單超時(shí)库快,請重復(fù)再試摸袁。

下單超時(shí)
  • 原因3: 用戶的App閃退/人工強(qiáng)退,之后重新打開重新下單

也許可以使用一些技術(shù)手段避免用戶下重單义屏,但是心急的用戶可能會重啟流程/重啟App/重啟手機(jī)靠汁。在這種強(qiáng)制的手段下蜂大,任何技術(shù)手段都會失效——用戶壓根就不讓你的技術(shù)執(zhí)行,你怎么玩蝶怔?

在這些條件下县爬,如何避免用戶多下了一筆訂單呢?

用冪等防止重復(fù)訂單

在技術(shù)方面添谊,這是一個(gè)分布式一致性的問題,即客戶端和服務(wù)器端對某個(gè)訂單是否成功/失敗達(dá)成一致察迟。防止重單的關(guān)鍵是使用一個(gè)由客戶端生成的斩狱,可用于避免重復(fù)的key,俗稱dedup key(deduplicate key之意)扎瓶。這個(gè)key可以用任意可以保證全局唯一性的方式生成所踊,比如uuid「藕桑客戶端和服務(wù)器需要使用這個(gè)dedup key作為串聯(lián)條件秕岛,一起解決去重問題。

客戶端的流程

客戶端需要實(shí)現(xiàn)這樣一個(gè)下單界面误证。用戶點(diǎn)擊【確認(rèn)下單】時(shí)继薛,應(yīng)該產(chǎn)生一個(gè)獨(dú)一無二的dedup key,連定訂單數(shù)據(jù)發(fā)送給服務(wù)器端愈捅。在服務(wù)器返回之前遏考,該界面應(yīng)該一直等待,直到服務(wù)器響應(yīng)成功/失敗或者超時(shí)發(fā)生(比如15秒后蓝谨,收不到服務(wù)器響應(yīng))灌具。如果超時(shí)發(fā)生,應(yīng)該向用戶提示是否重試下單或者退出該界面譬巫。當(dāng)用戶點(diǎn)擊【重試】時(shí)咖楣,應(yīng)該用剛剛生成的dedup key來再次發(fā)送下單請求——如果用戶一直不退出這個(gè)流程,每次用戶點(diǎn)擊重試芦昔,都應(yīng)該用這個(gè)dedup key來重試下單诱贿,直到服務(wù)器正常返回,或者用戶放棄返回烟零。

下單的客戶端流程

后端數(shù)據(jù)表設(shè)計(jì)

后端在訂單數(shù)據(jù)表中瘪松,需要增加dedup_key這列,并設(shè)置唯一約束锨阿。

create table order(
  # ...
  dedup_key varchar(60) not null comment 'key to pretend order duplication',
  # ...
  unique uniq_dedup_key(dedup_key)
);

下單的實(shí)現(xiàn)

在實(shí)現(xiàn)下單邏輯時(shí)宵睦,基于該dedup_key實(shí)現(xiàn)一個(gè)"create-or-get"語義的下單接口——簡單說就是

如果帶有指定dedup_key的訂單已經(jīng)存在,則直接返回墅诡;否則壳嚎,用該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 ...
  }
}

這時(shí),這段下單代碼總是能返回一個(gè)訂單(除非發(fā)生一些DB掛了之類的錯(cuò)誤)烟馅,要么是新創(chuàng)建的说庭,要么就是一個(gè)已經(jīng)存在的單。注意郑趁,最好在訂單里增加一個(gè)屬性(比如例子中用“duplicated”)來表示這個(gè)訂單是這次新生成的刊驴,還是因?yàn)閮绲榷苯臃祷氐?/strong>。這樣前端可以有針對性的對這兩種情況提示不同的文案寡润。

技術(shù)搞定冪等就足夠了嗎捆憎?

上面的流程沒有考慮一種情況,就是用戶中途強(qiáng)制退出客戶端梭纹,或者直接點(diǎn)擊【返回】回到產(chǎn)品頁躲惰,重新走下單流程。這個(gè)時(shí)候客戶端就無法判斷用戶到底是想重新下單变抽,還是想第二次下單础拨。此時(shí),可以從產(chǎn)品設(shè)計(jì)上考慮一下绍载。

比如诡宗,在客戶端緩存一個(gè)表,記錄所有沒有確認(rèn)結(jié)果的訂單击儡。

產(chǎn)品代碼 產(chǎn)品數(shù)量 金額 dedup key
未確認(rèn)訂單1 AAA 1 1000 xxx-yyy-zzz
未確認(rèn)訂單2 BBB 2 500.00 Aaa-bbb-ccc
...

通過這個(gè)表僚焦,我們可以一下用戶的意圖。比如曙痘,如果用戶重新提交了一筆訂單芳悲,其產(chǎn)品代碼、金額與表中記錄的某條完全一致边坤,就可以提示一下用戶:

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

如果用戶想重試名扛,可以繼續(xù)用表中對應(yīng)記錄的dedup key重新發(fā)起下單。

這樣不是絕對準(zhǔn)確的茧痒,僅僅是盡量的減少用戶誤操作的可能性肮韧。當(dāng)然,在產(chǎn)品設(shè)計(jì)上可以能出于用戶交互簡化旺订,不一定真的會這樣做弄企。這就需要其他機(jī)制來配合,比如“通知”区拳。

通知

一旦服務(wù)器下單成功拘领,可以通過某種通知機(jī)制(如APNS、Websocket)主動(dòng)將訂單推送至客戶端樱调,強(qiáng)行讓客戶端重新拉取最新的訂單信息约素,并配合“未確認(rèn)訂單”表届良,以通知Badge/彈框等方式提示用戶剛剛一筆狀態(tài)未知的訂單成功/失敗了。

另外一種手段就是圣猎,服務(wù)器端實(shí)時(shí)掃描用戶的下單數(shù)據(jù)士葫,一旦發(fā)現(xiàn)可能的重單,就立刻通知客服主動(dòng)聯(lián)系用戶送悔,及時(shí)處理問題慢显。

如果還攔不住……

經(jīng)過層層阻攔,可能還是會有用戶誤操作欠啤,直到收到兩份商品才發(fā)現(xiàn)下重了鳍怨。此時(shí)就得依靠運(yùn)營/客服的支持了。提供用戶申訴的手段跪妥,讓用戶提出哪些訂單是重復(fù)的,并且由銷售系統(tǒng)店家声滥、商品提供者和買家三方共同根據(jù)用戶操作的記錄來協(xié)商如何處理眉撵。我們需要讓技術(shù)幫助讓這種人工處理的幾率盡量小。因?yàn)槊看翁幚矶紩馁M(fèi)較大的人工成本落塑,和一些運(yùn)營費(fèi)用(比如賠款纽疟、小禮品等等)。

這么麻煩憾赁,有必要嗎污朽?

這要分業(yè)務(wù)場景,對于很多電商來講可能不是必要的龙考。因?yàn)閺挠脩粝聠蔚接唵伪粚徍颂幚磉M(jìn)入到發(fā)貨階段需要一定的時(shí)間(可能是半小時(shí)~1小時(shí))蟆肆,并且一定是支付成功后才會開始進(jìn)行下一步流程。在這個(gè)時(shí)間段晦款,用戶大概率能從網(wǎng)絡(luò)錯(cuò)誤中恢復(fù)過來炎功,自行區(qū)分是否下重了。配合客服主動(dòng)提示缓溅,會極大的降低出問題的概率蛇损。

但是對于理財(cái)服務(wù)來說,這種去重就非常必要了坛怪。因?yàn)?/p>

  • “下單+支付”淤齐。用戶購買理財(cái)往往是“下單+支付”一起執(zhí)行,不可以單獨(dú)下單/單獨(dú)支付
  • 用戶的入金可能很大袜匿。例如數(shù)萬更啄,數(shù)十萬
  • 準(zhǔn)確性丟失。如果一旦下重了居灯,有可能影響用戶的投資資金配置的準(zhǔn)確性锈死。
  • 撤銷難贫堰。部分理財(cái)產(chǎn)品存在下單不可撤銷的問題;或者即便撤銷待牵,資金也無法立刻回款其屏。等到回款,可能這個(gè)購入機(jī)會就錯(cuò)過去了缨该。例如對于基金交易偎行,錯(cuò)過1個(gè)交易日,價(jià)格就會發(fā)生變動(dòng)贰拿。

基于這些特性蛤袒,在理財(cái)產(chǎn)品中,就要竭盡全力的去重膨更。

結(jié)論

以上所講是處理重復(fù)訂單問題的一般方法妙真。你可以注意到,無論多么好的技術(shù)荚守,也不可能100%的攔截所有的可能性珍德,必須依靠技術(shù)+產(chǎn)品設(shè)計(jì)+運(yùn)營支持的綜合手段才能解決這類問題。

另外矗漾,本文還沒涉及到關(guān)于訂單支付(支付也可能重復(fù)哦)帶來的進(jìn)一步的復(fù)雜性锈候,也沒有討論在高并發(fā)情況下的性能優(yōu)化,僅僅討論下單本身的問題敞贡。所以可以想象一下現(xiàn)實(shí)中的交易業(yè)務(wù)比這里的說的要復(fù)雜得多泵琳。

本文介紹的原理也不僅僅適用于防止下重復(fù)訂單,而是可以應(yīng)用到任何需要“創(chuàng)建一個(gè)不應(yīng)該重復(fù)資源”的場景誊役,比如“向用戶發(fā)一條通知”获列,“觸發(fā)一次不能重復(fù)的批處理任務(wù)“……

希望今天你有g(shù)et到:)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛔垢,一起剝皮案震驚了整個(gè)濱河市蛛倦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啦桌,老刑警劉巖溯壶,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甫男,居然都是意外死亡且改,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門板驳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來又跛,“玉大人,你說我怎么就攤上這事若治】叮” “怎么了感混?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長礼烈。 經(jīng)常有香客問我弧满,道長,這世上最難降的妖魔是什么此熬? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任庭呜,我火速辦了婚禮,結(jié)果婚禮上犀忱,老公的妹妹穿的比我還像新娘募谎。我一直安慰自己,他們只是感情好阴汇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布数冬。 她就那樣靜靜地躺著,像睡著了一般搀庶。 火紅的嫁衣襯著肌膚如雪拐纱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天地来,我揣著相機(jī)與錄音,去河邊找鬼熙掺。 笑死未斑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的币绩。 我是一名探鬼主播蜡秽,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缆镣!你這毒婦竟也來了芽突?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤董瞻,失蹤者是張志新(化名)和其女友劉穎寞蚌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钠糊,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挟秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抄伍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艘刚。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖截珍,靈堂內(nèi)的尸體忽然破棺而出攀甚,到底是詐尸還是另有隱情箩朴,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布秋度,位于F島的核電站炸庞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏静陈。R本人自食惡果不足惜燕雁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲸拥。 院中可真熱鬧拐格,春花似錦、人聲如沸刑赶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撞叨。三九已至金踪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牵敷,已是汗流浹背胡岔。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枷餐,地道東北人靶瘸。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像毛肋,于是被迫代替她去往敵國和親怨咪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容