電商平臺(tái),交易始終是眾多業(yè)務(wù)邏輯中的關(guān)鍵部分。對(duì)于平臺(tái)而言,交易流程本質(zhì)上就是處理數(shù)據(jù),資金和商品在買(mǎi)賣(mài)雙方以及平臺(tái)三方之間的流轉(zhuǎn),對(duì)應(yīng)到系統(tǒng)服務(wù)模塊,就是訂單,支付和物流三大部分。其中,支付和物流目前有眾多第三方平臺(tái)提供支撐,前者比如微信支付寶銀聯(lián),后者比如快遞100和聚合數(shù)據(jù)等等。而交易流程的各個(gè)環(huán)節(jié)尤其是訂單從生成到支付完成之間這部分,則由于不同的項(xiàng)目的商業(yè)邏輯和特定數(shù)據(jù)的差別,需要每個(gè)項(xiàng)目單獨(dú)進(jìn)行設(shè)計(jì)。當(dāng)然,在大體的框架上,絕大多數(shù)EShop網(wǎng)站之間是差不多的。本文則重點(diǎn)介紹我們項(xiàng)目在實(shí)際操作過(guò)程中,交易系統(tǒng)是如何設(shè)計(jì)和演化的。
第一個(gè)階段:功能原型階段
時(shí)間:項(xiàng)目啟動(dòng)后1個(gè)月內(nèi)。
在這個(gè)階段,我并沒(méi)有太多參考現(xiàn)有分布式電商系統(tǒng)的結(jié)構(gòu),而是把主要精力放在和項(xiàng)目業(yè)務(wù)息息相關(guān)的數(shù)據(jù)模型設(shè)計(jì)上。這是因?yàn)楣疽龅姆较蚴且粋€(gè)完全垂直的領(lǐng)域,雖然有同類(lèi)型的競(jìng)爭(zhēng)對(duì)手,但是幾乎沒(méi)有可供參考的實(shí)際數(shù)據(jù)實(shí)例。所以我們需要在最短時(shí)間里,完成業(yè)務(wù)邏輯的模型化,并盡可能發(fā)現(xiàn)遺漏的(包括開(kāi)發(fā)人員未理解的和投資人本身沒(méi)有描述清楚的潛在需求的)模型進(jìn)行補(bǔ)足。因此這個(gè)階段我刻意避免過(guò)度設(shè)計(jì)(因?yàn)楹艽罂赡苄陨嫌胁糠謹(jǐn)?shù)據(jù)模型是要翻工設(shè)計(jì)的,事實(shí)證明這是一定的,因?yàn)榇蠹叶紱](méi)有做過(guò)這個(gè)領(lǐng)域,認(rèn)識(shí)和理解上的偏差和錯(cuò)誤在所難免),而是帶領(lǐng)后臺(tái)開(kāi)發(fā)組全力幾乎是以最簡(jiǎn)單最暴力的手段實(shí)現(xiàn)已經(jīng)明確的功能點(diǎn),盡快能讓移動(dòng)端原型能運(yùn)轉(zhuǎn)起來(lái)驗(yàn)證我們的產(chǎn)品思路。
所以這個(gè)階段的交易流程設(shè)計(jì)非常粗暴簡(jiǎn)單:

