螞蟻變大象:淺談常規網站是如何從小變大的(二)

【 前一段時間寫了關於架構的總結,一共十篇,放在新浪博客上 : http://blog.sina.com.cn/zgwangbo001 ,微信關注:simplemain 今天放到51cto上】


微信請關注:

wKiom1bmGiOAvDQPAABM52vvCRU273.jpg


         【第四階段 第一次服務多機化】

         IO性能得到解決以後,我們可能就會面臨CPU瓶頸,即程序處理不過來了。那這個時候,最好的方式,就是優化程序。從整體架構和具體業務邏輯上去分析並做優化(可以藉助一些性能分析工具,如gprofxprof等)。根據之前的經驗,反射、正則表達式、字符串拼接、內存拷貝等是吃CPU的大戶,所以優化上可以重點考慮。通過性能優化,一般可以將性能提升30-40個百分點。

         如果性能優化後,都還不能滿足需求,那可以考慮更換更好的服務器。隨着時間的推移,硬件發展極爲迅速,且成本下降非常明顯。可以考慮雙核、4核等多核服務器。這樣是提升服務性能最快的方式。

         如果這樣都還不能滿足需求,那麼,恭喜你,說明你的網站流量已經有一定規模了。接下來,就是——多機。

         多機是小網站從技術架構上要邁出的第一步,也是一個質的飛躍的階段(想當年,我還利用JAVA提供的庫做了一個可多機的網站,不過一直沒用上,哈哈)。這個階段,最簡單的就是將WebServer、數據庫和cache這兩個東東移動到另外的機器上去,利用WebServer、數據庫和cache的網絡協議對其進行訪問。這樣就能將原本被他們佔用的cpu給我們的邏輯程序使用。

 

 

 

         通過這樣的拆分後,我們就邁出了多機的第一步。雖然看起來比較簡單和容易,但是這也是非常具有里程碑意義的。這樣的優化,可能會提升2030%左右的一個CPU idle。能夠使得我們的網站能夠經受更大的壓力。

 

――――――――――――― 休息一下的分割線 ―――――――――――――

 

         昨天在濟南玩了一天,去了傳說中的大明湖畔,雖然沒有看到夏雨荷,不過卻領略了春意的盎然,各種花爭奇鬥豔。看了趵突泉,對泉沒啥感覺,不過對泉裏的魚到是很深的印象——太肥了。

         另外還吃了一個比較有特點的早餐——甏肉米飯。早上起來吃碩大一塊五花肉。現在想起來都覺得有點膩。

         今天去青島,現在正在高速上。待會兒該我開車了,先上圖,必須有圖有真像。

 

 

 

 

 

 

 

 

 


 

