作者: HannahLin
來源:medium
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
前陣子在與朋友一起籌劃的後端開發線上分享會 BESG 有成員分享了 TinyURL 的系統設計 (System Design),剛好也看到了知名 YouTuber Terry 關於 Google 系統設計面試的影片,瞭解到在美國的資訊業,不論你是前端、後端、資料工程師還是 DevOps,System Design 系統設計幾乎都是面試時的必考題。
有人可能會覺得,反正那是國外的狀況,我在國內找前端的工作,不需要會系統設計也可以錄取吧?是沒錯,以目前國內的前端業界來看,面試大多是不會考系統設計的,但是其實學習系統設計並不僅是為了應付面試,更是學習如何應付複雜系統的能力,也是從 Junior 開發者過渡到 Senior 開發者的關鍵。就算身為前端開發者,也會需要面對越來越複雜的系統,學會基本的系統設計思維除了能讓你更瞭解系統的整體架構外,同時也加強在開發時和其他角色溝通與協作的能力。
我是一個剛要進入社會,準備開始自己第一份正職的菜鳥工程師,主要 focus 在 Web 前端技術,但也熱衷於學習後端開發與雲端技術。我想透過這篇文章,以自己是前端開發者的角度出發,去介紹我認為前端開發者也該擁有的基本系統設計思維,也就是說主要會介紹系統設計最表層的元素,而不會去深入探討每一個技術的深入實作,目標在廣而不在深。
要知道系統設計是一門非常非常非常複雜的技術(說了三次,應該瞭解到底多複雜了?),絕對不是我這樣的菜雞可以精通的,因此這篇文應該會蠻入門也蠻淺的,主要目的是希望和我一樣剛入行的前端開發者在看完文章後,也能擁有最基本的系統設計思維,除了處理 UI 畫面與瀏覽器相關的眉眉角角外,應該也要理解一個合格的系統在背後是怎麼運作的。
本篇文章的流程會是這樣的,首先我會把我認為系統設計的重要元素列出來,並對每個元素進行更近一步的介紹,等讀者有了大概的認知後,再分享我認為在面對系統設計時可以採用的思維走向,最後再以一個實際的系統設計範例按照先前介紹的思維走向來解決問題。
以分散式系統為設計目標
雖然我們知道分散式相對於單機來說複雜許多,在單機都處理不好的狀況下去碰分散式系統幾乎是死路一條,不過單機的 Vertical Scaling 是有侷限性的,並且 Single Point Of Failure 的問題也讓系統充滿風險,要建立一個“可靠的大型系統”,採用分散式架構似乎是無法避免了。況且在遇到系統設計面試問題時,通常都會預設要設計的系統是高流量的,畢竟設計一個只能承受低流量的系統是毫無意義的,因此本篇文章討論的系統設計都會以分散式系統為出發點。
我們希望設計出來的系統能擁有哪些特性?
Scalability 可擴充套件性
當系統遇到的流量漸漸變大時,我們會希望系統的伺服器或儲存空間也能夠跟著擴充套件,來避免無法負荷的狀況。
用白話一點的方式來比喻的話,想像今天你跟 4 個朋友約好要出去玩,你們租了 Toyota 的四人座 Vios,不過另外 2 個人看到你們要出去玩,就堅持要你們帶上他們,當然現在 6 個人是塞不下小小的 Vios 的,你們只好換租可以載 6 人的 Luxgen U6。
這就很像是系統設計垂直擴充套件(Vertical Scaling) 的概念,藉由提升單機 CPU 或記憶體來提升效能。然而這樣的提升是有限制的,想像越來越多朋友想跟你們一起出去玩,你說到了 30 個人,你還可以換包遊覽車,那 300 個呢?(別跟我說包火車)這時可能就得放棄全部人都擠一臺車的方式,改為租用多臺遊覽車來載運所有人。這在系統設計上稱作水平擴充套件(Horizontal Scaling),用多臺機器來分流處理單機可能無法負荷的流量,達到系統的可擴充套件性。
Reliability 可靠性
可靠性代表一個系統在它開始執行之後到某個時間點,系統正常執行的機率,也就是系統無故障執行的概率。
Availability 可用性
可用性是一個容易跟可靠性搞混的指標,它的定義為系統在面對各種異常時可以正確提供服務的能力,更嚴謹的定義為“系統服務不中斷執行時間佔實際執行時間的比例。””如果以公式來看:
Availability % = (available time / total time) *100
因此可用性跟容錯性是相關聯的,在單機架構下,機器炸了,使用者也 access 不到服務了,而分散式的狀況下即使有機器 shutdown,也會由其他機器馬上遞補上,對使用者來說服務一直是可用的,這也是為什麼說分散式系統可以提升可用性的原因。
Reliability 與 Availability 這兩個指標常常讓人搞混,一個 reliable 的系統通常也會是一個 available 的系統,Availability 雖然可以透過分散式系統的冗餘電腦 (redundant) 來達成,但卻不能保證這個系統是 reliable 的。
Efficiency 高效率
代表這個系統的效率如何,一般常見的指標有系統的延遲(Latency)與吞吐量(Throughput).
Latency 延遲:執行一個操作要花費的“時間長度”。以我自己較熟悉的 web 領域來說,Latency 指的是使用者發出請求後,等待 server 接收請求,進行處理後回傳給使用者的總花費時間。
Throughput 吞吐量:以一個時間區間作為單位,單位時間內可以執行“幾次”操作,或運算的“次數”。同樣以 web 來舉例,Throughput 指的是單位時間內伺服器可以接收的請求量。
要建構一個高效率的系統,我們會希望系統可以達到 Low Latency 與 High Throughput。
Manageability 可管理性
故名思義,代表一個系統是不是方便管理,是不是能快速迭代新功能?是不是能夠快速追蹤 bug?或是能不能把 infrastructure 抽象化,讓應用工程師可以專注在程式邏輯的開發。
System Design Common Components & Topics
無論是面對系統設計的題目或是自己在思考系統的架構,有些技術是建構分散式系統時非常重要的元素,瞭解這些元素將會使我們對於分散式系統更加了解,在系統設計時也更得心應手,這些常見的元素有:
Load Balancer
Web Server
Database (SQL vs NoSQL)
Schema Design
Caching
Replication
Partitions
Sharding
Read/Write Splitting
Algorithm
Queue
Consistent Hashing
Proxy
CAP Theorem
如果要每個技術或概念都介紹的話應該得花不少篇幅,因此這邊如果讀者對某些概念不熟悉,就麻煩自行研究囉!
面對系統設計時的思維走向
系統設計是一個開放式的問題,沒有所謂一定正確且標準的解法,不管哪種方法幾乎都會帶來 trade off,所以我接下來提供的思維走向也許不是最好最標準的,也不一定適用於所有的情形,但我認為它可以幫助我們快速建構出系統的基本雛形與軟硬體需求,如果你對系統設計毫無想法,不妨試試看參考這個思維走向,再根據自己的需求去做細部的調整。
Step.1 弄清系統需求
這是最基本卻也是最重要的一步,弄清系統的需求後,你才知道自己到底要設計什麼,如果是在系統設計的面試,這也是應該要儘早跟面試官確定的事。系統需求一般來說可以分為兩種型別:
- Functional Requirements
- Non-Functional Requirements
Functional Requirements 代表系統該要有的功能,以 YouTube 系統舉例,使用者可以建立自己的頻道,也可以去訂閱別人的頻道,在訂閱頻道釋出新影片時會收到通知…等等你想得到的各種功能,都算在 Functional Requirements 的範疇裡。
Non-Functional Requirements 顧名思義是一些跟系統功能較無直接關連的需求,例如系統需要有高可用性、系統延遲需要非常低、需要嚴格的資料一致性…等等。
Step.2 關於系統流量、容量、網路頻寬等指標的粗略計算
對系統的流量、儲存空間、網路做初步的估算,對於後面要考慮 scaling、caching、load balancing 時是有幫助的,再者對這些指標進行評估,也讓我們可以更好的掌握系統的資源成本。
同樣以設計 YouTube 來舉例, 我們可以先估算系統大約會有多少使用者,其中又有多少 DAU (Daily Activated User),估算一天大約會有幾部影片上傳,影片建立跟讀取的比例是多少…等等,這是比較偏向系統流量的考量。
下一步可以估算系統的儲存容量,例如每年儲存影片的總容量大約是多少?儲存的資料會存活多久?如果系統有實作 Caching,Memory 的用量又大概是多少?
而最後網路貸款也是我們可以事先預估的指標。
如果讀者不知道怎麼計算這些指標也不要擔心,在下一個章節會以設計 Instagram 為範例跑一次系統設計流程,到時候會示範估算這些指標的方式。
Step.3 定義 System Interface
當我們釐清系統的需求後,就可以按照需求來粗略規劃系統的 API,這也可以讓我們確認先前訂出的系統需求並沒有定義錯誤。這時可以先思考系統要採用哪種 API 架構,例如 REST APIs、SOAP、GraphQL,再來可以簡單定義出有哪些 API endpoints,以 YouTube 來舉例:
uploadVideo(user_id, video_content, video_location, user_location, ……)
addVideoToFavorite(user_id, video_id, timestamp, …….)
Step.4 定義 Data Model | DB Schema
在 System Design
的前段及早定義出 DB Schema
將幫助我們更清楚系統的資料流,我們應該清楚不同 Entities 之間是怎麼互動與溝通的,以 YouTube 為例,Data Model 可能是這樣子:
User: UserID, Name, Email, DoB, CreationDate, LastLogin, ….
Video: VideoID,VideoLink, VideoLocation, NumberOfLikes, TimeStamp, …
.….
在這個階段也可以先思考究竟系統適合哪種資料庫?RDBMS 還是 NoSQL ? 還有使用者上傳的影片與圖片,又適合儲存在哪呢?
Step.5 High-level design
這步驟是系統的 High-level 設計,可以在一張圖表畫出系統大概由哪些 Components 組成,因為先前步驟已經確定了系統需求,也對流量做了初步估算,所以在這個步驟我們可以設計系統需不需要做分流、讀寫分離,以 YouTube 的例子來說,我們可能還需要一個分散式的檔案儲存系統來存放影片。
Step.6 System Detailed design
先完成 high-level 的系統設計,接下來才進入系統的 detailed design,如果是面對系統面試,在時間有限的狀況下,可以針對前面 high-level design 畫出的架構圖裡挑出兩三個 components 來說明就好,而這邊建議可以跟著面試官的引導走。(如果不是在面試,當然你就有大把時間可以好好對每個環節做 detailed 的設計了)
針對特定的 component 或 topic,我們可以提供兩三種可行的方法,並思考它們各自的優缺點,這也是個開放性的問題,重要的是你要能清楚每種方式的 trade off,並找出最“適合”自己系統的做法。
- 在需要存取大量資料的情況下,我們該怎麼把資料做 partition 並且存到不同資料庫伺服器裡 ?
- 我們應該在系統的哪些 layer 加入快取服務 ?
- 系統中哪些部分比較需要做 load balancing ?
Step.7 找到系統可能瓶頸或 trade off 並嘗試給出解決方案
如果是在系統設計的面試中,在做完系統架構設計之後,可以針對自己設計的系統提出一些可能的瓶頸,畢竟沒有所謂完美的系統架構,能夠講出自己所設計的系統有哪些缺陷或瓶頸,代表你對系統整體的掌握度是高的,也許在面試官的眼中是加分的行為(不過這邊可能得注意挖坑給自己跳的狀況)。
嘗試設計一個 Instagram 吧!
雖然知道了面對系統設計可以採取的思路,但我相信各位讀者看到這裡還是覺得十分抽象吧?那不如我們就按照前面的思考步驟實際設計一個 IG 系統體會看看吧!(下面代稱這個系統為“Fake IG”好了?)
(大家應該都知道 IG 是什麼吧!??)
Step.1 理清系統需求
前面有提到可以針對“Functional Requirements”與 “Non-Functional Requirements”來區分系統需求
Functional Requirements:
- 使用者可以上傳照片、影片,另外也可以讀取與下載
- 使用者可以追蹤其他使用者以觀看最新貼文
- 使用者可以根據 tags 來搜尋文章
- 當使用者重新整理頁面後,系統會根據使用者追蹤的人顯示該些使用者的最新貼文
Non-Functional Requirements:
- Fake IG 系統需要具有高可用性(high availability)
- 重新整理頁面看最新貼文時可以允許短暫的延遲
- 在某些狀況下,系統的一致性可以犧牲,例如使用者貼文的愛心數量(每個使用者看到的數量即使不一樣,也不會造成什麼影響)
- 系統希望可以擁有高可靠性(high reliability),使用者上傳的照片、影片不能夠遺失
我們都知道實際上 IG 的功能絕對比上面列出來的還要多,例如在照片上標記其他使用者、限時動態…等等,這些較進階的功能可以自己思考要不要列出。(設計出基本功能比較重要)
Step.2 關於系統流量、容量、網路頻寬等指標的粗略計算
關於系統的流量,我們可以先假設高一點,畢竟設計能面對高流量的系統才有意思。假設我們的 Fake IG 系統的使用者總人數有 5000 萬人,當然裡面不乏許多早就沒在使用的幽靈使用者,所以這邊假設每天仍然活躍的使用者(daily active users, DAU)有 100 萬人。
接下來我們先忽略使用者可以上傳影片這個行為,先限定使用者只能夠上傳照片,不然要考慮的因素實在太多了。假設一天大約有 150 萬張照片會被上傳,平均一張照片的檔案大小為 250 KB,那麼儲存一天中新上傳的照片總共需要的容量為
1.5M * 250KB ~= 375GB
如果將時間拉遠來看,10 年下來存放照片需要的容量為
375GB 365(days) 10 ~= 1368.75TB
當你在後面的步驟定義好 DB Schema 後,也可以再回過頭來用同樣的方式估算資料庫要儲存的資料總大小,例如我們的系統一定會有儲存使用者資訊的 User Table
,而它的欄位與空間如下
UserID (4 bytes) + Name (20 bytes) + Email (32 bytes) + Birthday (4 bytes) + CreationDate (4 bytes) = 64 bytes
先前有假設 Fake IG 的使用者總人數有 5000 萬人,因此儲存使用者資訊的 total storage 約為:
50M* 64 ~=3.2GB
其他的 DB Schema 與上傳圖片使用的 Network Bandwidth 也可以採用類似的方法估算需要的容量。
Step.3 定義 System Interface
規劃系統 API 的步驟,因為是一個十分開放式的問題,答案也不太會影響接下來的步驟因此這邊就不多花篇幅說明。
Step.4 定義 Data Model | DB Schema
定義出 Data Model 可以讓我們清楚資料的關聯與資料的流動,在未來要做 data sharding 或 partitioning 時也會比較容易。在 Fake IG 系統中,我們先假設只需要先考慮使用者與使用者上傳的照片就好(不然我這邊會寫不完,還請高抬貴手),在學生時期十分認真修資料庫課程的你可能會馬上畫出以下的資料庫關聯圖
畫完圖後你可能會覺得這個 use case 還蠻適合使用如 MySQL 的 RDBMS 的,畢竟資料是有 join 的需求的。不過 RDBMS 在 Scaling 上是非常非常複雜且困難的,例如資料一致性的處理方式也與單機時大不相同。而使用 NoSQL 其實也是可行的,雖然可能需要一些額外的 table 來儲存資料的關聯 (例如 UserPhoto),不過在 Scaling 上卻較為簡單。我也沒辦法告訴你,使用 RDBMS 還是 NoSQL 好,它們各有各的優缺點也有各自適用的時機,因此這個問題也算是開放式的,應該在多深入瞭解後再做出選擇。
另外像照片影片這種資源,一般會需要像是 AWS S3 這樣的 Distributed File Storage,而在我們的資料庫中則只存圖片或影片在 file storage 的 link 還有其他的 meatdata。
Step.5 High-level design
當然我們也不一定要畫出整個系統的架構圖,尤其是在面試的話,我們得在有限的時間內儘量 focus 在系統的重點部分,以 Fake IG 來說我們先試著設計使用者上傳照片與讀取照片的功能。
以 high-level 的角度來看 Fake IG 的照片系統,大致上會有兩種情境,也就是使用者上傳照片與讀取照片,所以我們會需要一臺 application server 來處理使用者的讀寫 requests,另外會需要一個如 AWS S3 一樣的分散式 file Storage 來儲存照片,另外也會再開一個 database 來儲存照片相關的 metadata。
Step.6 System Detailed design
不過仔細思考就會知道上傳照片相比讀取照片所耗費的時間會多出更多,因為上傳照片可能涉及了硬碟的寫入,也不像讀取一樣可以加一層快照層。通常 web server 是有連線數量的限制的,如果上傳照片的過程太耗時,可能會佔用 web server 的連線請求,導致要讀取照片的 request 被卡住,造成響應的延遲。要處理這個 bottleneck 的其中一種方式就是將讀取與上傳拆成兩個獨立的 server 來分別處理請求,而負責上傳照片的 server 還能搭配 message queue 來消化請求。這樣的做法還有一個好處就是兩個服務可以依照需求各自 Scale 與 Opimize。
對於 Availability 與 Reliability,也可以考慮為系統的各個 component 加入 replica service,這樣 horizontal scaling 的結果是可以避免 single point of failure,在其中一個服務節點掛掉時仍能保持系統可用性,搭配 Load Balancer 也可以做到流量的分流,降低系統的延遲。
提到 Load Balancer,它的目的是達成 horizontal scaling,不過它也可能會有 single point of failure 的風險,因此 replica 也可以應用在它上面。就算是隻需要一個 instance 來運作的服務,也可以為其建立 redundant secondary copy,這個 copy 不會 serve 任何的流量,卻可以當作備胎,在主服務掛掉時立即取代它的工作成為 master node。
同樣的 Scaling 思維也適用於儲存服務例如 Fake IG 使用的 File Storage 與 Database,畢竟我們不希望系統出現檔案或資料遺失的狀況,因此可以為 Database 與 File Storage 加 backup server,讓資料也可以避免單點失效後遺失的問題。
不過還是得呼籲一下,加 replica services 未必能改善所有效能或其他的瓶頸,再來成本也是需要考量的一個元素,因此還是得依照需求謹慎評估是否需要 scaling,需要的話也要謹慎評估 scaling 的程度。
更進一步可以思考 database 資料是不是需要 Partitioning 或 Sharding,前者是在同一個資料庫中將 table 拆成數個小 table,後者則是將 table 放到數個資料庫中。
Partitioning 的 table 與 schema 可能會改變,Sharding 的 schema 則是相同,但分散在不同資料庫中,Partitioning 是為了分散單表的壓力,Sharding 則是分散單庫的壓力,實際上還是要依照需求找出適合當前系統的方式。
以 Fake IG 來說,如果真要選擇我可能會選擇比較容易擴充套件的 Sharding,不過 Sharding 也帶來相當多的問題例如橫跨不同 shards 的 transaction、rollback、schema change、join、每個 shard db 還要另外去做 backup……等等。
如果選擇了 Sharding 的方式,那麼 Shard 時依賴的 key 就很重要了,一般來說可以分為 Range-based 與 Hash 兩種方式(如果完全不知道 Sharding 的讀者可以參考我之前的筆記文章),假設我們以 userId 來作為 shard 的 key,那可能會出現 hot user 造成某一個資料庫 loading 過重的問題(想想那些在 IG 上追蹤人數破億的全球巨星,一天不知道會有多少使用者去瀏覽他們的文章呢,那麼存取這些明星資料的資料庫可能流量就會比其他 instance 還要大很多)。
總之我覺得 Sharding 的複雜度還有坑真的是挺多的,也許在一開始得先考慮清楚系統是不是真的需要 Sharding,如果在面試提出要做 Sharding 或 Partitioning,也要做好萬全準備,以免只是挖更多坑給自己跳。
當然 Caching 也是一個必定要考慮的機制,因為 Fake IG 會產生許多圖片,而這些資源非常適合暫存在離 end users 更接近的 CDN 裡。除了 CDN Cache,也可以在資料庫前加一個例如 Memcache 的快照伺服器,用來暫存一些較常被抓取的資料。
關於到底要存哪些資料到快照這個問題,我們不可能把所資料都放到快照裡,根據“80–20 Rule”,80% 的流量可能都來自於 20% 的 hot data,因此我們可以找出較常被 user 存取的 20% photo 與 metadata 資料存到快取中。
而快照是需要一些清除策略(cache eviction policy)的,不然無限增長的狀況下很容易就沒有快照空間了。在 Fake IG 系統中,我覺得 LRU Cache (Least Recently Used Cache)就還蠻適合的,當要清除快取空間時,可以先從近期最少被存取的資料開始移除。
總結
雖然說身為前端開發者,在求職過程甚至是整個職涯都有可能不會碰到系統設計相關的問題,不過因為我將自己定義為雜食性的軟體工程師,遇到有興趣、有挑戰性的技術我都想學,再加上自己一直有個不切實際的矽谷夢,所以依然深信自己未來某一天也會遇到系統設計的挑戰。而自己也的確在接觸這些內容後對整個系統應用架構有了更進一步的瞭解,所以我非常推薦前端開發者也可以嘗試瞭解最基礎的系統設計。
原文:https://medium.com/starbugs/%...
交流
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq44924588... 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。