Categories
程式開發

如何為從1到10萬用戶的應用程序,設計不同的擴展方案?


對於創業公司來說,有用戶註冊是好事情,但是當用戶從零擴展到成千上萬之後,Web應用程序又該如何支持呢?

通常來說,這種情況的解決方案要么是來自突然爆發的緊急事件,要么是系統出現瓶頸進行升級改造。雖然方式不同,但是我們也發現了,一個邊緣項目發展成高度可擴展項目,其升級方案是有一些普適的“公式”可以套用,本文以Graminsta為例,為大家介紹當用戶從1位發展到10萬,應用程序如何擴展?

1位用戶:1台機器

無論是網站還是移動應用,應用程序幾乎都包括這三個關鍵組件:API、數據庫和客戶端,其中數據庫用來存儲持久數據,API服務於數據及與其有關的請求,而客戶端負責將數據呈現給用戶。

在現代應用程序開發中,客戶端往往會被視為一個獨立於API的實體,這樣一來就可以更輕鬆地擴展應用程序了。

當剛開始構建應用程序時,可以讓這三個組件都運行在一個服務器上,類似於我們的開發環境,一位工程師在同一台計算機上運行數據庫、API和客戶端。

當然,理論上我們可以把它部署到雲上的單個DigitalOcean Droplet或AWS EC2實例上,如下所示:

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 1

但是,當我們的用戶未來不止1個的時候,其實剛開始就應該考慮是否要將數據層拆分出來。

10個用戶:拆分數據層

拆分數據層,並將其作為一個類似於Amazon的RDS或Digital Ocean的託管數據庫的託管服務。這樣做的話,雖然成本會比在一台機器上或EC2實例上自託管高一些,但是我們可以獲得很多現成且方便的東西,例如多區域冗餘、只讀副本、自動備份等等。

Graminsta 現在的系統如下所示:

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 2

100個用戶:拆分客戶端

當網站流量變得穩定之後,就到了拆分客戶端的時候了。

需要注意的是,拆分實體是構建可擴展應用程序的關鍵所在。當系統中的某一部分獲得了更多流量,那麼就應該把它拆分出來,根據其自身的特定流量模式來處理服務的擴展。這也是我會把客戶端和API看作是相互獨立的組件的原因,這樣,我們就可以輕鬆為多平台構建產品,例如web、移動web、iOS、Android、桌面應用、第三方服務等,它們都是使用相同API的客戶端。

現在,Graminsta的系統如下所示:

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 3

1000個用戶:負載均衡器

當新用戶越來越多,如果只有一個API實例可能滿意滿足所有的流量,這時我們需要更多的計算能力。

這時,負載均衡器該上場了,我們在API前面添加一個負載均衡器,它會把流量路由到該服務的一個實例上,我們就可以進行水平擴展(通過添加更多運行相同代碼的服務器來增加可以處理的請求數量)。

我們在web端和API前面添加了一個獨立的負載均衡器,這意味著我們擁有了多個運行API和web客戶端代碼的實例。該負載均衡器會把請求路由到任何一個流量最小的實例上。並且,我們還可以從中得到冗餘,當一個實例宕機(過載或崩潰)時,其他實例還可以繼續運行,響應傳入的請求,而不是整個系統宕機。

負載均衡器還支持自動擴展,在流量高峰時可以增加實例的數量,當流量低谷時,減少實例數量。借助負載均衡器,API層實際上可以無限擴展,如果請求增加,我們只需要不斷增加實例就可以了。

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 4

編者註:到目前為止,我們擁有的產品和PaaS公司(如Heroku或AWS的Elastic Beanstalk)提供的開箱即用產品非常類似。 Heroku把數據庫託管在單獨的主機上,用自動擴展來管理負載均衡器,並允許我們把API和web客戶端分開託管。對於早期初創企業來說,使用Heroku等服務來做項目是一個不錯的選擇,所有必需的、基本的東西都是開箱即用。

10000個用戶:CDN

