怎麼應付高流量的問題,在這年來似乎變成了面試必問的問題。第一次遇到這個問題應該是在 2014 年左右 Accupass 的搶票問題,之後也有遇到一些真實的案例 ,不過真的要來看 scaling 這個主題,我覺得並不是要專注在熱門事件/活動的 peak 上,而是一個系統的使用量從小到大不斷的成長,系統應該要怎麼持續性優化的過程。

再談這個主題之前,有幾件事想先說:

  1. scaling 不應該 overengineering。不同時期的系統都有符合當下的設計,過猶不及 ,能夠處理更高流量的系統相對來說更加複雜,需要更多的時間以及更專業的人才,這部分最大的影響就是成本會拉高很多,以及開發與營運的團隊是否能跟上這樣的設計。
  2. 如何正確的判斷現在的 bottlenecks,要解決問題就要知道問題在哪裡,才能對症下藥。我們最常聽到的就是網站擠爆、很慢,需要加開伺服器,加開伺服器真的能解決問題嗎?如果問題是在 database ,請問要怎麼加開伺服器?問題是 bandwidth 不足的時候,開伺服器有用嗎?問題是程式碼 performance 相關的時候,多開幾台可能有用,但是為什麼不處理 performance issue呢?
  3. 承上,知道 bottlenecks 之後,事情才剛開始,要怎麼選擇適合的解決方案,需要對解決方案有足夠的知識以及如何符合 product roadmap。沒有一個解決方案是完美的,任何一個方案都一定有其優缺點,並且這些優缺點會隨著產品的不同階段有不同的看法,舉例來說,使用 FailOver 的方案伴隨的缺點是系統變得複雜、維運的難度提升,成本增加。但是想看看,會需要用到 FailOver 的產品,其規模應該也有一定程度,這個時候他們還會認為上述的缺點是問題嗎?

接下來會討論在 scaling 中會遇到的 bottlenecks 以及其對應的解決方案

Vertical V.S. Horizontal Scaling

Vertical scaling 指的是提升機器的 CPU, RAM, Disk 等對效能有影響的硬體條件,我覺得這是要最先考慮的解決方案(不過只限定在 cloud vendor 下),只要提升費用就能解決問題,快速又有效,這個解決方案最大的關鍵在於,我們要能夠判斷 Vertical scaling 不再能夠有效解決問題的時機。

Horizontal Scaling 指的是使用價格比較划算的機器複製同樣的服務,以量取勝,這個概念類似我們常聽到的加開機器(在 docker 下,這個概念會被 container 取代 )。雖然 Horizontal Scaling 常常跟 Vertical scaling 做比較,但我覺得這兩者並不是互斥的存在,Horizontal Scaling 如果調整的好,會比 Vertical scaling 來得有效率跟經濟實惠。

Horizontal Scaling 使用上的限制比較多,加上必須搭配 Load Balancer 一起使用(有了 LB 又必須要考慮 High Availability…),所以整體的複雜度會提高不少 。一般來說,使用 Horizontal Scaling 服務最好是 stateless 的,才不會造成用戶驗證身份的無限循環或是資料的遺失,如果服務本身是 stateful 的,有些情況可以利用 LB 做 sticky sessions 的方式,讓請求方固定連線到同一台裝置,雖然説 sticky sessions 可以解決 connection state 的問題,不過只要服務本身是 stateless 的,就跟本不用擔心了,另外 sticky sessions 固定了 user & device ,因此也會降低了 horizontal scaling 的彈性。

台灣著名的 BBS 站台 PTT,在系統架構的限制下,只能採用 Vertical scaling 。 PTT 的連線方式有 telnet (這是不安全的,請別再使用) SSH 以及 WebSocket ,每一種連線方式都是 stateful ,連線進去之後,會在系統開一個 process 來存放這一個 connection 的資料,如果今天後端是兩台主機,就會造成無法識別身份的問題,不過這也還好解決,用 sticky sessions 就可以了,真正的問題在於 PTT 不是使用 DBMS 的解決方案,資料是存在 File System 裏面,PTT 的主程式啟動之後,會把所有的資料載入到記憶體裏面,再透過 Shared Memory 的方式,讓連線的 process 可以去存取看板、文章資料等,因此如果有多台主機,就會有各自的 Shared Memory ,造成 data-race 的問題。

Cache

使用 Cache 可以降低 Server 或是 database 的負擔,常見的 cache 有 static file cache (image, js, css etc…) 或是 memory cache 用來記錄不太會改變,但是很常被讀取的資料。

Cache 的部分可以參考這一篇文章

Load balancing

Load Balancer 的原理可以參考過去的文章,使用 load balancer 分散請求給後方的服務是分散式架構中很基礎核心的部分,不過同樣地,要在 production 使用一個技術前,請先理解其運作的原理,特別是 L4 跟 L7 的差別。load balancer 的優點很多,包含:

  1. 可以預防用戶存取到後方不正常的 service
  2. 有效地分配用戶的數量,不讓單一 service 過載
  3. SSL termination,降低後方 server 的負擔
  4. Canary Deployment

缺點的話,我覺得最重要的就是一定要有 High Availability 的架構,否則 load balancer 本身就會成為系統的 bottleneck,也因此會需要更多的成本來負擔 HA ,同時複雜度也提升了。

Database

Database 的 scaling 思維我覺得跟 web server 不同,主要是因為 database 的 bottleneck 要對應的解決方案各有所不同,而且 database 不像一般的 service 可以任意的增減,以下會進行說明。vertical scaling 通常會是首選,升級 database 的硬體規格可以有效的解決大部分的問題,database 主要的工作,是從 disk 中把資料取出來,因此在調教 database 的思考模式就是怎麼樣能夠快速的取出資料,利用 index 在 RAM 中尋找 data 是首選,資料量越大,RAM 的需求也會越高,等 RAM 達到瓶頸之後,就會開始考慮另一種思維,盡量不要讓 database 在大量的 table 中找資料,因此就會利用到 partition, replication, sharding 等分散式技術,不過這些技術跟一般的 service horizontal scaling 不同,資料必須事先進行規劃,而且都是必須保存的資料,無法任意的移除。除了在 database 本身做處理,還有一種很有效的改善方案是盡量不要跟 database 要資料,改用 cache 處理,比起各方面都很昂貴 (expensive) 的 database ,cache 是很划算的 。如果對上述的 database 技術有興趣可以看這一篇

Asynchronous

當系統 scaling 到某一種程度的時候,我們會發現系統越來越複雜,而且處理需求的等待時間也越來越長,我覺得這是自然的,就跟人類的組織一樣,所以為了減少使用者的等待時間,又不能把必要的流程刪減,有一個做法就是 asynchronous,常見的 asynchronous pattern 有 message queue, task queue 等方式,優先讓使用者能得到立即的回應,把後續的工作交由 queue 管理。不過不見得用 asynchronous 方式處理是好的,當需求的處理並不慢,或是請求一定要等某個最花時間的服務處理完才有意義的時候,還是乖乖用 synchronous 的方式讓使用者等待吧!asynchronous 很常跟其他名詞搞混,這一篇文章有詳細解釋他們的不同。

小結

scaling 是在 system design 中會考慮到的一個情況,所以要能處理 scaling 的問題,就要回到 system 本身來思考,除了需要大量的知識之外也必須跟實務上的經驗結合。最後,上面的討論其實略過了 peak 的問題,peak 跟文章中討論的 scaling 問題得處理方式略有不同,但是基本的道理都一樣,還是要回到理解 system 才能做出適合的解決方案,之後有機會再來論看看 peak 的情況要怎麼思考囉。