在這個(gè)階段,后臺(tái)數(shù)據(jù)庫(kù)設(shè)計(jì)上有兩個(gè)重點(diǎn):
1)購(gòu)物車(chē)和普通訂單之間的關(guān)系:
在我們的設(shè)計(jì)中,添加到購(gòu)物車(chē)的動(dòng)作本身已經(jīng)觸發(fā)創(chuàng)建一個(gè)訂單,只是這是一個(gè)特殊的訂單,它的“Status=Draft”,只有在用戶(hù)從購(gòu)物車(chē)中確認(rèn)購(gòu)買(mǎi)時(shí)才將此訂單轉(zhuǎn)換成一個(gè)普通訂單,也就是修改“status=waiting for payment”。同一個(gè)賣(mài)家的不同商品,在購(gòu)物車(chē)中被添加進(jìn)同一個(gè)訂單中。
2)合并付款
合并付款是和購(gòu)物車(chē)相輔相成的,沒(méi)有購(gòu)物車(chē)就不存在合并付款的概念。所謂合并付款,本質(zhì)上是需要對(duì)多個(gè)子訂單的支付單進(jìn)行合并,這些子訂單對(duì)象本身仍然是獨(dú)立的。注意,這里的子訂單一般以商家為區(qū)分。
另外需要提及的是,這里我埋了一個(gè)大坑,以至于后期第三個(gè)階段我們不得不做出相應(yīng)的改動(dòng):我從市場(chǎng)和投資人的需求中分析的結(jié)論是“我們根本不可能存在可以隨意修改價(jià)格的訂單”。所以為了速度和簡(jiǎn)單,這個(gè)階段里所有Order都是外鍵直接關(guān)聯(lián)到商品信息的。事實(shí)證明,這樣的想法真是太天真。即便結(jié)論是正確的,作為一個(gè)上規(guī)模的電商平臺(tái),所有訂單數(shù)據(jù)必須和商品數(shù)據(jù)之間做冗余。再重復(fù)一遍,這里的數(shù)據(jù)表一定要做冗余設(shè)計(jì)!后面我會(huì)仔細(xì)分析原因。
第二個(gè)階段:完善階段
時(shí)間點(diǎn):項(xiàng)目啟動(dòng)后2-5個(gè)月
通過(guò)第一個(gè)階段的原型驗(yàn)證后,商品的交易流程基本走通,商品和交易的關(guān)鍵屬性字段都已經(jīng)基本到位。于是我開(kāi)始關(guān)注數(shù)據(jù)庫(kù)的結(jié)構(gòu)合理性。首先我們的數(shù)據(jù)庫(kù)有一個(gè)非常不合理的問(wèn)題:?jiǎn)螏?kù)!
很顯然我并不能責(zé)怪后臺(tái)開(kāi)發(fā)人員,因?yàn)樵谠驼撟C階段,速度是我們追求的第一目標(biāo),把所有的數(shù)據(jù)庫(kù)表都扔在一個(gè)庫(kù)里是非常簡(jiǎn)單并且相對(duì)還是很合理的選擇。但是對(duì)于產(chǎn)品而言,真正發(fā)布時(shí)所有的雞蛋都在一個(gè)籃子里顯然不是明智的選擇,單庫(kù)會(huì)面臨諸多問(wèn)題:
1)性能很容易到達(dá)瓶頸。這個(gè)自然不用多說(shuō)。
2)升級(jí)維護(hù)困難。如果所有業(yè)務(wù)邏輯訪(fǎng)問(wèn)的是同一個(gè)庫(kù),那么在升級(jí)數(shù)據(jù)庫(kù)或者維護(hù)數(shù)據(jù)時(shí),將會(huì)造成全平臺(tái)業(yè)務(wù)的暫停,這個(gè)在項(xiàng)目初始上線(xiàn)時(shí)還不是大問(wèn)題,但是只要業(yè)務(wù)規(guī)模稍稍大些,產(chǎn)品需求變化快些,頻繁的技術(shù)更新將會(huì)給平臺(tái)帶來(lái)嚴(yán)重的商務(wù)阻斷。
3)數(shù)據(jù)保障性很差。一旦數(shù)據(jù)庫(kù)損壞,所有業(yè)務(wù)都將無(wú)法使用。
所以,在后臺(tái)業(yè)務(wù)膨脹到一定程度之前,必須提前做一些調(diào)整。這里又要再次點(diǎn)出我的思路宗旨——?dú)㈦u勿用牛刀,在合適的時(shí)間段點(diǎn)到為止。數(shù)據(jù)庫(kù)的優(yōu)化是一個(gè)非常復(fù)雜和精細(xì)的工作,涵蓋的技術(shù)手段是比較廣的。但是我們可以做一些簡(jiǎn)單的處理卻仍然能夠很大幅度優(yōu)化架構(gòu):
1)數(shù)據(jù)庫(kù)切分:
數(shù)據(jù)庫(kù)的切分有水平切分和垂直切分(包括庫(kù)和表)。我比較傾向于在庫(kù)級(jí)別做垂直切分,在表級(jí)別做水平切分。所謂垂直切分,一般以業(yè)務(wù)為區(qū)別,不同的業(yè)務(wù)使用不同的庫(kù)表;水平切分一般是對(duì)大表而言的,當(dāng)一張表因?yàn)橛涗涍^(guò)大而導(dǎo)致搜索性能下降時(shí),可以對(duì)表進(jìn)行水平切分,讓一張單表變成多張分表,索引時(shí),按照一定的索引算法找到對(duì)應(yīng)的分表再進(jìn)行查找。
我根據(jù)項(xiàng)目的實(shí)際情況,絕大多數(shù)數(shù)據(jù)表紀(jì)錄數(shù)都在1K~10K級(jí)別,因此我覺(jué)得水平切分暫時(shí)是沒(méi)有意義的。但是在這個(gè)時(shí)候按照業(yè)務(wù)內(nèi)容進(jìn)行垂直切分?jǐn)?shù)據(jù)庫(kù)就很有必要了。因?yàn)檫@是一個(gè)B2C的平臺(tái),平臺(tái)數(shù)據(jù)在本質(zhì)上就有兩大類(lèi):內(nèi)容類(lèi)和交易類(lèi)。其中內(nèi)容類(lèi)的數(shù)據(jù)更新頻繁,而且讀取遠(yuǎn)大于寫(xiě)入;而交易類(lèi)數(shù)據(jù)在業(yè)務(wù)處理上幾乎完全和內(nèi)容數(shù)據(jù)累獨(dú)立。所以完全可以將這兩部分的數(shù)據(jù)放在不同的庫(kù)里。這樣的話(huà),兩部分業(yè)務(wù)邏輯的更新都不會(huì)影響到對(duì)方,同時(shí)也容易針對(duì)兩部分?jǐn)?shù)據(jù)分別進(jìn)行優(yōu)化處理,比如后文會(huì)提到的,內(nèi)容部分?jǐn)?shù)據(jù)可以做讀寫(xiě)分離,而交易類(lèi)數(shù)據(jù)可以做主從備份。
目前項(xiàng)目的主數(shù)據(jù)庫(kù)分為3大部分,defaultpro,orderpro以及aispro,分別涵蓋了所有內(nèi)容數(shù)據(jù)表,交易訂單類(lèi)數(shù)據(jù)表和系統(tǒng)管理數(shù)據(jù)表。
2)主從備份:
相比于內(nèi)容數(shù)據(jù),交易類(lèi)數(shù)據(jù)是非常敏感重要的,出一點(diǎn)差錯(cuò)都不行。因此,對(duì)于交易數(shù)據(jù)的保護(hù)來(lái)說(shuō),備份是必須的?,F(xiàn)在既然已經(jīng)把交易類(lèi)數(shù)據(jù)單獨(dú)出一個(gè)獨(dú)立的庫(kù),而且初期項(xiàng)目交易數(shù)據(jù)量可以預(yù)期不會(huì)很大,那么備份就比較簡(jiǎn)單了,直接對(duì)庫(kù)做1對(duì)1備份。利用阿里云自身RDS的自動(dòng)主從備份功能,主從數(shù)據(jù)庫(kù)warm up之后幾乎可以做到毫秒級(jí)的同步備份。不過(guò)注意,截至到本文,阿里云的主從同步備份要求MySql版本不低于5.6
在這個(gè)項(xiàng)目里,我把交易類(lèi)數(shù)據(jù)做主從備份還有一個(gè)目的。上一篇文章有提到,一個(gè)完整的B2C系統(tǒng)還有一套獨(dú)立的財(cái)務(wù)和客服系統(tǒng)。當(dāng)我們把交易類(lèi)數(shù)據(jù)單獨(dú)備份到從庫(kù)后,財(cái)務(wù)系統(tǒng)和客服系統(tǒng)則在主從庫(kù)上進(jìn)行讀寫(xiě)分離,所有只讀操作都在從庫(kù)中進(jìn)行,當(dāng)財(cái)務(wù)需要對(duì)交易數(shù)據(jù)進(jìn)行更新時(shí)(比如更新分賬,退款審核,匯款審核操等等操作),才將直接操作主數(shù)據(jù)庫(kù)。在這里需要提一下,我們有對(duì)財(cái)務(wù)和客服類(lèi)操作制定了不同的策略:實(shí)時(shí)更新和延遲更新。實(shí)時(shí)更新時(shí)將通過(guò)RPC直接更新主數(shù)據(jù)庫(kù),而延遲更新時(shí)則將操作緩存在Redis中,定時(shí)定點(diǎn)統(tǒng)一將緩存的操作更新到主數(shù)據(jù)庫(kù)(比如我們規(guī)定退款訂單更新?tīng)顟B(tài)可以選擇在退款審核成功后第二日凌晨3:00進(jìn)行)。
3)讀寫(xiě)分離:
上面第二點(diǎn)其實(shí)已經(jīng)提到了讀寫(xiě)分離的概念。但是對(duì)于B2C系統(tǒng)而言,更加強(qiáng)大或者說(shuō)更加重要的讀寫(xiě)分離是針對(duì)內(nèi)容數(shù)據(jù)的。因?yàn)楫吘棺x寫(xiě)分離的優(yōu)勢(shì)在讀寫(xiě)比越大效果越好。而平臺(tái)內(nèi)容數(shù)據(jù)在讀寫(xiě)次數(shù)比上,隨著C端用戶(hù)的增加將會(huì)越來(lái)越大。所以很有必要對(duì)內(nèi)容數(shù)據(jù)進(jìn)行讀寫(xiě)分離。這點(diǎn)上,阿里云RDS的基礎(chǔ)設(shè)施做得還是比較到位的,你可以很方便的指定庫(kù)進(jìn)行主從備份后進(jìn)行讀寫(xiě)分離。到目前為止,我們的系統(tǒng)暫時(shí)還沒(méi)有對(duì)內(nèi)容數(shù)據(jù)庫(kù)進(jìn)行讀寫(xiě)分離,但是當(dāng)業(yè)務(wù)規(guī)模增長(zhǎng)到一定程度的時(shí)候,這顯然是第一步需要做的事情。
第三個(gè)階段:功能強(qiáng)化階段
時(shí)間點(diǎn):項(xiàng)目啟動(dòng)6個(gè)月
這時(shí),我們的項(xiàng)目1.0版本馬上就要發(fā)布了。但是在這個(gè)時(shí)候,市場(chǎng)部門(mén)出現(xiàn)了一些比較特殊的情況,1)要求我們能夠主動(dòng)的修改商品價(jià)格,以便他們進(jìn)行業(yè)務(wù)上的推廣; 2)我們的支付方式要求除了在線(xiàn)支付和現(xiàn)金匯款之外,還要有現(xiàn)金充值后余額支付,禮券,積分等其他計(jì)價(jià)手段。
要求1)這意味著,一個(gè)商品的價(jià)格必須要和訂單價(jià)格分離,這其實(shí)已經(jīng)和淘寶沒(méi)有太大區(qū)別了。這算是我在整個(gè)項(xiàng)目中唯數(shù)不多的判斷失誤的地方。實(shí)時(shí)上在最一開(kāi)始的時(shí)候我有考慮到這個(gè)問(wèn)題,但是我錯(cuò)誤的(其實(shí)是幼稚的)估計(jì)了投資人的思路和公司的業(yè)務(wù)模式,很主觀(guān)的認(rèn)為這種情況不是我們的業(yè)務(wù)模式,所以直接排除了。這也算是個(gè)小白的教訓(xùn):永遠(yuǎn)不要輕信投資人對(duì)業(yè)務(wù)方向的規(guī)劃,也永遠(yuǎn)不要武斷地對(duì)商業(yè)模式進(jìn)行提前估計(jì),寧愿信其有不可信其無(wú),在技術(shù)上必須干苦力活多做一些B方案儲(chǔ)備。當(dāng)然,在這個(gè)問(wèn)題上,關(guān)于對(duì)商業(yè)模式的思考也是一個(gè)創(chuàng)業(yè)者必修功課,雖然我是技術(shù)負(fù)責(zé)人,但是我在商業(yè)模式和市場(chǎng)上有自己的思考,也在不斷嘗試著做更多產(chǎn)品經(jīng)理的工作,這方面我會(huì)在產(chǎn)品分類(lèi)文章里做些記錄。
要求2)意味著交易流程更加復(fù)雜化,單個(gè)交易內(nèi)涉及的內(nèi)部業(yè)務(wù)更多。原來(lái)粗暴簡(jiǎn)單的從商品訂單直接轉(zhuǎn)化為支付鏈接的流程將不再適用。
這個(gè)時(shí)候,必須要重新設(shè)計(jì)整個(gè)交易流程的細(xì)節(jié)了。在進(jìn)一步討論新的交易流程之前,我們先來(lái)看看一個(gè)在傳統(tǒng)制造業(yè)里普遍存在的概念:訂單和工單。
訂單,一般而言是對(duì)外的概念,它是指企業(yè)的外部需求,這種需求是雙向的,可以是下游客戶(hù)對(duì)企業(yè)的商品需求也可以是企業(yè)對(duì)上游客戶(hù)的原材料需求;工單,一般是對(duì)內(nèi)的概念,它是指企業(yè)完成下游客戶(hù)需求的產(chǎn)品時(shí),內(nèi)部的運(yùn)作需要。
舉個(gè)栗子:
比如一個(gè)汽車(chē)制造廠(chǎng),4S店是下游客戶(hù),看作是C端(Customer),他們向汽車(chē)廠(chǎng)提出了汽車(chē)購(gòu)買(mǎi)需求,這就是產(chǎn)品訂單;當(dāng)汽車(chē)廠(chǎng)收到一個(gè)汽車(chē)訂單后,它會(huì)分解成一系列的生產(chǎn)步驟。比如,它可能將一輛汽車(chē)的生產(chǎn)需求分解為車(chē)身,動(dòng)力總成,輪胎外設(shè),噴漆4個(gè)部分,分別交給4個(gè)不同的車(chē)間進(jìn)行處理。這些分項(xiàng)需求就是工單。車(chē)身車(chē)間接到工單,可能再次分解成新的分項(xiàng),比如變成鍛壓,精磨,焊接等等多個(gè)不同的工單交給不同的生產(chǎn)線(xiàn)部門(mén),同時(shí),車(chē)間(或者制造廠(chǎng))還需要向上游的合作伙伴鋼鐵公司(B端,Business partner)要求購(gòu)買(mǎi)鋼板原材料,這時(shí)又產(chǎn)生了新的鋼材訂單。當(dāng)一輛汽車(chē)訂單完成時(shí),實(shí)時(shí)上制造廠(chǎng)可以查詢(xún)到該批次汽車(chē)的所有原材料進(jìn)貨商的資料和進(jìn)貨單。
也就是說(shuō),訂單可以分解成多個(gè)工單(1:N),而工單本身又可以產(chǎn)生新的訂單(M:N),從而下游訂單和上游訂單之間也存在關(guān)聯(lián)(M:N)。
這是我們現(xiàn)在修改后的交易流程設(shè)計(jì)(對(duì)應(yīng)于原始版本中紅框區(qū)域):

核心分為4大表,交易訂單 -> 工單流水 -> 支付訂單 -> 支付流水
圍繞著4個(gè)核心表,又有一些輔助表:訂單流水,券幣流水,余額流水等。
這些表背后的處理邏輯整體劃分為兩大部分:業(yè)務(wù)邏輯和支付邏輯。這兩部分是相對(duì)獨(dú)立的模塊,業(yè)務(wù)邏輯不關(guān)心最終支付途徑,支付邏輯不關(guān)心具體業(yè)務(wù)。兩者之間通過(guò)交易訂單ID和支付訂單ID進(jìn)行弱關(guān)聯(lián)。
稍微具體一些表設(shè)計(jì)見(jiàn)下圖:

這樣,通過(guò)工單和支付訂單這個(gè)環(huán)節(jié)的設(shè)計(jì),我們著重解決了需求(2),而在交易訂單中增加必要的冗余信息,并將訂單ID和支付訂單ID進(jìn)行弱關(guān)聯(lián),我們解決了需求(1)。
2016年2月16日,完稿于卡帕萊。