移 動互聯網、雲計算和大數據的成熟和發展,讓更多的好想法得以在很短的時間內實現爲 產品。此時,若是用戶需求抓得準,用戶數量將極可能得到爆發式增加,而不須要像以往同樣須要精心運營幾年的時間。然而用戶數量的快速增加(尤爲是短期內 的爆發式增加),一般會讓應用開發者有些吃不消,不得不面臨一些嚴峻的技術挑戰:如何避免由於單臺機器當機致使服務不可用;如何避免在服務容量不足時,用 戶體驗降低,等等。在系統構建之初就採用高可用和可伸縮架構,將能有效避免這些問題。數據庫
如 何構建高可用可和可伸縮架構呢?七牛雲存儲首席架構師李道兵在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。
- 內 網服務也能夠作心跳,但須要注意的是,之前爲了安全咱們會把內網服務綁定在內網 IP上,避免出現安全問題。但爲了使用keepalived,必須監聽在全部IP上(若是監聽在心跳IP上,那麼機器沒有持有該IP時,服務沒法啓動), 簡單的方案是啓用 iptables, 避免內網服務被外網訪問。
- 服務器利用率降低,這時能夠考慮作混合部署來改善這一點。
比較常見的一個錯誤時,若是有兩臺機器,兩個公網IP,DNS上把域名同時定位到兩個IP,就以爲已經作了高可用了。這徹底不是高可用,由於若是一臺機器當機,那麼就有一半左右的用戶沒法訪問。cookie
業務層session
業務層一般是由PHP、Java、Python、Go等寫的邏輯代碼構成的,須要依賴於後臺數據庫及一些緩存層面的東西。如何實現業務層的高可用呢?最核心的就是,業務層不要有狀態,將狀態分散到緩存層和數據庫。目前你們一般喜歡將如下幾種數據放入業務層。架構
第一個是session,即用戶登陸相關的數據,但好的作法是將session放在數據庫裏,或者一個比較穩定的緩存系統中。
第二個是緩存,在訪問數據庫時,若是一個查詢很慢,就但願將這些結果暫時放到進程裏,下次再作查詢時就不用再訪問數據庫了。這種作法帶來的問題是,當業務層服務器不僅一臺時,數據很難作到一致,從緩存拿到的數據就多是錯誤的。
一 個簡單的原則就是業務層不要有狀態。在業務層沒有狀態時,一臺業務層服務器當掉了 以後,Nginx/Apache會自動將全部的請求打到另一臺業務層的服務器上。因爲沒有狀態,兩臺服務器沒有任何差別,因此用戶徹底感覺不到。若是把 session放在業務層裏面的話,那麼面臨的問題是,這個用戶之前是登陸在一臺機器上的,這個進程死掉後,用戶就會被登出了。
友 情提醒:有一段時間比較流行cookie session,就是將session中的數據加密以後放在客戶的cookie裏,而後下發到客戶端,這樣也能作到與服務端徹底無狀態。但這裏面有不少 坑,若是能繞過這些坑就能夠這樣使用。第一個坑是怎麼保證加密的密鑰不泄露,一旦泄露就意味着攻擊者能夠僞造任何人的身份。第二個坑是重放攻擊,如何避免 別人經過保存 cookie 去不停地嘗試的驗證碼,固然也還有其餘一些攻擊手段。若是沒有好辦法解決這兩方面的問題,那麼cookie session儘可能慎用。最好是將session放在一個性能比較好的數據庫中。若是數據庫性能不行,那麼將session放在緩存中也比放在 cookie裏要好一點。
緩存層
很是簡單的架構裏是沒有緩存這個概念的。但在訪問量上來以後,MySQL之類的數據庫扛不住了,好比在SATA盤裏跑MySQL,QPS到達200、300甚至500時,MySQL的性能會大幅降低,這時就能夠考慮用緩存層來擋住絕大部分服務請求,提高系統總體的容量。
緩 存層作高可用一個簡單的方法就是,將緩存層分得細一點兒。好比說,緩存層就一臺機 器的話,那麼這臺機器當了之後,全部應用層的壓力就會往數據庫裏壓,數據庫扛不住的話,整個網站(或應用)就會隨之當掉。而若是緩存層分在四臺機器上的 話,每臺只有四分之一,這臺機器當掉了之後,也只有總訪問量的四分之一會壓在數據庫上面,數據庫能扛住的話,網站就能很穩定地等到緩存層從新起來。在實踐 中,四分之一顯然是不夠的,咱們會將它分得更細,以保證單臺緩存當機後數據庫還能撐得住便可。在中小規模下,緩存層和業務層能夠混合部署,這樣能夠節省機 器。
數據庫層
在數據庫層面實現高可用,一般是在軟件層面來作。例如,MySQL有主從模式(Master-Slave),還有主主模式(Master-Master)都能知足需求。
MongoDB也有ReplicaSet的概念,基本都能知足你們的需求。
總 之,要想實現高可用,須要作到這幾點:入口層作心跳,業務層服務器無狀態,緩存層 減少粒度,數據庫作一個主從模式。對於這種模式來說,咱們作的高可用不須要太多服務器,這些東西均可以同時部署在兩臺服務器上。這時,兩臺服務器就能知足 早期的高可用需求了。任何一臺服務器當機用戶徹底無感知。
如何實現可伸縮
入口層
在入口層實現伸縮性,能夠經過直接水平擴機器,而後DNS加IP來實現。但須要注意,儘管一個域名解析到幾十個IP沒有問題,可是不少瀏覽器客戶端只會使用前幾個IP,部分域名供應商對此有優化(如每次返回的IP順序隨機),但這個優化效果不穩定。
推薦的作法是使用少許的Nginx機器做爲入口,業務服務器隱藏在內網(HTTP類型的業務這種方式居多)。另外,也能夠把全部IP下發到客戶端,而後在客戶端作一些調度(特別是非HTTP型的業務,如遊戲、直播)。
業務層
業務層的伸縮性如何實現?與作高可用時的解決方案同樣,要實現業務層的伸縮性,保證無狀態是很好的手段。此外,加機器繼續水平部署便可。
緩存層
比 較麻煩的是緩存層的伸縮性,最簡單粗暴的方式是什麼呢?趁着半夜量比較低的時候, 把整個緩存層所有下線,而後上線新的緩存層。新的緩存層啓動起來以後,再等這些緩存慢慢預熱。固然這裏一個要求,你的數據庫能抗住低估期的請求量。若是扛 不住呢?取決於緩存類型,下面咱們先能夠將緩存的類型區分一下。
- 強一致性緩存:沒法接受從緩存拿到錯誤的數據 (好比用戶餘額,或者會被下游繼續緩存這種情形)。
- 弱一致性緩存:能接受在一段時間內從緩存拿到錯誤的數據 (好比微博的轉發數)。
- 不變型緩存:緩存key對應的value不會變動 (好比從SHA1推出來的密碼, 或者其餘複雜公式的計算結果)。
那 什麼緩存類型伸縮性比較好呢?弱一致性和不變型緩存的擴容很方便,用一致性 Hash便可;強一致性狀況稍微複雜一些,稍後再講。使用一致性Hash,而不用簡單Hash的緣由是緩存的失效率。若是緩存從9臺擴容到10臺,簡單 Hash 狀況下90%的緩存會立刻失效,而若是使用一致性Hash狀況,只有10%的緩存會失效。
那 麼,強一致性緩存會有什麼問題?第一個問題是,緩存客戶端的配置更新時間會有微小 的差別,在這個時間窗內有可能會拿到過時的數據。第二個問題是,若是擴容以後再裁撤節點,會拿到髒數據。好比 a 這個key以前在機器1,擴容後在機器2,數據更新了,但裁撤節點後key回到機器1,這時候就會拿到髒數據。
要解決問題2比較簡單,要麼保持永不減小節點,要麼節點調整間隔大於數據的有效時間。問題1能夠用以下的步驟來解決:
1. 兩套hash配置都更新到客戶端,但仍然使用舊配置;
2. 逐個客戶端改成只有兩套hash結果一致的狀況下會使用緩存,其他狀況從數據庫讀,但寫入緩存;
3. 逐個客戶端通知使用新配置。
Memcache 設計得比較早,致使在伸縮性高可用方面的考慮得不太周到。Redis在這方面有很多改進,特別是 @ngaut 團隊基於 redis開發了 codis 這個軟件,一次性地解決了緩存層的絕大部分問題。推薦你們考察一下。
數據庫
在數據庫層面實現伸縮,方法不少,文檔也不少,此處不作過多贅述。大體方法爲:水平拆分、垂直拆分和按期滾動。
總 之,咱們能夠在入口層、業務層面、緩存層和數據庫層四個層面,使用剛纔介紹的方法 和技術實現系統高可用和可伸縮性。具體爲:在入口層用心跳來作到高可用,用平行部署來伸縮;在業務層作到服務無狀態;在緩存層,能夠減少一些粒度,以方便 實現高可用,使用一致性Hash將有助於實現緩存層的伸縮性;數據庫層的主從模式能解決高可用問題,拆分和滾動能解決可伸縮問題。
本文中分享的這些技巧和方法,主要想幫助不太複雜的業務場景或者中小型應用快速搭建起高可用可伸縮的系統。關於如何構建高可用和可伸縮系統還有不少更爲細節的點和實踐經驗值得探討,望之後能與你們作更充分的交流。