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