――――――――――――― 技術繼續開始的分割線 ―――――――――――――

        

         【第五階段 邏輯程序的多機化】

 

         當我們的訪問量持續增加的時候,我們承受這成長的快樂和痛苦。流量刷刷往上漲,高興!但是呢,服務器叫苦不絕,我們的程序已經快到不能服務的邊緣了。怎麼辦?

         “快使用分佈式,哼哼哈嘿”J

         這個時候,我們就需要針對CPU瓶頸,將我們的程序分別放在多臺服務器上,讓他們同時提供服務,將用戶請求分攤到多個提供服務的機器。

         好,如果提供這樣的服務,我們會遇到什麼樣的問題?怎麼樣來解決?

         我們一個個的來分析:

         一、WebServer怎麼樣來分流用戶請求?That’s a good question!

         在考慮這個問題的時候,我們常見的WebServer早已給我們想好了解決方案。現在主流的WebServer幾乎都提供一個叫“Load Balance”的功能,翻譯過來就是負載均衡。我們可以在WebServer上配置一組機器列表,當請求來臨的時候,WebServer會根據一定的規則選取某一臺機器,將請求轉發到對應的邏輯處理程序上。

         有同學馬上就會問了,“一定的規則”是怎麼樣的規則?他能解決什麼樣的問題?如果機器宕了怎麼辦?……

         哈哈,這裏的一定規則就是“Load Balance”。負載均衡其實是分佈式計算和存儲中最基礎的算法,他的好壞,直接決定了服務的穩定性。我曾經設計和開發了一個負載均衡算法(現在正在大規模使用),有一次就因爲一個很小的case,導致服務大面積出現問題。

         負載均衡要解決的就是,上游程序如何在我們提供的一堆機器列表中,找到合適的機器來提供下游的服務。因此,我們可以將負載均衡分成兩個方向來看:第一,根據怎樣的規則來選機器;第二,符合規則的機器中,哪些是能提供服務的。對於第一個問題,我們通常使用隨機、輪詢、一致Hash等算法;對於第二個問題,我們要使用心跳、服務響應判定等方法檢測機器的健康狀態。關於負載均衡,要談的話點其實很多,我之前也寫過專門的一篇文章來介紹,後續有空了,我再詳細的描述。

         總之,分流的問題,我們可以通過負載均衡來比較輕鬆的解決了。

        

         二、用戶的session如何來同步?That’s a good question,TOO!

         雖然HTTP協議是一個無狀態的服務協議,但是,用戶的基本信息是要求能夠保證的。比如:登錄信息。原來在單機的時候,我們可以很簡單的使用類似setSession(“user”, ”XXX”)的函數來解決。當使用多機的時候,該怎麼樣來解決呢?

         其實這個問題也是當年困擾我很久的一個問題。如果用setSession,用戶在某一臺機器上登錄了,當下次請求來的時候,到其他機器了,就變成未登錄了。OhMy God

         Ok,讓我們一個個的來看:

         1、一臺機器登錄,其他機器不知道;

         2、用戶請求可能到多臺機器。

         對於第一個問題,如果我們在一臺機器的登錄信息讓其他機器知道,不就OK了嘛。或者,大家都在一臺機器上登錄,不就可以了嘛。     對於第二個問題,如果我們讓同一個用戶的請求,只落在同一臺機器上,不就OK了嘛。因此,我們可以提出三種解決方案:

         1、提供session同步機制;

         2、提供統一session服務;

         3、將同一用戶分流到同一機器。

         嗯,這三種方式,你會選哪個呢?如果是我,我就選最後一個。因爲我是一個懶蟲,我會選最簡單的一個,我信奉的一個原則既是:簡單粗暴有效!哈哈。在WebServer層使用一致Hash算法,按session_id進行分流(如果WebServer沒有提供該功能,可以簡單寫一個擴展,或者乾脆在WebServer後面做一個代理即可)。但是這種方案有一個致命的問題,當一臺機器宕機了以後,該機器上的所有用戶的session信息即會丟失,即使是做了磁盤備份,也會有一段時間出現session失效。

         好,那看看第一種方案。其實現在有一些框架已經提供了這樣的服務機制。比如Tomcat就提供session同步機制。利用自有的協議,將一臺機器上的session數據同步到其他的機器上。這樣就有一個問題,我需要在所有的機器上配置需要同步的機器,機器的耦合度瞬間就增加了,煩啊!而且,如果session量比較大的話,同步的實效性還是一個問題。

         那再來看看第二種方案,提供統一session服務。這個就是單獨再寫一個邏輯程序,來管理session, 並且以網絡服務的方式提供查詢和更新。對於這樣的一個階段的服務來講,顯得重了一些。因爲,我們如果這樣做,又會面臨一堆其他的問題,比如:這個服務是否 存在單點(一臺服務器,如果宕機服務就停止),使用什麼樣的協議來進行交互等等。這些問題在我們這個階段都還得不到解決。所以,看起來這個方案也不是很完 美。

         好吧,三種方案選其一,如果是你,你會選哪一種呢?或者還有更好的方案?如果我沒錢沒實力(傳說中的“屌絲”,哈哈),我就可能犧牲一下服務的穩定性,採用代價最低的。

 

         三、數據訪問同步問題。

         當多個請求同時到達,並且競爭同一資源的時候(比如:秒殺,或是定火車票),我們怎麼來解決呢?

         這個時候,因爲我們用到了單機數據庫,可以很好的利用數據庫 的“鎖”功能來解決這個問題。一般的數據庫都提供事務的功能。事務的級別分多種,比如可重複讀、串行化等,根據不同的業務需求,可能會選擇不同的事務級 別。我們可以在需要競爭的資源上加上鎖,用於同步資源的請求。但是,這個東東也不是萬能的,鎖會極大的影響效率,所以儘量的減少鎖的使用,並且已經使用鎖 的地方儘量的優化,並檢查是否可能出現死鎖。

         cache也有對應的解決方案,比如延遲刪除或者凍結時間等技術,就是讓資源在一段時間處於不可讀狀態,用戶直接從數據庫查詢,這樣保證數據的有效性。

 

         好了,上述三個問題,應該涵蓋了我們在這個階段遇到的大部分問題。那麼,我們現在可以把整體的架構圖畫出來看看。

 

 

         這樣的結構,足夠我們撐一段時間了,並且因爲邏輯程序的無狀態性,可以通過增加機器來擴展。而接下來我們要面對的,就是提交增長和查詢量增加帶來的存儲性能的瓶頸。

 

