從畢業到現在已經快7年開發經驗了,做過基礎使用者系統、積分商城、偷菜遊戲、論壇、部落格等等;也一個人全棧開發線上視訊網站(http://sishuok.com/),也開發過幾萬、幾十萬、幾千萬、幾個億不同量級的系統,踩過不少坑,也學到許多經驗。
設計了一些系統,也有了一些自己的觀點,個人認為設計系統要因場景因時間而異,一個系統不是一下子就設計的非常完美,在有限的資源情況下一定是先解決當下最核心的問題,並預測/發現未來可能出現的問題,一步步解決最痛點的問題。也就是說系統設計是不斷迭代的過程,在迭代中發現問題修復問題;即滿足需求的系統是不斷迭代優化出來的,不是一下子就架構的非常完美,這是一個持續的過程,個人不相信完美架構銀彈。不過如果一開始就有好的基礎系統設計,未來可以更容易達到一個比較滿意的目標。
在設計系統時應該多思考墨菲定律:
- 1、任何事都沒有表面看起來那麼簡單;
- 2、所有的事都會比你預計的時間長;
- 3、會出錯的事總會出錯;
- 4、如果你擔心某種情況發生,那麼它就更有可能發生。
但是也要思考80/20法則,在系統設計初期將有限的資源用到刀刃上,我們的目標是系統滿足現有需求並能支援未來需求。
在持續開發系統過程中前輩們也總結了很多設計原則/經驗;而我個人也有幸運用了一些經驗/原則。設計原則是系統發展初期或進化過程中根據自己系統特徵匹配使用的,如果剛開始不是核心問題請不要複雜化系統設計。
拆分
在系統設計初期,是做一個大而全的系統還是進行按功能拆分系統這個需要進行權衡;比如做私塾線上時本身使用者量/交易量不會特別大,而且開發就我一個人,資源有限,那就沒必要對系統拆分(比如拆分商品、訂單等等),就是做一個大而全的系統。而比如設計一個京東秒殺系統,預測到一旦上線量會非常大,而且投入的資源還是蠻充足的,這種情況下就要考慮進行按功能拆分系統。
筆者遇到的拆分主要有如下幾種情況:
系統維度:按照系統功能/業務拆分,比如商品系統、購物車、結算、訂單系統等等;
功能維度:對一個系統進行功能再拆分,比如優惠券系統,可以拆分為後臺券建立系統、領券系統、用券系統等;
讀寫維度:根據讀寫比例特徵進行拆分,比如商品系統,交易的各個系統都會讀取,讀大於寫,因此就可以進行拆分:商品寫服務、商品讀服務;讀服務可以考慮全量快取提升效能;比如寫的量太大,需要考慮分庫分表;還有些聚合讀取的場景,如商品詳情頁,請考慮資料異構拆分系統,將分散在多處的資料聚合到一處儲存,提升讀的效能和可靠性;
AOP維度:根據訪問特徵,按照AOP進行拆分,比如商品詳情頁,可以分為CDN、頁面渲染系統;CDN就是一個AOP系統;
模組維度:比如按照基礎或者程式碼維護特徵進行拆分,如基礎模組:分庫分表、資料庫連線池等等;還有如程式碼維護一般按照三層架構(Web、Service、DAO)進行劃分。
服務化
首先判斷是不是隻需要簡單的單點遠端服務呼叫即可,如果單機扛不住了需要叢集,是不是可以在客戶端註冊多臺機器,使用Nginx進行負載均衡即可解決;如果隨著呼叫方越來越多,就要考慮使用服務自動註冊和發現(如Dubbo使用zookeeper);還要考慮服務的分組/隔離,比如有的系統訪問量太大導致把整個服務打掛,因此需要為不同的呼叫方提供不同的服務分組,隔離訪問;後期還會隨著呼叫量的增加還要考慮如服務的限流、黑白名單等等。還有一些細節需要注意,如超時時間、重試機制、服務路由(能動態切換不同的分組)、故障補償等等,這些都會影響到服務的質量。
總結為程式內服務—>單點遠端服務—>叢集手動註冊服務—>自動註冊和發現服務—->服務的分組/隔離/路由—->限流/黑白名單。
資料版本化,可回滾
在設計時考慮是否需要進行資料的版本化,資料維護出問題是否需要回滾。比如商品的維護是不是需要版本化。我們目前有一些非常重要的系統需要對資料進行版本化並且支援可回滾。整體設計類似於下圖設計:
流程可定義
如果接觸過保險業務,會發現不同的保險理賠服務是不一樣的,因此我們在系統設計時就設計了一套理賠流程服務。而承保流程和理賠流程是分離,然後進行關聯,從而可以複用一些理賠流程並提供個性化的理賠流程。
狀態與狀態機
在設計交易訂單系統時,會存在正向狀態(待付款、待發貨、已發貨、完成)和逆向狀態(取消、退款)等,正向狀態和逆向狀態應該根據自己系統的特徵來決定是不是需要分離儲存。
另外還有訂單狀態的變遷,比如待支付、已支付待發貨、待收貨、完成的遷移;要考慮是不是需要使用狀態機來驅動狀態的變更和後續流程節點操作。
還要考慮併發狀態修改問題,同時對同一個訂單隻存在一個修改;狀態變更的有序問題,狀態變更訊息的先到後到問題,如支付成功訊息和使用者取消訊息的時間差。
訊息佇列
訊息佇列,用來解耦一些不需要同步呼叫的服務或者訂閱一些自己系統關心的變化;使用訊息佇列可以實現服務解耦(一對多消費)、非同步、緩衝(削峰)等。比如電商系統中的交易訂單資料,該資料有非常多的系統關心並訂閱,比如訂單生產系統、定期送系統、訂單風控系統等等;如果訂閱者太多,那麼訂閱單個訊息佇列就會成為瓶頸,此時需要考慮對訊息佇列進行多個映象複製。
大流量緩衝持久化
在電商搞大促時,此時的系統流量會高於正常流量的幾倍甚至幾十倍,此時就要進行一些特殊的設計來保證系統平穩度過這段時期;而解決的手段很多,一般都是犧牲強一致性,而是保證最終一致性即可。
比如扣減庫存,可以考慮這樣設計:
直接在Redis中扣減,然後記錄下扣減日誌,通過Worker去同步到DB。
還有如交易訂單系統,可以考慮這樣設計:
首先結算服務呼叫訂單接單服務將訂單儲存到:訂單Redis和訂單佇列表,訂單佇列表可以按照需求水平擴充套件N個表,通過佇列緩衝表提升接單的能力;然後通過同步Worker同步到訂單中心表;假設使用者支付了訂單,訂單狀態機會驅動狀態變更,此時可能訂單佇列表的訂單還沒有同步到訂單中心表,此時狀態機就要根據實際情況進行重試。
如果使用者檢視單個訂單詳情可以直接從訂單Redis就能查到;但如果查詢訂單列表需要考慮訂單Redis和列表的合併。
同步Worker在設計時需要考慮併發處理和重複處理的問題,單機序列掃描處理(每臺Worker只掃描其中的一部分表)還是叢集處理(Map-Reduce),另外需要考慮是否需要對訂單佇列表新增相關欄位:處理人(哪個應用正在處理)和處理狀態(正在處理、已處理、處理失敗)。
資料校對
在使用了訊息非同步機制的場景下,可能存在訊息的丟失,需要考慮進行資料校對和修正來保證資料一致性和完整性。可以通過Worker定期去掃描原始表進行補償,掃描週期根據實際場景進行定義。
資料異構化
訂單分庫分表一般按照訂單ID進行分,那麼如果要查詢某個使用者的訂單列表就需要聚合N個表的資料然後返回,這樣會導致訂單表的讀效能很低;此時需要對訂單表進行異構,異構一套使用者訂單表,按照使用者ID進行分庫分表;另外還需要考慮對歷史訂單資料進行歸檔處理。
還一種異構場景,如商品詳情頁,因為資料來源太多,影響服務穩定性的因素就太多了,因此最好的辦法是把使用到的資料進行異構儲存,形成資料閉環;提升服務的效能和穩定性。而有些資料異構的意義不大,如庫存價格可以考慮非同步載入,或者併發請求合併。
後臺系統操作可反饋
在我接觸過的很多系統,很多場景都需要反饋,比如修改了某些內容想預覽看看最終效果,即想得到一些反饋;還有一些是規則系統,希望看到這些規則在系統資料下的反饋。因此在設計後臺系統請考慮效果的可預覽、可反饋。
後臺系統審批化
對於有些重要的後臺功能需要設計審批流,比如調整價格,並對操作進行日誌記錄從而保證操作可追溯、可審計。
防重設計
比如結算頁需要考慮重複提交,還有如下單扣減庫存時需要防止重複扣減庫存。解決方案可以考慮防重KEY、防重表。而有些場景如重複支付,如有的電商網站同時支援微信支付、京東支付,渠道不一樣是無法防止重複支付的,但是系統設計時需要將支付的每筆情況記錄下。比如下圖是我在京東使用京東支付和微信支付模擬的重複支付之後進行退款的支付明細:
冪等設計
在交易系統中經常會用到訊息,而現有訊息中介軟體基本不保證不發生重複訊息的消費;因此需要業務系統考慮在重複訊息消費時進行冪等處理。還有如使用第三方支付時,第三方支付會進行非同步回撥,因此也要考慮做好回撥的冪等處理。
文件&註釋
我接觸的一些系統是完全沒有文件、程式碼沒有註釋的,完全都是人傳人;這將導致後來人接手很痛苦,而且對有些程式碼是完全不敢改動的,比如有些程式碼完全是因為業務的一些特殊情況而寫的,可以說是沒有註釋是完全不懂為什麼那麼做的。因此在一個系統發展的一開始就應該有文件庫(設計架構、設計思想、資料字典/業務流程、現有問題)、業務程式碼/特殊需求有註釋。
備份
包括程式碼和人員。程式碼主要提交到程式碼倉庫進行管理和備份,程式碼倉庫應該至少具備多版本的功能。人員備份指的是一個系統至少應該有兩名開發瞭解,即假設其中一名離職了也不會出現新人接手之後手忙腳亂事故頻發的狀況。還有一些是“核心人員”,寫著系統的核心程式碼,被認為是“不可替代的”,這種情況也是儘可能的讓他帶一名兄弟一起開發核心程式碼(業務系統),即使離職也還是可以努力下克服困難的。
本文只是整理了一小部分原則,還有很多好的原則無法在一篇文章中全部闡述,比如可回滾(系統出問題時第一時間應該回滾處理,必要情況下摘除並保留一臺問題機器進行問題排查)、有損服務(故障功能降級/遮蔽、部分人可用、部分系統可用)、灰度釋出(功能只對部分人開發,從而保證假設出問題只是影響一小部分人)等等,每一個原則都可以寫一篇文章好好闡述。
前端交易型系統本身是非常複雜的,以上原則只是筆者在實際開發時遇到過並使用的一些原則,而還有很多好的原則和經驗是可以借鑑的,如果您有好的想法歡迎整理成文章分享給更多的人。另外筆者對支付/結算、供應鏈、庫房生產等部分也不熟悉,只進行了前端交易系統的一些原則的總結,也希望更多人加入進來來完善設計原則庫。