如何設計一個微博系統?- 4招教你搞定系統設計

braden-collum-75XHJzEIeUc-unsplash

經常在面試的時候,會被問到系統設計類的題目,比如如何設計微信朋友圈、如何設計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. 可擴展性設計

這是最後一步,面試官可能會針對系統中的某個模塊,給出擴展性相關的問題,這塊的問題可以分爲兩類:

  1. 當前系統的優化。因爲沒有完美的系統,我們的設計的也不例外,因此這類問題需要我們認真反思系統的設計,找出其中可能的缺陷,提出具體的解決方案。
  2. 擴展當前系統。例如我們當前的設計能夠支撐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整體設計

根據上面的分析,這一步將主要服務拆分出來,可以分爲讀微博服務、發微博服務、搜索服務;同時還有相關的時間線服務、微博信息服務,用戶信息服務、用戶關係圖服務、消息推送服務等等。考慮到服務高可用,這裏也引入了緩存。

拆分好了之後,根據用戶使用場景,可以設計出如下圖所示的系統架構圖,注意到目前爲止,都是粗略設計,我們只需要將服務抽象出來,完成具體的功能即可。後續步驟會對主要服務進行詳細設計。

下圖的架構中,主要實現了用戶發微博、瀏覽微博時間線和搜索的場景。

GJtsyu

設計核心模塊

上一步我們完成了微博的架構設計,這一步從用戶場景入手,詳細設計核心模塊。

用戶發微博

用戶發微博的時候,發微博服務需要完成如下幾項工作:

  • 將微博寫入到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的水平擴展等,方便應對各種突發情況。

QK5lHU

下面是進一步優化之後的系統架構圖:

m5YDbe

針對這個系統,我們還可以進一步優化,以下是幾個思路,也可以自己思考看看,

  • 對於有大量粉絲的用戶,比如很多明星;在現有架構上明星每次都會將微博利用數據傳輸服務,發到每個粉絲的時間線上去,這樣其實會造成大量的流量,針對這種情況,可以將這些明星用戶單獨處理,每當粉絲在刷新主頁的時候,會根據這些粉絲的時間線以及明星的微博信息,合併來生成粉絲的主頁時間線,避免了不必要的流量浪費。
  • 只保存最近一段時間的微博數據在緩存中, 這主要是爲了節省集羣空間,並且一般熱點微博都是最近的微博。
  • 只保存活躍用戶的主頁時間線在緩存中,對於過去一段時間內,比如30天內未活躍的用戶,我們只有在該用戶首次瀏覽的時候,從DB中將數據load出來組成時間線。

擴展的方向不止上面說的這幾點,大家可以從緩存(CDN,用戶緩存、服務器緩存)、異步(MQ、微服務等等)、數據庫調優等方向去思考,看如何提升整體性能。這些相關內容我會在後續文章中仔細講述,歡迎關注【碼老思】,後續文章敬請期待。


參考:

可以關注公衆號【碼老思】,一時間獲取最通俗易懂的原創技術乾貨。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章