(今天就先寫到這兒,早點洗洗睡了,明天還要去爬嶗山。)

         【第六階段 讀寫分離,提升IO性能】

 

         好了,到現在這個階段,我們的單機數據庫可能已經逐步成爲瓶頸,數據庫出現比較嚴重的讀寫衝突(即:多個線程或進程因爲讀寫需要,爭搶磁盤,使得磁盤的磁頭不斷變換磁道或盤片,造成讀寫都很緩慢)。

         那我們針對這樣的問題,看看有哪些方法來解決。

         一、減少讀取量。我們所有的問題來源就是因爲讀寫量增加,所以看起來這個是最直接最根源的解決辦法。不過,用戶有那麼大請求量我們怎麼可能減少呢?其實, 對於越後端的系統,這是越可能的事情。我們可以在每一層都減少一部分往後傳輸的請求。具體到數據庫的話,我們可以考慮通過增加cache命中率,減少數據庫壓力。增加cache命中率有很多中方法,比如對業務訪問模式進行優化、多級cache模式、增加內存容量等等。業務模式的修改不是太好通用,因此這裏我們考慮如何通過增加內存容量來解決問題。

         對於單機,現在通用的cache服務一般都可以配置內存大小,這個只需要很簡單的配置即可。另外,我們也可以考慮多機cache的方案,通過增加機器來擴充內存容量。因此,我們就引入了分佈式cache,現在常用的cache(如:memcache),都帶有這樣的功能,支持多機cache服務,可以通過負載均衡算法,將請求分散到多臺不同的機器上,從而擴充內存容量。

         這裏要強調一點。在我們選擇均衡算法的時候,是有考慮的。這個時候,常常選賊一致Hash算法,將某一系列ID分配到固定的機器,這樣的話,能放的KV對基本等於所有機器相加。否則,如果不做這樣的分配,所有機器內存裏面的內容會有大量重複,內存並沒有很好的利用。另外,因爲採用一致Hash,即使一臺機器宕掉,也會比較均勻的分散到其他機器,不會造成瞬間其他機器cache大量失效或不命中的問題。

         二、減少寫入量。要減少用戶的提交,這個看起來是不太現實的。確實,我們要減少寫入的量似乎是很難的一件事。不過也不是完全不可能。這裏我們會提到一個思想:合併寫入。就是將有可能的寫入在內存裏進行合併,到一定時間或是一定條件後,再一起寫入。其實,在mysql等 存儲引擎內部,都是有這樣的機制的。打個比方,比如有一個邏輯是修改用戶購買物品的數量,每次用戶購買物品後,計數都加一。如果我們現在不是每次都去實時 寫磁盤,而是到一定的時間或一定次數後,再寫入,這樣就可以減少大量的寫入操作。但是,這裏需要考慮,如果服務器宕掉以後,內存數據的恢復問題(這一部分 會在後面來描述)。因此,如果想簡單的使用數據合併,最好是針對數據重要性不是很強的業務,即使丟掉一部分數據,也沒有關係。

         三、多機承擔請求,分散壓力。如果我們能將原來單機的服務,擴充成多機,這樣我們就能很好的將處理能力在一定限度內很好的擴展。那怎麼來做呢?其實有多種方法,我們常用的有數據同步和數據訂閱。

         數據同步,我們將所有的更新數據發送到一臺固定的數據服務器上,由數據服務程序處理後,通過日誌等方式,同步到其他機器的數據服務程序上。如下圖:

 

 

         這種結構的好處就是,我們的數據基本能保證最終一致性(即:數據可能在短暫時間內出現不一致,但最後的數據能達到一致),而且結構比較簡單,擴展性較好。另外,如果我們需要實時數據,我們可以通過查詢Master就行。但是,問題也比較明顯,如果負責處理和分發的機器掛掉了,我們就需要考慮單點備份和切換方案。

        

         數據訂閱,我們也可以通過這樣的方式來解決數據多機更新的問題。這種模式既是在存儲邏輯和數據系統前,增加一個叫做Message Queue(消息隊列,簡稱MQ)的東東,前端業務邏輯將數據直接提交到MQMQ將數據做排隊等操作,各個存儲系統訂閱自己想要的數據,然後讓MQ推送或自己拉取需要的數據。

 

 

         MQ不帶任何業務處理邏輯,他的作用就是數據轉發,將數據轉發給需要的系統。其他系統拿到數據後,自行處理。

         這樣的結構,好處是擴展比較方便,數據分發效率很高。但是問題也比較明顯,因爲處理邏輯分散在各個機器,所以數據的一致性難以得到保證。另外,因爲這種模 式看起來就是一個異步提交的模式,如果想得到同步的更新結果,要做很多附加的工作,成本很高且耦合度很大。還有,需要考慮MQ的單點備份和切換問題。

        

         因爲現在數據庫(如Mysql)基本帶有數據同步功能,因此我們在這個階段比較推薦數據同步的方法。至於第二種方式,其實是很好的一種思想,後續我們會有着重的提及。那再來看我們的架構,就應該演變成這樣的結構。

 

 


         到目前這個階段,我們基本上就實現了從單機到多機的轉變。數據的多機化,必然帶來的問題:一致性!這個是否有解決方案?這個時候我們需要引入一個著名的理論:CAP原理。

         CAP原理包含了三個要素:一致性(Consistency)、可用性(Availability)、分區容忍性(Partition tolerance)。三個要素中,最多隻能保證兩個要素同時滿足,不能三者兼顧。架構設計時,要根據業務需要進行取捨。比如,我們爲了保證可用性和分區容忍性,可能會捨去一致性。

         我們將數據分成多機,提高了系統的可用性,因此,一致性的保證很難做到強一致性。有可能做到最終一致性。這也是分佈式引入以後的煩惱。

         這樣的一個系統,也是後續我們分佈式架構的一個雛形,雖然比較粗糙,但是他還是比較簡單實用,對於一般中型網站,已經能很好的解決問題。

        

―――――――――――――開始休息的分割線 ―――――――――――――

        

         山東的城市真是不錯,昨天去了淄博,城市很乾淨,路很寬敞,人少,較之青島,感覺要舒服很多。去年騎車環這山東北面也去了煙臺、蓬萊、威海等幾個城市,整體感覺非常不錯。很喜歡山東這個地方。

         而且,山東人的實誠也給我留下了很好的印象。

         剛剛過了河北收費站,下午就要回到帝都了。

 

更多技術文章 請微信關注simplemain,或掃碼二維碼

wKiom1bj5lTR8BAfAAHI1JO9-Hs987.jpg

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