前端交易型系統(tǒng)設(shè)計(jì)原則
從畢業(yè)到現(xiàn)在已經(jīng)快7年開發(fā)經(jīng)驗(yàn)了,做過基礎(chǔ)用戶系統(tǒng)、積分商城、偷菜游戲、論壇、博客等等;也一個(gè)人全棧開發(fā)在線視頻網(wǎng)站(http://sishuok.com/),也開發(fā)過幾萬、幾十萬、幾千萬、幾個(gè)億不同量級(jí)的系統(tǒng),踩過不少坑,也學(xué)到許多經(jīng)驗(yàn)。
設(shè)計(jì)了一些系統(tǒng),也有了一些自己的觀點(diǎn),個(gè)人認(rèn)為設(shè)計(jì)系統(tǒng)要因場(chǎng)景因時(shí)間而異,一個(gè)系統(tǒng)不是一下子就設(shè)計(jì)的非常***,在有限的資源情況下一定是先解決當(dāng)下最核心的問題,并預(yù)測(cè)/發(fā)現(xiàn)未來可能出現(xiàn)的問題,一步步解決最痛點(diǎn)的問題。也就是說系統(tǒng)設(shè)計(jì)是不斷迭代的過程,在迭代中發(fā)現(xiàn)問題修復(fù)問題;即滿足需求的系統(tǒng)是不斷迭代優(yōu)化出來的,不是一下子就架構(gòu)的非常***,這是一個(gè)持續(xù)的過程,個(gè)人不相信***架構(gòu)銀彈。不過如果一開始就有好的基礎(chǔ)系統(tǒng)設(shè)計(jì),未來可以更容易達(dá)到一個(gè)比較滿意的目標(biāo)。
在設(shè)計(jì)系統(tǒng)時(shí)應(yīng)該多思考墨菲定律:
1、任何事都沒有表面看起來那么簡(jiǎn)單;
2、所有的事都會(huì)比你預(yù)計(jì)的時(shí)間長(zhǎng);
3、會(huì)出錯(cuò)的事總會(huì)出錯(cuò);
4、如果你擔(dān)心某種情況發(fā)生,那么它就更有可能發(fā)生。
但是也要思考80/20法則,在系統(tǒng)設(shè)計(jì)初期將有限的資源用到刀刃上,我們的目標(biāo)是系統(tǒng)滿足現(xiàn)有需求并能支持未來需求。
在持續(xù)開發(fā)系統(tǒng)過程中前輩們也總結(jié)了很多設(shè)計(jì)原則/經(jīng)驗(yàn);而我個(gè)人也有幸運(yùn)用了一些經(jīng)驗(yàn)/原則。設(shè)計(jì)原則是系統(tǒng)發(fā)展初期或進(jìn)化過程中根據(jù)自己系統(tǒng)特征匹配使用的,如果剛開始不是核心問題請(qǐng)不要復(fù)雜化系統(tǒng)設(shè)計(jì)。
拆分
在系統(tǒng)設(shè)計(jì)初期,是做一個(gè)大而全的系統(tǒng)還是進(jìn)行按功能拆分系統(tǒng)這個(gè)需要進(jìn)行權(quán)衡;比如做私塾在線時(shí)本身用戶量/交易量不會(huì)特別大,而且開發(fā)就我一個(gè)人,資源有限,那就沒必要對(duì)系統(tǒng)拆分(比如拆分商品、訂單等等),就是做一個(gè)大而全的系統(tǒng)。而比如設(shè)計(jì)一個(gè)京東秒殺系統(tǒng),預(yù)測(cè)到一旦上線量會(huì)非常大,而且投入的資源還是蠻充足的,這種情況下就要考慮進(jìn)行按功能拆分系統(tǒng)。
筆者遇到的拆分主要有如下幾種情況:
- 系統(tǒng)維度:按照系統(tǒng)功能/業(yè)務(wù)拆分,比如商品系統(tǒng)、購(gòu)物車、結(jié)算、訂單系統(tǒng)等等;
- 功能維度:對(duì)一個(gè)系統(tǒng)進(jìn)行功能再拆分,比如優(yōu)惠券系統(tǒng),可以拆分為后臺(tái)券創(chuàng)建系統(tǒng)、領(lǐng)券系統(tǒng)、用券系統(tǒng)等;
- 讀寫維度:根據(jù)讀寫比例特征進(jìn)行拆分,比如商品系統(tǒng),交易的各個(gè)系統(tǒng)都會(huì)讀取,讀大于寫,因此就可以進(jìn)行拆分:商品寫服務(wù)、商品讀服務(wù);讀服務(wù)可以考慮全量緩存提升性能;比如寫的量太大,需要考慮分庫(kù)分表;還有些聚合讀取的場(chǎng)景,如商品詳情頁,請(qǐng)考慮數(shù)據(jù)異構(gòu)拆分系統(tǒng),將分散在多處的數(shù)據(jù)聚合到一處存儲(chǔ),提升讀的性能和可靠性;
- AOP維度:根據(jù)訪問特征,按照AOP進(jìn)行拆分,比如商品詳情頁,可以分為CDN、頁面渲染系統(tǒng);CDN就是一個(gè)AOP系統(tǒng);
- 模塊維度:比如按照基礎(chǔ)或者代碼維護(hù)特征進(jìn)行拆分,如基礎(chǔ)模塊:分庫(kù)分表、數(shù)據(jù)庫(kù)連接池等等;還有如代碼維護(hù)一般按照三層架構(gòu)(Web、Service、DAO)進(jìn)行劃分。
服務(wù)化
首先判斷是不是只需要簡(jiǎn)單的單點(diǎn)遠(yuǎn)程服務(wù)調(diào)用即可,如果單機(jī)扛不住了需要集群,是不是可以在客戶端注冊(cè)多臺(tái)機(jī)器,使用Nginx進(jìn)行負(fù)載均衡即可解決;如果隨著調(diào)用方越來越多,就要考慮使用服務(wù)自動(dòng)注冊(cè)和發(fā)現(xiàn)(如Dubbo使用zookeeper);還要考慮服務(wù)的分組/隔離,比如有的系統(tǒng)訪問量太大導(dǎo)致把整個(gè)服務(wù)打掛,因此需要為不同的調(diào)用方提供不同的服務(wù)分組,隔離訪問;后期還會(huì)隨著調(diào)用量的增加還要考慮如服務(wù)的限流、黑白名單等等。還有一些細(xì)節(jié)需要注意,如超時(shí)時(shí)間、重試機(jī)制、服務(wù)路由(能動(dòng)態(tài)切換不同的分組)、故障補(bǔ)償?shù)鹊?,這些都會(huì)影響到服務(wù)的質(zhì)量。
總結(jié)為進(jìn)程內(nèi)服務(wù)--->單點(diǎn)遠(yuǎn)程服務(wù)--->集群手動(dòng)注冊(cè)服務(wù)--->自動(dòng)注冊(cè)和發(fā)現(xiàn)服務(wù)---->服務(wù)的分組/隔離/路由---->限流/黑白名單。
數(shù)據(jù)版本化,可回滾
在設(shè)計(jì)時(shí)考慮是否需要進(jìn)行數(shù)據(jù)的版本化,數(shù)據(jù)維護(hù)出問題是否需要回滾。比如商品的維護(hù)是不是需要版本化。我們目前有一些非常重要的系統(tǒng)需要對(duì)數(shù)據(jù)進(jìn)行版本化并且支持可回滾。整體設(shè)計(jì)類似于下圖設(shè)計(jì):
流程可定義
如果接觸過保險(xiǎn)業(yè)務(wù),會(huì)發(fā)現(xiàn)不同的保險(xiǎn)理賠服務(wù)是不一樣的,因此我們?cè)谙到y(tǒng)設(shè)計(jì)時(shí)就設(shè)計(jì)了一套理賠流程服務(wù)。而承保流程和理賠流程是分離,然后進(jìn)行關(guān)聯(lián),從而可以復(fù)用一些理賠流程并提供個(gè)性化的理賠流程。
狀態(tài)與狀態(tài)機(jī)
在設(shè)計(jì)交易訂單系統(tǒng)時(shí),會(huì)存在正向狀態(tài)(待付款、待發(fā)貨、已發(fā)貨、完成)和逆向狀態(tài)(取消、退款)等,正向狀態(tài)和逆向狀態(tài)應(yīng)該根據(jù)自己系統(tǒng)的特征來決定是不是需要分離存儲(chǔ)。
另外還有訂單狀態(tài)的變遷,比如待支付、已支付待發(fā)貨、待收貨、完成的遷移;要考慮是不是需要使用狀態(tài)機(jī)來驅(qū)動(dòng)狀態(tài)的變更和后續(xù)流程節(jié)點(diǎn)操作。
還要考慮并發(fā)狀態(tài)修改問題,同時(shí)對(duì)同一個(gè)訂單只存在一個(gè)修改;狀態(tài)變更的有序問題,狀態(tài)變更消息的先到后到問題,如支付成功消息和用戶取消消息的時(shí)間差。
消息隊(duì)列
消息隊(duì)列,用來解耦一些不需要同步調(diào)用的服務(wù)或者訂閱一些自己系統(tǒng)關(guān)心的變化;使用消息隊(duì)列可以實(shí)現(xiàn)服務(wù)解耦(一對(duì)多消費(fèi))、異步、緩沖(削峰)等。比如電商系統(tǒng)中的交易訂單數(shù)據(jù),該數(shù)據(jù)有非常多的系統(tǒng)關(guān)心并訂閱,比如訂單生產(chǎn)系統(tǒng)、定期送系統(tǒng)、訂單風(fēng)控系統(tǒng)等等;如果訂閱者太多,那么訂閱單個(gè)消息隊(duì)列就會(huì)成為瓶頸,此時(shí)需要考慮對(duì)消息隊(duì)列進(jìn)行多個(gè)鏡像復(fù)制。
大流量緩沖持久化
在電商搞大促時(shí),此時(shí)的系統(tǒng)流量會(huì)高于正常流量的幾倍甚至幾十倍,此時(shí)就要進(jìn)行一些特殊的設(shè)計(jì)來保證系統(tǒng)平穩(wěn)度過這段時(shí)期;而解決的手段很多,一般都是犧牲強(qiáng)一致性,而是保證最終一致性即可。
比如扣減庫(kù)存,可以考慮這樣設(shè)計(jì):
直接在Redis中扣減,然后記錄下扣減日志,通過Worker去同步到DB。
還有如交易訂單系統(tǒng),可以考慮這樣設(shè)計(jì):
首先結(jié)算服務(wù)調(diào)用訂單接單服務(wù)將訂單存儲(chǔ)到:訂單Redis和訂單隊(duì)列表,訂單隊(duì)列表可以按照需求水平擴(kuò)展N個(gè)表,通過隊(duì)列緩沖表提升接單的能力;然后通過同步Worker同步到訂單中心表;假設(shè)用戶支付了訂單,訂單狀態(tài)機(jī)會(huì)驅(qū)動(dòng)狀態(tài)變更,此時(shí)可能訂單隊(duì)列表的訂單還沒有同步到訂單中心表,此時(shí)狀態(tài)機(jī)就要根據(jù)實(shí)際情況進(jìn)行重試。
如果用戶查看單個(gè)訂單詳情可以直接從訂單Redis就能查到;但如果查詢訂單列表需要考慮訂單Redis和列表的合并。
同步Worker在設(shè)計(jì)時(shí)需要考慮并發(fā)處理和重復(fù)處理的問題,單機(jī)串行掃描處理(每臺(tái)Worker只掃描其中的一部分表)還是集群處理(Map-Reduce),另外需要考慮是否需要對(duì)訂單隊(duì)列表添加相關(guān)字段:處理人(哪個(gè)應(yīng)用正在處理)和處理狀態(tài)(正在處理、已處理、處理失敗)。
數(shù)據(jù)校對(duì)
在使用了消息異步機(jī)制的場(chǎng)景下,可能存在消息的丟失,需要考慮進(jìn)行數(shù)據(jù)校對(duì)和修正來保證數(shù)據(jù)一致性和完整性。可以通過Worker定期去掃描原始表進(jìn)行補(bǔ)償,掃描周期根據(jù)實(shí)際場(chǎng)景進(jìn)行定義。
數(shù)據(jù)異構(gòu)化
訂單分庫(kù)分表一般按照訂單ID進(jìn)行分,那么如果要查詢某個(gè)用戶的訂單列表就需要聚合N個(gè)表的數(shù)據(jù)然后返回,這樣會(huì)導(dǎo)致訂單表的讀性能很低;此時(shí)需要對(duì)訂單表進(jìn)行異構(gòu),異構(gòu)一套用戶訂單表,按照用戶ID進(jìn)行分庫(kù)分表;另外還需要考慮對(duì)歷史訂單數(shù)據(jù)進(jìn)行歸檔處理。
還一種異構(gòu)場(chǎng)景,如商品詳情頁,因?yàn)閿?shù)據(jù)來源太多,影響服務(wù)穩(wěn)定性的因素就太多了,因此***的辦法是把使用到的數(shù)據(jù)進(jìn)行異構(gòu)存儲(chǔ),形成數(shù)據(jù)閉環(huán);提升服務(wù)的性能和穩(wěn)定性。而有些數(shù)據(jù)異構(gòu)的意義不大,如庫(kù)存價(jià)格可以考慮異步加載,或者并發(fā)請(qǐng)求合并。
后臺(tái)系統(tǒng)操作可反饋
在我接觸過的很多系統(tǒng),很多場(chǎng)景都需要反饋,比如修改了某些內(nèi)容想預(yù)覽看看最終效果,即想得到一些反饋;還有一些是規(guī)則系統(tǒng),希望看到這些規(guī)則在系統(tǒng)數(shù)據(jù)下的反饋。因此在設(shè)計(jì)后臺(tái)系統(tǒng)請(qǐng)考慮效果的可預(yù)覽、可反饋。
后臺(tái)系統(tǒng)審批化
對(duì)于有些重要的后臺(tái)功能需要設(shè)計(jì)審批流,比如調(diào)整價(jià)格,并對(duì)操作進(jìn)行日志記錄從而保證操作可追溯、可審計(jì)。
防重設(shè)計(jì)
比如結(jié)算頁需要考慮重復(fù)提交,還有如下單扣減庫(kù)存時(shí)需要防止重復(fù)扣減庫(kù)存。解決方案可以考慮防重KEY、防重表。而有些場(chǎng)景如重復(fù)支付,如有的電商網(wǎng)站同時(shí)支持微信支付、京東支付,渠道不一樣是無法防止重復(fù)支付的,但是系統(tǒng)設(shè)計(jì)時(shí)需要將支付的每筆情況記錄下。比如下圖是我在京東使用京東支付和微信支付模擬的重復(fù)支付之后進(jìn)行退款的支付明細(xì):
冪等設(shè)計(jì)
在交易系統(tǒng)中經(jīng)常會(huì)用到消息,而現(xiàn)有消息中間件基本不保證不發(fā)生重復(fù)消息的消費(fèi);因此需要業(yè)務(wù)系統(tǒng)考慮在重復(fù)消息消費(fèi)時(shí)進(jìn)行冪等處理。還有如使用第三方支付時(shí),第三方支付會(huì)進(jìn)行異步回調(diào),因此也要考慮做好回調(diào)的冪等處理。
【本文是51CTO專欄作者張開濤的原創(chuàng)文章,作者微信公眾號(hào):開濤的博客( kaitao-1234567)】