如何設計一個高效能的網站——方向性問題

from @unsplash
當你向用戶辛辛苦苦的推銷一個產品,使用者已經決定登陸網站去下單的時候,卻因為等了五秒,頁面還沒加載出來,而說了一聲:下次吧,你的心可能就是涼涼的。
這可能就是為什麼人們想要設計一個高效能的網站的原因了。
高效能和高可用其實是非常相關的話題,效能差到極點就是一種不可用,把可用做的好一點也能提高效能。但今天我們主要談一談如何設計一個高效能的網站,或曰網路服務。
效能設計的前人經驗
如果你正在忍受著效能的困擾,有急迫的願望想要提高效能,那麼首先恭喜你,你進步了,你的使用者變多了。
但針對不同的場景,滿足效能提升這個需求的做法是不一樣的。我們最好具體問題,具體分析。
如果你原來是單臺伺服器部署的:應用服務 + 資料庫 + 檔案服務都在一臺機器上,那麼針對不同的情況,你可能會想到不同的辦法:
如果你的瓶頸出現在應用服務的接受能力上:
- 把應用服務抽離出來,用一臺 CPU 更好的機器來執行相關服務,因為應用服務需要大量邏輯計算;
- 也可以把應用服務部署在多臺機器上,用負載均衡(選擇適合的分發演算法),把請求分發到多臺機器上,每臺機器只負責自己的流量,避免處理不過來。
如果瓶頸出現在資料的訪問上:
- 那麼可以看看資料的特點,如果這些資料中有熱點資料,那麼可以把其中狀態比較穩定,不經常改變的資料快取起來;快取可以選擇本地快取,也可以選擇專門的快取伺服器(如果你覺得一臺的記憶體空間不夠的話,可以建立一個叢集做快取,這個我們在“如何設計高可用網站”時會討論)。
- 如果資料的狀態需要經常查詢,但是不經常改變,可以通過建立索引,提高讀取效能。
- 進一步,還可以實現主從兩個資料庫,讓主資料庫(Master)只負責寫,實現資料庫的讀寫分離。
如果你的瓶頸出現在一些靜態檔案的下載上:
最後,前端也可以做一些優化,比如:
把 js 檔案放在 body 的後半部分進行載入,適當的把 css、js 檔案聚合到一起,不要太少,太少的話一個檔案過大,下載很慢,而且沒法在瀏覽器中多執行緒的下載;但也不要太多,太多的話會把很多時間花費在建立 http 連結上。
為了看的更清楚一點,我畫一張圖給你:

初具規模的架構
最終,上面說的各種瓶頸,隨著你的發展,你真的都遇到了,那麼再次恭喜你。
但如果你已經發展到這個地步,資料量很大,想要繼續提高效能,那麼我們可以繼續拆分資料,拆分業務,具體來說就是:
如果你的瓶頸出現在了資料的讀寫上:
- 可以把一張表拆分成多個(分表),水平切分,或者垂直切分。2. 也可以把不同業務的資料分拆到不同的資料庫,這樣應用服務對於原先單一主資料庫(Master)的寫操作可以分散的不同的資料庫中。(這樣的分庫分表我們接下來會接著討論)
- 將一些資料儲存在 NoSQL 的資料庫上,這些 NoSQL 的通常提供一個特性:保障資料的讀取時間恆定,同時也適合多節點的分散式儲存。
如果你的瓶頸出現在應用服務上:
- 可以把其中一部分重要服務拆出來,單獨部署,用更好的資源去運維。比如首頁、訂單、支付這樣的服務。
- 在不同服務之間相互排程的時候,可以引入非同步機制,實現一個阻塞佇列,來用生產者消費者的模式處理一部分事務。
我們修改一下上面的圖,看看我們的架構已經是什麼樣子了:

更復雜的網路服務系統
我們圖中每個服務只畫了了一兩臺機器,但其實,在這樣的架構下,他們有能力拓展到更多臺。
當然,除了上面這些架構上的問題,也需要工程師們寫程式碼的時候,考慮效能,引入多執行緒。
五大方向
上面的零零碎碎都是一些別人實踐出來的經驗,但其實還有更多方法,比如不同地區的DNS指向不同的伺服器,方法都可以自己想,而不一定是 google 別人的經驗。但在自己思考的時候,有 5 個方向可以作為思考的起點:
- 改善硬體
- 使用快取
- 引入(分散式)叢集
- 引入非同步
- 優化程式碼
這五個方向,依據的也是前人的經驗。但排除掉具體的細節,可能更有利於你自己發揮。
後面的文章中我們就講一講這五大方向上的一些細節。