高可用可伸縮架構實用經驗談

移動互聯網、雲計算和大數據的成熟和發展,讓更多的好想法得以在很短的時間內實現爲產品。此時,如果用戶需求抓得準,用戶數量將很可能獲得爆發式增長,而不需要像以往一樣需要精心運營幾年的時間。然而用戶數量的快速增長(尤其是短時間內的爆發式增長),通常會讓應用開發者有些吃不消,不得不面臨一些嚴峻的技術挑戰:如何避免突發的當機事件,如何避免服務的不可用狀態,如何在服務容量不足時,避免用戶體驗下降,等等。在系統構建之初就採用高可用和可伸縮架構,將能有效避免這些問題。

如何構建高可用和可伸縮架構呢?七牛雲存儲首席架構師李道兵在3月22的「開發者最佳實踐日」第十期沙龍活動上給出了自己的方案。他結合自己多年的實踐經驗,針對一些不太複雜的業務場景,從入口層、業務層、緩存層和數據庫層四個層面細緻講述瞭如何構建高可用和可伸縮系統。

如何實現高可用

入口層

入口層,通常指Nginx和Apache等層面的東西,負責應用(不管是Web應用還是移動應用)的服務入口。我們通常會將服務定位在一個IP,如果這個IP對應的服務器當機了,那麼用戶的訪問肯定會中斷。此時,可以用Keepalived來實現入口層的高可用。例如,機器A 的IP是 1.2.3.4,機器 B 的 IP 是 1.2.3.5, 那麼再申請一個 IP 1.2.3.6(稱爲跳IP), 平時綁定在機器A上,如果A當機,IP會自動綁定在機器B上;如果B當機,IP會自動綁定在機器A上。對於這種形式,我們將DNS綁定到心跳IP上,即可實現入口層的高可用。

但這個方案有一點小問題。第一,它的切換可能會有一到兩秒的中斷,也就是說,如果不是要求到非常嚴格的毫秒級就不會有問題。第二,對入口的機器會有些浪費,因爲買了兩臺機器的入口,可能就只有一臺機器用上。對一些長連接的應用可能會有一些中斷,這時候就需要客戶端做配合。簡單來說,對於比較普通的業務來說,這個方案就能解決一部分問題。

這裏要注意,Keepalived在使用上會有一些限制。

  • 兩臺機器必須在同一個網段,不是在同一個網段,沒有辦法實現互相搶IP。

  • 服務監聽,以往基本上都是在監聽內容,而對於心跳機來講,必須監聽在所有的端口上,我們就會配合IPtables,避免內網的服務不小心暴露在外。主要由於內網其實也有心跳需求,不能爲了要心跳,不小心將內網服務暴露在公開的網絡上,這樣會帶來安全的問題。

  • 服務器利用率下降,這時可以考慮做混合部署來改善這一點。
    那麼,兩臺機器,兩個公網IP,DNS上把域名同時定位到兩個IP,算高可用嗎?這完全不是高可用,因爲如果一臺機器當機,那麼就有一半左右的用戶無法訪問。

業務層

業務層通常是由PHP、JavaScript和Python等寫的邏輯代碼構成的,需要依賴於後臺數據庫及一些緩存層面的東西。如何實現業務層的高可用呢?最核心的就是,業務層不要有狀態,將狀態分散到緩存層和數據庫。目前大家通常喜歡將以下幾種數據放入業務層。

第一個是Session,即用戶登錄請求的數據,但好的做法是將Session放在數據庫裏,或者一個比較穩定的緩存中。

第二個是緩存,在訪問數據庫的一個查詢表時,如果這個查詢錶慢,就希望將這些結果暫時放到進程裏,下次再做查詢時就不用再訪問數據庫了。帶來的問題是,數據很難做到一致,因爲在做高可用時,就不是一臺機器了,也就是說業務成長不是隻有一份,各個業務層之間很難保持數據同步。

簡簡單單的原則就是沒有狀態。在業務層沒有狀態時,一臺業務層當掉了之後,Nginx/Apache會自動將所有的流量打到另外一臺業務層的服務器上。並且由於沒有狀態,兩者沒有任何差異,所以用戶完全感受不到。如果把Session放在業務層裏面的話,那麼面臨的問題是,這個用戶以前是登錄的,這個進程死掉後,用戶就會登出了。

友情提醒:有一段時間比較流行Cookie Session,就是將Session中的數據加密之後放在客戶的Cookie裏,然後保存到客戶端。這樣也能做到與服務端完全無狀態,但這裏面有很多坑,如果能繞過這些坑就可以這樣使用。第一個坑是怎麼保證加密的密鑰不泄露。 第二個坑是存放***,如何避免別人獲取到Session中的信息去不停地嘗試的密碼、驗證碼或者其他一些***手段。如果沒有好辦法解決這兩方面的問題,那麼Cookie Session儘量慎用,最好是將Session放在一個性能比較好的數據庫中。如果數據庫性能不行,那麼將Session放在緩存中也比放在Cookie裏要好一點。

緩存層

非常簡單的架構裏是沒有緩存這個概念的。但在訪問量上來之後,MySQL扛不住了,比如在Sata盤裏跑MySQL,QPS到達200、300甚至500時,MySQL的性能會大幅下降,這時就可以考慮用緩存層來擋住絕大部分服務請求,就可以提升系統整體的容量。