對於Graminsta來說,處理和上傳圖像為服務器帶來了很大的負擔。所以,Graminsta選擇了使用雲存儲服務來託管靜態內容,例如圖像、視頻等(AWS的S3或Digital Ocean的Spaces),而API應該避免圖像處理和圖像等業務。

另外,使用雲存儲服務,我們還可以使用CDN,可以在遍布全球不同的數據中心自動緩存圖像。我們的主數據中心可能託管在

我們從雲存儲服務得到的另一樣東西是CDN(在AWS,這是一個被稱為Cloudfront的插件,但是很多雲存儲服務都以開箱即用的方式提供它)。 CDN將在遍布全球不同的數據中心自動緩存我們的圖像。

雖然我們的主數據中心可能託管在俄亥俄州,如果有人在日本對圖像發出了請求,那麼雲供應商就會進行複制,將其存儲在位於日本的數據中心,下一個請求該圖像的日本用戶就會很快收到圖像。

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 5

10萬個用戶:擴展數據層

負載均衡器在環境中添加了10個API實例,使得API的CPU和內存消耗都很低,CDN幫助我們解決了世界各地圖像請求的問題。但是現在,我們有一個問題需要解決,那就是請求延遲。

通過研究,我們發現數據庫CPU的消耗佔比達到了80%-90%,因此擴展數據層成為了當務之急。數據層的擴展是一件很棘手的事情,雖然對於服務無狀態請求的API服務器來說,只需要添加更多實例即可,但是對於大多數數據庫系統來說,卻不是這樣。

緩存

要從數據庫獲得更多信息的最簡單方法之一是給系統引入一個新的組件:緩存層。實現緩存最常用的方法是使用內存中的鍵值存儲(如Redis或Memcached),且大多數雲廠商都會提供數據庫服務的託管版本。

當該服務正在進行對數據庫相同信息的大量重複調用時,就是緩存大顯身手的時候了。當我們訪問數據庫一次時,緩存就會保存信息,之後再進行相同請求時,就不必再訪問數據庫了。

例如,如果有人想在Graminsta中訪問Mavid Mobrick的個人資料頁面時,我們把從數據庫中得到的結果,緩存在Redis中關鍵字 user:id下,到期時間為30秒。之後,每當有人訪問Mavid Mobrick的個人資料時,我們會首先查看Redis,如果存在相關資料,那就直接從Redis提供數據。

大多數緩存服務的另一個優點是,與數據庫相比,更容易擴展。 Redis有個內建的Redis集群(Redis Cluster)模式,用的是跟負載均衡器類似的方式,可以把我們的Redis緩存分佈到多台機器上 。

所有高度擴展的應用程序幾乎都充分利用了緩存的優勢,緩存是構建快速API不可或缺的部分,可以提供更好的查詢和更高效的代碼,如果沒有緩存,我們可能很難擴展到數百萬用戶的規模。

只讀副本

由於對數據庫的訪問相當多,因此我們需要在數據庫管理系統來添加只讀副本。借助上面提到的託管服務,只需要點擊一下就可以完成。只讀副本將和主數據庫保持一致,並且能夠用於SELECT語句。

如何為從1到10萬用戶的應用程序,設計不同的擴展方案? 6

未來展望

隨著應用的不斷擴展,我們會把重點放在拆分獨立擴展的服務。例如,如果我們使用了websockets,那麼會把websockets處理代碼抽取出來,放在新的實例上,同時安裝負載均衡器。該負載均衡器可以根據websocket連接打開或關閉的數量來上下擴展,與我們收到的HTTP請求數量無關。

如果未來還會遇到數據層的限制,我們就會對數據庫進行分區和分片。

我們會使用New Relic或Datadog等服務安裝監控程序,並通過監控程序發現比較慢的請求,改進它。同時,隨著擴展的不斷進行,我們希望能夠發現更多的瓶頸並解決它。

原文鏈接:

https://alexpareto.com/scalability/systems/2020/02/03/scaling-100k.html