從儲(chǔ)值卡充值業(yè)務(wù)看分布式事務(wù)的設(shè)計(jì)

公司有一項(xiàng)儲(chǔ)值卡充值業(yè)務(wù):客戶(hù)在微信公眾號(hào)開(kāi)通儲(chǔ)值卡服務(wù)悦屏,通過(guò)微信支付往卡里面充值逞怨,充值成功后客戶(hù)可收到消息通知,并進(jìn)行消費(fèi)苫纤。

看起來(lái)是一項(xiàng)很簡(jiǎn)單的業(yè)務(wù),最初我們儲(chǔ)值卡團(tuán)隊(duì)的實(shí)現(xiàn)也確實(shí)很簡(jiǎn)單纲缓。我們看看最初的實(shí)現(xiàn):


儲(chǔ)值卡充值最初版本實(shí)現(xiàn)

相信聰明的你一眼就能看出問(wèn)題:

  1. 壓根沒(méi)有考慮分布式事務(wù)一致性卷拘,比如第 12 步根本沒(méi)有考慮卡系統(tǒng)充值失敗的情況該如何處理,而是默認(rèn)其一定能成功祝高;
  2. 大部分的處理都是放在前端業(yè)務(wù)系統(tǒng)(除了這里的公眾號(hào)系統(tǒng)恭金,還有 POS 機(jī)系統(tǒng),而 POS 機(jī)是通過(guò)調(diào)公眾號(hào)系統(tǒng)接口來(lái)實(shí)現(xiàn)的)褂策;
  3. 第 4 步直接下單,第 5 步直接調(diào)微信支付颓屑,壓根沒(méi)有跟卡系統(tǒng)有任何通信:這里默認(rèn)用戶(hù)的充值行為一定是合法的斤寂;
  4. 在微信的支付回調(diào)中(第 10 步往后),是先處理一系列業(yè)務(wù)邏輯揪惦,最后才調(diào)充值接口遍搞,這里也是默認(rèn)卡充值一定能成功;

看到這里你可能會(huì)大呼開(kāi)發(fā)人員是不是沒(méi)長(zhǎng)腦子器腋?

實(shí)際情況是溪猿,這個(gè)版本的開(kāi)發(fā)是幾年前的事情了,那時(shí)候公司還是創(chuàng)業(yè)早期纫塌,第一目標(biāo)是盡快上線(xiàn)能用诊县,而且客戶(hù)量沒(méi)有那么大,雖然中間也出現(xiàn)過(guò)一些數(shù)據(jù)不一致的情況措左,也都通過(guò)人工處理了事了依痊。

隨著公司業(yè)務(wù)的發(fā)展,用戶(hù)量越來(lái)越大怎披,而且還要和第三方合作(儲(chǔ)值卡作為一種支付方式提供給第三方使用)胸嘁,問(wèn)題出現(xiàn)得也越來(lái)越頻繁,不得不將這塊提上重構(gòu)議程凉逛。

那么性宏,針對(duì)上面提的幾點(diǎn)問(wèn)題,我們大體能想到如下重構(gòu)項(xiàng):

  1. 將充值業(yè)務(wù)邏輯從前端系統(tǒng)剝離状飞,做成單獨(dú)的服務(wù)毫胜;
  2. 在下單前书斜,先調(diào)一下卡系統(tǒng)接口,檢查用戶(hù)的充值行為是否合法指蚁,避免后面不必要的麻煩菩佑;
  3. 在支付回調(diào)中,處理充值失敗的場(chǎng)景凝化;

初步設(shè)計(jì)如下:


儲(chǔ)值卡充值:第一版重構(gòu)設(shè)計(jì)

這里我們重點(diǎn)討論下對(duì)第 14 步(卡充值接口返回結(jié)果)的處理:

  1. 如果返回充值成功稍坯,那萬(wàn)事大吉,該干嘛干嘛搓劫;
  2. 如果失敗呢瞧哟?可能的處理方式如下:
    1. 繼續(xù)重試,最多重試 3 次枪向,如果成功了勤揩,萬(wàn)事大吉;
    2. 如果上面重試還是失敗秘蛔,那么調(diào)微信退款陨亡,并將訂單狀態(tài)改成充值失敗深员;

騷年你等等负蠕!
你說(shuō)什么?重試失敗了就去退款倦畅?

實(shí)踐中遮糖,遠(yuǎn)程調(diào)用失敗的一個(gè)很大原因是網(wǎng)絡(luò)超時(shí)(而超時(shí)的很大原因又是對(duì)方負(fù)載過(guò)高),而面對(duì)超時(shí)叠赐,我們是不知道對(duì)方到底有沒(méi)有處理成功的欲账,萬(wàn)一這邊把錢(qián)退掉了,那邊又充值成功咋辦芭概?(我們是 SaaS 服務(wù)商赛不,這時(shí)真正的損失方是我們的商戶(hù),而商戶(hù)無(wú)疑會(huì)找我們索賠的)


立即退款會(huì)帶來(lái)問(wèn)題

一種方案是:
在多次重試失敗后發(fā)起微信退款之前谈山,先調(diào)卡系統(tǒng)查詢(xún)接口俄删,如果查詢(xún)結(jié)果是充值成功,則不退款奏路,繼續(xù)后續(xù)流程畴椰,否則發(fā)起退款;

該方案在實(shí)際中也基本行不通鸽粉,因?yàn)槿绻嵌螘r(shí)間網(wǎng)絡(luò)有問(wèn)題或者對(duì)方服務(wù)器負(fù)載高斜脂,查詢(xún)也有很大概率失敗,或者就算查成功了并返回充值記錄不存在触机,也有可能之前調(diào)的充值接口還在跑(比如處于鎖等待狀態(tài))帚戳。