緩存層做高可用一個簡單的方法就是,將緩存層分得細一點兒。比如說,緩存層就一份的話,那麼緩存層當了以後,所有應用層的壓力就會往數據庫裏壓,數據庫扛不住的話,整個網站(或應用)就會隨之當掉。而如果緩存層分在四臺機器上的話,每臺只有四分之一,這臺機器當掉了以後,也只有總訪問量的四分之一會壓在數據庫上面,也許此時數據庫能扛住,網站就很穩定地等到緩存層重新起來。在實踐中,四分之一顯然是不夠的,我們會將它分得更細,以保證單臺緩存當機後數據庫還能撐得住即可。在中小規模下,緩存層和業務層可以混合部署,這樣可以節省機器。

數據庫層

在數據庫層面實現高可用,通常是在軟件層面來做。例如,MySQL有主從模式,還有主主模式都能滿足需求。MongoDB也有ReplicaSet的概念,基本都能滿足大家的需求。

總之,要想實現高可用,需要做到這幾點:入口層做心跳,業務層服務器無狀態,緩存層減小粒度,數據庫做一個主從模式。對於這種模式來講,我們做的高可用不需要太多服務器,這些東西都可以同時部署在兩臺服務器上。這時,兩臺服務器早期的高可用的一個需求就完全滿足了。任何一臺服務器當機用戶完全無感知。

如何實現可伸縮

入口層

在入口層實現伸縮性,可以通過直接鋪機器,然後DNS加IP來實現。但需要注意,儘管一個域名解析到十個IP沒有問題,但是很多瀏覽器客戶端只會使用前幾個IP,部分域名供應商對此有優化(如每次返回的IP順序隨機),只是這個優化效果不穩定。

推薦的做法是使用少量的Nginx機器作爲入口,業務服務器隱藏在內網(HTTP類型的業務這種方式居多)。另外,也可以在客戶端做一些調度(特別是非HTTP型的業務,如直播)。

業務層

業務層的伸縮性如何實現?與做高可用時的解決方案一樣,要實現業務層的伸縮性,保證無狀態是很好的手段。此外,加機器繼續水平部署即可。

緩存層

比較麻煩的是緩存層的伸縮性,最簡單粗暴的方式是什麼呢?趁着量比較低的時候,把整個緩存層全部當掉,比如以前四臺機器,那麼可以加到六臺機器。帶來的一個問題,以前的緩存會有失效,會有數據不一致的問題。這時,可以將緩存全部停掉,同時再慢慢地把這些緩存啓動起來,啓動起來之後,再等這些緩存慢慢預熱。當然這裏一個要求,你真的可以這麼幹。如果扛不住呢?取決於緩存類型,下面我們先可以將緩存的類型區分一下。

  • 強一致性緩存:無法接受從緩存拿到錯誤的數據 (比如用戶餘額,或者會被下游繼續緩存這種情形)

  • 弱一致性緩存:能接受在一段時間內從緩存拿到錯誤的數據 (比如微博的轉發數)。

  • 不變型緩存:緩存key對應的value不會變更 (比如從SHA1推出來的密碼, 或者其他複雜公式的計算結果)。

那什麼緩存類型伸縮性比較好呢?弱一致性和不變型緩存的擴容很方便,用一致性Hash即可;強一致性情況稍微複雜一些。這時要使用一致性Hash,而不能用簡單Hash。如果緩存從9臺擴容到10臺,簡單Hash 情況下90%的緩存會馬上失效,而一致性Hash情況下,只有10%的緩存會失效。

那麼,強一致性緩存會有什麼問題?第一個問題是,緩存客戶端的配置更新時間會有微小的差異,在這個時間窗內有可能會拿到過期的數據。第二個問題是,如果擴容之後裁撤節點,會拿到髒數據。比如 a 這個key之前在機器1,擴容後在機器2,數據更新了,但裁撤節點後key回到機器1,這時候就會有髒數據的問題。

要解決問題2比較簡單,要麼保持永不減少節點,要麼節點調整間隔大於數據的有效時間。問題1可以用這些方法來解決:兩套hash配置都更新到客戶端,但仍然使用舊配置;逐個客戶端改爲只有兩套hash結果一致的情況下會使用緩存,其餘情況從數據庫讀,但寫入緩存;逐個客戶端通知使用新配置。

數據庫

在數據庫層面實現伸縮,方法很多,文檔也很多,此處不做過多贅述。大致方法爲:水平拆分、垂直拆分和定期滾動。

總之,我們可以在入口層、業務層面、緩存層和數據庫層四個層面,使用剛纔介紹的方法和技術實現系統高可用和可伸縮性。具體爲:在入口層用心跳,用一些平衡部署的手段;在業務層做到服務無狀態;在緩存層,可以減小一些粒度,以方便實現高可用,使用一致性Hash將有助於實現緩存層的伸縮性;數據庫層的主從模式能解決高可用問題,拆分和滾動能解決可伸縮問題。

bVlbH1

本文中分享的這些技巧和方法,主要想幫助不太複雜的業務場景或者中小型應用快速搭建起高可用可伸縮的系統。關於如何構建高可用和可伸縮系統還有很多更爲細節的點和實踐經驗值得探討,望以後能與大家做更充分的交流。


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