經常在面試的時候,會被問到系統設計類的題目,比如如何設計微信朋友圈、如何設計12306系統、如何設計一個搶票系統等等。如果是沒有準備過,一般都會不知所措,難以找到切入點。今天這裏碼老思會介紹一個解決系統設計類問題的通用框架,無論什麼問題,朝這幾步走,一定能找到解決辦法。
系統設計在考察什麼?
系統設計面試中,經常會被問到如何設計微信、如何設計微博、如何設計百度……我們怎麼能在如此短的時間內設計出來一個由成千上萬的碼農、PM,經年累月地迭代出來的如此優秀的產品?如果面試者這麼優秀,那還面試啥?百度、谷歌也不可能只是一個搜索框而已,底下的東西複雜去了。
所以首先要明確,系統問題的答案一定不可能是全面的,面試官也不會期望我們給出一個滿分答案。所謂的系統設計面試實際上是在模擬一個場景:兩名同事在一起就一個模糊的問題,討論一番,得出一個還不錯的解決方案。
因此在這個過程中,我們需要與面試官進行充分的溝通,瞭解清楚需求,迴應面試管的各種問題,闡述我們設計的方案。最終設計的結果是開放性的,沒有標準答案;而面試官會在這個過程中,會充分挖掘我們的溝通、分析、解決問題的能力,最後給出通過與否的結論。
常見誤區
沒有方向,只提出各種關鍵詞
在面試中,常見的錯誤是面試官給出問題後,候選人就開始懟各種關鍵詞,什麼Load Balancer,Memcache,NodeJS,MongoDB,MySQL.
不是說我們不能說這些技術詞彙,但是系統設計首先我們需要搞清楚具體的需求,然後在大概確定系統架構,最後才根據場景進行技術選型,因爲並不存在一個完全完美的系統設計,往往都是各方面之間的均衡,至於如何均衡,需要結合面試官的要求和具體實際來選擇。
一開始直奔最優解
系統設計考察我們如何去解決一個具體的問題,在實際工作中,我們也是先實現功能之後,再在此基礎上去進行鍼對性的優化;在面試中同樣也十分重要,一方面,面試官希望看到你從一開始設計系統,到慢慢完善的過程,另外,系統設計其實沒有最優解,往往都是各種因素之間權衡後的結果,就像CAP理論一樣,無法同時滿足,我們的系統只要能滿足面試官提出來的要求,剛好夠用就行。
悶頭設計不溝通
很多人在聽到問題之後,就開始悶頭設計,絲毫不會和麪試官進行溝通交流。這樣不僅不利於自己理解題目意圖,而且讓面試官無法瞭解你是如何一步步去解決問題的過程。
這個時候可以把面試官當做一位同事,比如你對題目不理解,可以提出問題搞清楚題目意圖;又比如你在哪個環節卡住了,也不要一直悶在那,可以大膽向面試官求助尋求提示,也能節省不少時間。
就像開始說的,系統設計沒有最優解,你的思路和解決問題的過程很重要,而這些就是通過不斷的溝通來傳遞給面試官的。
如何搞定系統設計 - 4步法
針對系統設計,這裏給大家提供一個4步法解決方案,無論是任何系統設計的題目,都可以按照這幾步去思考解決。
1. 確定問題需求
這一步主要是確定問題的需求和系統使用場景,爲了搞清楚這個問題,需要和麪試官你來我往地問問題。
在做設計的同時,問面試官的要求是什麼,做出合理的假設、取捨,讓面試官看出你的思考過程,最終綜合所有的信息完成一個還不錯的設計。不要害怕問問題。那並不會說明我們不懂,而是讓面試官理解我們的思考過程。當我們問出問題後,面試官要麼給出明確的回答;要麼讓我們自己做出假設。當需要做出假設時,我們需要把假設寫下來備用。
這個舉個例子,比如面試官讓你設計weibo,因爲weibo的功能較爲龐大,例如發微博、微博時間線、關注、取消關注、微博熱搜榜等等,我們無法在短短的面試時間內完成這麼多功能設計,所以這時候可以詢問面試官我們需要實現哪些功能。
比如需要實現微博時間線的功能,我們得進一步確認,整體用戶量多大,系統的QPS多大,因爲這涉及到我們後續的系統設計,而且如果對於QPS特別高的情況,在後續的設計中需要針對此進行專門的擴展性優化。
QPS有什麼用?
- QPS = 100,那麼用你的筆記本作Web服務器就好了;
- QPS = 1K,一臺好點的Web 服務器也能應付,需要考慮Single Point Failure;
- QPS = 1m,則需要建設一個1000臺Web服務器的集羣,並且要考慮如何Maintainance(某一臺掛了怎麼辦)。
QPS 和 服務器/數據庫之間的關係:
- 一臺Web Server承受量約爲 1K的QPS(考慮到邏輯處理時間以及數據庫查詢的瓶頸);
- 一臺SQL Database承受量約爲 1K的QPS(如果JOIN和INDEX query比較多的話,這個值會更小);
- 一臺 NoSQL Database (Cassandra) 約承受量是 10k 的 QPS;
- 一臺 NoSQL Database (Memcached) 約承受量是 1M 的 QPS。
以下是一些通用的問題,可以通過詢問系統相關的問題,搞清楚面試官的意圖和系統的使用場景。
- 系統的功能是什麼
- 系統的目標羣體是什麼
- 系統的用戶量有多大
- 希望每秒鐘處理多少請求?
- 希望處理多少數據?
- 希望的讀寫比率?
- 系統的擴張規模怎麼樣,這涉及到後續的擴展
總結,在這一步,不要設計具體的系統,把問題搞清楚就行。不要害怕問問題,那並不會說明我們不懂,而是讓面試官理解我們的思考過程。
2. 完成整體設計
這一步根據系統的具體要求,我們將其劃分爲幾個功能獨立的模塊,然後做出一張整體的架構圖,並依據此架構圖,看是否能實現上一步所提出來的需求。
如果可能的話,設想幾個具體的例子,對着圖演練一遍。這讓我們能更堅定當前的設計,有時候還能發現一些未考慮到的邊界case。
這裏說的比較抽象,具體可以參考下面的實戰環節,來理解如何完成整體設計。
3. 深入模塊設計
至此,我們已經完成了系統的整體設計,接下來需要根據面試官的要求對模塊進行具體設計。
比如需要設計一個短網址的系統,上一步中可能已經把系統分爲了如下三個模塊:
- 生成完整網址的hash值,並進行存儲。
- 短網址轉換爲完整url。
- 短網址轉換的API。
這一步中我們需要對上面三個模塊進行具體設計,這裏面就涉及到實際的技術選型了。下面舉個簡單的例子。
比如說生成網址的hash值,假設別名是http://tinyurl.com/<alias_hash>,alias_hash是固定長度的字符串。
如果長度爲 7,包含[A-Z, a-z, 0-9],則可以提供62 ^ 7 ~= 3500 億個 URL。至於3500億的網址數目是否能滿足要求,目前世界上總共有2億多個網站,平均每個網站1000個網址來計算,也是夠用的。而且後續可以引入網址過期的策略。
首先,我們將所有的映射存儲在一個數據庫中。 一個簡單的方法是使用alias_hash作爲每個映射的 ID,可以生成一個長度爲 7 的隨機字符串。
所以我們可以先存儲<ID,URL>。 當用戶輸入一個長 URL http://www.lfeng.tech時,系統創建一個隨機的 7 個字符的字符串,如abcd123作爲 ID,並插入條目<"abcd123", "http://www.lfeng.tech">進入數據庫。
在運行期間,當有人訪問http://tinyurl.com/abcd123時,我們通過 ID abcd123查找並重定向到相應的 URL http://www.lfeng.tech。
當然,上面的例子中只解決了生成網址的問題,還有網址的存儲、網址生成hash值之後產生碰撞如何解決等等,都需要在這個階段解決。這裏面涉及到各種存儲方案的選擇、數據庫的設計等等,後面會有專門的文章進行介紹。
4. 可擴展性設計
這是最後一步,面試官可能會針對系統中的某個模塊,給出擴展性相關的問題,這塊的問題可以分爲兩類:
- 當前系統的優化。因爲沒有完美的系統,我們的設計的也不例外,因此這類問題需要我們認真反思系統的設計,找出其中可能的缺陷,提出具體的解決方案。
- 擴展當前系統。例如我們當前的設計能夠支撐100w 用戶,那麼當用戶數達到 1000w 時,需要如何應對等等。
這裏面可能涉及到水平擴展、垂直擴展、緩存、負載均衡、數據庫的拆分的同步等等話題,後續會有專門的文章進行講解。
實戰 - 4步法解決Weibo設計
這部分我以Weibo的設計爲例,帶大家過一遍如何用4步法解決實際的系統設計。
確定weibo的使用場景
因爲weibo功能較多,這裏沒有面試官的提示,我們假定需要實現微博的時間線以及搜索的功能。
基於這個設定,我們需要解決如下幾個問題:
- 用戶發微博:服務能夠將微博推送給對應的關注者,同時可能需要相應的提醒。
- 用戶瀏覽自己的微博時間線。
- 用戶瀏覽主頁微博,也就是需要將用戶關注對象的微博呈現出來。
- 用戶能夠搜索微博。
- 整個系統具有高可用性。
初步估算
基於上面的需求,我們進一步做出一些假設,然後計算出大概的儲存需求和QPS,因爲後續的技術選型依賴於當前系統的規模。這裏我做出如下假定:
- 假設有1億活躍用戶,每人平均每天5條微博,也就是每天5億條微博,每月150億條。
- 每條微博的平均閱讀量是20,也就是每月3000億閱讀量。
- 對於搜索,假定每人每天搜索5次,一個月也就是150億次搜索請求。
下面進一步對存儲和QPS進行估算,
首先是儲存,每條微博至少包含如下幾個內容:
微博id
:8 bytes用戶id
:32 bytes微博正文
:140 bytes媒體文件
:10 KB (這裏只考慮媒體文件對應的鏈接)
總共10KB左右。
每月150億條微博,也就是0.14PB,每年1.68PB。
其次是QPS,這裏涉及到3個接口:
- 發微博接口:每天5億條微博,也就是大約6000QPS
- 閱讀微博接口:每天100億閱讀,也就是大約12萬QPS
- 搜索微博接口:每天每人搜索5次,那也大約6000QPS。
這裏估算出來的數據爲後續技術選型做參考。
完成weibo整體設計
根據上面的分析,這一步將主要服務拆分出來,可以分爲讀微博服務、發微博服務、搜索服務;同時還有相關的時間線服務、微博信息服務,用戶信息服務、用戶關係圖服務、消息推送服務等等。考慮到服務高可用,這裏也引入了緩存。
拆分好了之後,根據用戶使用場景,可以設計出如下圖所示的系統架構圖,注意到目前爲止,都是粗略設計,我們只需要將服務抽象出來,完成具體的功能即可。後續步驟會對主要服務進行詳細設計。
下圖的架構中,主要實現了用戶發微博、瀏覽微博時間線和搜索的場景。
設計核心模塊
上一步我們完成了微博的架構設計,這一步從用戶場景入手,詳細設計核心模塊。
用戶發微博
用戶發微博的時候,發微博服務需要完成如下幾項工作:
- 將微博寫入到MySQL等關係型數據庫,考慮到流量較大,這裏可以引入Kafka等MQ來進行削峯。
- 查詢用戶關係圖服務,找到該用戶的所有follower,然後把微博插入到所有follower的時間線上,注意到這裏時間線的信息都是存放在緩存中的。
- 將微博數據導入到搜索集羣中,提供給後續搜索使用。
- 將微博的媒體文件存放到對象儲存中。
- 調用消息推送服務,將消息推送到所有的followers。這裏同樣可以採用一個MQ來進行異步消息推送。
對於每個用戶,我們可以用一個Redis的list,來存放所有該用戶關注對象的微博信息,類似如下:
第N條微博 | 第N+1條微博 | 第N+2條微博 |
---|---|---|
8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte |
weibo_id user_id meta | weibo_id user_id meta | weibo_id user_id meta |
後續的時間線服務,可以根據這個列表生成用戶的時間線微博。
用戶瀏覽微博時間線
當用戶瀏覽主頁時間線時,微博時間線服務會完成如下的工作:
- 從上面設計的Redis list中拿到時間線上所有的微博id和用戶id,可以在O(1)時間內完成。
- 查詢微博信息服務,來獲取這些微博的詳細信息,這些信息可以存放在緩存中,O(N)時間內可以完成。
- 查詢用戶信息服務,來獲取每條微博對應用戶的詳細信息,同樣也是O(N)時間完成。
注意到這裏有一個特殊的情形,就是用戶瀏覽自己的微博列表,對於這種情況,如果頻率不是很高的情況,可以直接從MySQL中取出用戶所有的微博即可。
用戶搜索微博
當用戶搜索微博時,會發生下面的事情:
- 搜索服務首先會進行預處理,包括對輸入文本的分詞、正則化、詞語糾錯等等處理。
- 接着將處理好的結果組裝成查詢語句,在集羣中完成對應的查詢,獲取搜索結果。
- 最後根據用戶的設置,可能需要對結果進行排序、聚合等等,最後將處理好的結果返回給用戶。
系統擴展設計
在做系統擴展設計之前,我們可以依據下面幾個步驟,找出系統中可能存在的瓶頸,然後進行鍼對性優化。
(1)對各個模塊進行benckmark測試,並記錄對應的響應時間等重要數據;
(2)綜合各種數據,找到系統的瓶頸所在;
(3)解決瓶頸問題,並在各種可選方案之間的做權衡,就像開頭所說,沒有完美的系統。
下面針對我們剛纔設計的微博系統,可能的瓶頸存在於下面幾個地方:
- 微博服務器的入口。這裏承受了最大的流量,因此可以引入負載均衡進緩解。
- 發微博服務。可以看到這裏需要和大量的服務進行交互,在流量很大的情況下,很容易成爲整個系統的瓶頸,因此可以考慮將其進行水平擴展,或者將發微博服務進行進一步拆分,拆成各個小組件之後,再進行單獨優化。
- MySQL服務器。這裏存儲着用戶信息、微博信息等,也承載了很大的流量,可以考慮將讀寫進行分離,同時引入主從服務器來保證高可用。
- 讀微博服務。在某些熱點事件時,讀微博服務會接收巨大的流量,可以引入相應的手段對讀服務進行自動擴展,比如K8s的水平擴展等,方便應對各種突發情況。
下面是進一步優化之後的系統架構圖:
針對這個系統,我們還可以進一步優化,以下是幾個思路,也可以自己思考看看,
- 對於有大量粉絲的用戶,比如很多明星;在現有架構上明星每次都會將微博利用數據傳輸服務,發到每個粉絲的時間線上去,這樣其實會造成大量的流量,針對這種情況,可以將這些明星用戶單獨處理,每當粉絲在刷新主頁的時候,會根據這些粉絲的時間線以及明星的微博信息,合併來生成粉絲的主頁時間線,避免了不必要的流量浪費。
- 只保存最近一段時間的微博數據在緩存中, 這主要是爲了節省集羣空間,並且一般熱點微博都是最近的微博。
- 只保存活躍用戶的主頁時間線在緩存中,對於過去一段時間內,比如30天內未活躍的用戶,我們只有在該用戶首次瀏覽的時候,從DB中將數據load出來組成時間線。
擴展的方向不止上面說的這幾點,大家可以從緩存(CDN,用戶緩存、服務器緩存)、異步(MQ、微服務等等)、數據庫調優等方向去思考,看如何提升整體性能。這些相關內容我會在後續文章中仔細講述,歡迎關注【碼老思】,後續文章敬請期待。
參考:
- https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/twitter/README.md
- https://cloud.tencent.com/developer/article/1899760
- https://www.zhihu.com/question/26312148
可以關注公衆號【碼老思】,一時間獲取最通俗易懂的原創技術乾貨。