面對(duì)該問(wèn)題玷或,我們決定用定時(shí)任務(wù)來(lái)解決。在微信支付回調(diào)中片任,如果多次調(diào)卡充值接口失敗偏友,我們不發(fā)起退款,也不進(jìn)行后續(xù)流程对供,而是在數(shù)據(jù)庫(kù)中寫(xiě)入一條異常記錄位他,然后結(jié)束本次處理。

在定時(shí)任務(wù)中(比如 10 分鐘一次)产场,我們?nèi)〕瞿切┊惓S涗浂焖瑁{(diào)卡系統(tǒng)相關(guān)接口核對(duì)最終狀態(tài),如果充值成功了京景,則補(bǔ)充執(zhí)行充值成功的后續(xù)流程窿冯,否則發(fā)起微信退款,并執(zhí)行其他充值失敗流程(如改訂單狀態(tài)确徙,給用戶(hù)發(fā)通知醒串、回調(diào)業(yè)務(wù)系統(tǒng)等)。

為了防止錢(qián)退了后卡又充值成功鄙皇,定時(shí)任務(wù)中只處理 1 小時(shí)前的數(shù)據(jù)厦凤。

另一個(gè)隱藏的問(wèn)題是,在前面的充值流程中育苟,直到微信支付回調(diào),卡系統(tǒng)都沒(méi)有關(guān)于這次充值行為的任何記錄椎木。這可能會(huì)導(dǎo)致后續(xù)一系列問(wèn)題违柏,其中一個(gè)問(wèn)題是,在最初下單(步驟 5)到最終充值(步驟 13)這段時(shí)間內(nèi)香椎,一旦任何變量(充值規(guī)則)發(fā)生改變漱竖,這次充值就有可能會(huì)失敗(或者導(dǎo)致數(shù)據(jù)差錯(cuò))畜伐。這個(gè)時(shí)間差短則幾十毫秒馍惹,長(zhǎng)則幾分鐘十幾分鐘都有可能。另一個(gè)次要問(wèn)題是玛界,一旦發(fā)生充值異常,卡系統(tǒng)自身是不知情的(因?yàn)闆](méi)有任何記錄),對(duì)卡系統(tǒng)的任何查詢(xún)也都不會(huì)反映這次充值行為堰酿。

為了解決該問(wèn)題徒像,我們引入預(yù)充值的概念。在下單后調(diào)微信支付前笨枯,先同步調(diào)卡系統(tǒng)的預(yù)充值接口薪丁,該接口計(jì)算充值合法性并生成一條預(yù)充值記錄遇西,該記錄包含充值賬號(hào)、充值金額严嗜、支付金額粱檀、充值單號(hào)等關(guān)鍵信息,狀態(tài)為“充值中”漫玄。

在微信支付回調(diào)中茄蚯,將預(yù)充值狀態(tài)改成“充值成功”,并處理一些其他邏輯称近。
綜合第队,最終方案如圖:


最終版本

總結(jié):

  1. 任何涉及到分布式事務(wù)的地方都是復(fù)雜的,必須小心設(shè)計(jì)刨秆;
  2. 遠(yuǎn)程過(guò)程處理不具有時(shí)序性凳谦,設(shè)計(jì)時(shí)必須考慮進(jìn)去(如退款后最終又充值成功的情況);
  3. 現(xiàn)實(shí)中的設(shè)計(jì)很多時(shí)候做不到完美衡未,我們要做的是保證出現(xiàn)異常的概率最小化并設(shè)置最終檢查哨兵(上面的定時(shí)任務(wù))尸执;
  4. 就算增設(shè)了哨兵,也不排除需要人工干預(yù)的可能性缓醋,因而在設(shè)計(jì)上盡量保證需要人工干預(yù)時(shí)有跡可循如失、方便處理;
  5. 遠(yuǎn)程調(diào)用需要有重試機(jī)制(上面只說(shuō)了對(duì)充值接口的重試送粱,其實(shí)其他接口也一樣需要有重試機(jī)制)褪贵;
  6. 記住一句話(huà):網(wǎng)絡(luò)總是不可靠的;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抗俄,一起剝皮案震驚了整個(gè)濱河市脆丁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌动雹,老刑警劉巖槽卫,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胰蝠,居然都是意外死亡歼培,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)茸塞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)躲庄,“玉大人,你說(shuō)我怎么就攤上這事钾虐《刘危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵禾唁,是天一觀(guān)的道長(zhǎng)效览。 經(jīng)常有香客問(wèn)我无切,道長(zhǎng),這世上最難降的妖魔是什么丐枉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任哆键,我火速辦了婚禮,結(jié)果婚禮上瘦锹,老公的妹妹穿的比我還像新娘籍嘹。我一直安慰自己,他們只是感情好弯院,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布辱士。 她就那樣靜靜地躺著,像睡著了一般听绳。 火紅的嫁衣襯著肌膚如雪颂碘。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天椅挣,我揣著相機(jī)與錄音头岔,去河邊找鬼。 笑死鼠证,一個(gè)胖子當(dāng)著我的面吹牛峡竣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播量九,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼适掰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了荠列?” 一聲冷哼從身側(cè)響起攻谁,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弯予,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體个曙,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锈嫩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垦搬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呼寸。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖猴贰,靈堂內(nèi)的尸體忽然破棺而出对雪,到底是詐尸還是另有隱情,我是刑警寧澤米绕,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布瑟捣,位于F島的核電站馋艺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迈套。R本人自食惡果不足惜捐祠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桑李。 院中可真熱鬧踱蛀,春花似錦、人聲如沸贵白。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)禁荒。三九已至猬膨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圈浇,已是汗流浹背寥掐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磷蜀,地道東北人召耘。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像褐隆,于是被迫代替她去往敵國(guó)和親污它。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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