高併發網站架構設計

最愛喝酸奶發表於2019-01-02

轉載自:http://blog.51cto.com/cloumn/detail/40?from=timeline

所謂高併發,指的是同一時間可以處理大量的WEB請求,這個指標用來衡量一個架構的體量和效能。這裡的大量如何評估呢?1000算不算?10000算不算?

對於中小型的站點來說,可能併發100多就很不錯了,但對於像淘寶這樣的大型站點,單憑一個介面呼叫的量就有可能達到百萬的併發。在雙11這樣的大型活動場景裡,淘寶的併發請求數都能達到上億次,這樣的體量無論是在國內還是在國際都是排在前列的。而本章節要講述的內容是如何設計一個可以承載巨量併發請求的架構。

要想設計一個高併發的架構,首先要搞清楚架構的分層,因為每一個層面都有可能造成影響高併發的瓶頸點。找到瓶頸點後,只要把瓶頸點解除,自然會提升整個架構的併發處理能力。我們先來看一個綜合分層的架構圖:

在這裡插入圖片描述


1. CDN

對於大型網站來說增加CDN這一層是非常有必要的,CDN(Content Delivery Network,內容分發網路),它屬於網路範疇的一個技術,它依靠部署在各個區域的邊緣伺服器,實現負載均衡、內容分發排程等功能。它使得使用者就近獲取內容,降低網路堵塞,提供使用者訪問響應速度。

來舉一個通俗點的例子:小明公司做了了一個針對全國使用者的業務,伺服器放在了北京,但是深圳使用者在訪問網站的時候非常卡頓,有時候甚至訪問不到。經排查,造成該問題的原因是深圳使用者所在網路到北京的機房延遲非常大。小明想到了一個辦法,他在深圳的某機房假設了一臺伺服器,把北京伺服器上的檔案傳輸到深圳的伺服器上,當深圳使用者訪問網站時,讓該使用者直接去訪問深圳的伺服器,而不是訪問北京的伺服器。同理,其他城市也效仿深圳假設了類似的伺服器,這樣全國各地的使用者訪問公司業務都很順暢了。

例子中的解決方案其實就是CDN實現原理,當然,真正的CDN技術要複雜得多,要考慮很多問題,比如邊緣伺服器的分佈、機房的網路、頻寬、伺服器的儲存、智慧DNS解析、邊緣伺服器到真實伺服器之間的網路優化、靜態和動態資源的區分、快取的優化、壓縮、SSL等等問題。關於這些細節技術我不做過多解釋,但希望大家能通過我的描述瞭解CDN在架構中存在的意義。

CDN是處於整個架構體系中最前端的一層,它是直接面對使用者的,CDN會把靜態的請求(圖片、js、css等)直接消化掉,然後把動態的請求往後傳遞。實際上,一個網站(比如,淘寶網)超過80%的請求都是靜態的請求,那也就意味如果前端架設了CDN,即使併發1億,也只有2000萬到了後端的WEB上。那麼你可能會問,CDN能支援8000萬的併發嗎?這個主要取決於CDN廠商的實力,如果他們搞10000個節點(即邊緣伺服器),每個節點上消化8000併發,如果搞10萬個節點,每個節點只需要消
化800個併發而已。然而,一臺普通的Nginx伺服器(配置2核CPU4G記憶體)輕鬆處理5萬個併發(前提是做過優化,並且處理的請求是靜態請求、或者只是轉發請求)。


2. 負載均衡

這一層,其實就是一個反向代理(或者叫做分發器),它的主要作用是把使用者的請求按照預設的演算法傳送給它後面的WEB伺服器。該層在實現上大致分為兩類:四層和七層(網路OSI七層模型),Nginx的負載均衡就屬於七層,而LVS屬於四層。從吞吐量上來分析,四層的負載均衡更有優勢。

所以,要想實現高併發,負載均衡這一層必須要使用四層技術,其中LVS就是一款不錯的開源負載均衡軟體。LVS有三種實現模式:

1)基於iptables的NAT模式

在這裡插入圖片描述

在這種模式下,負載均衡器上有設定iptables nat表的規則,實現了把使用者的請求資料包轉發到後端的Real Server(即WEB
Server)上,而且還要把WEB Server的響應資料傳遞給使用者,這樣負載均衡器很容易成為一個瓶頸,當併發量很大時,一定會
影響整個架構的效能。

2)DR模式

在這裡插入圖片描述

LVS的DR模式和NAT不一樣,負載均衡器只需要分發使用者的請求,而WEB Server的返回資料並不通過負載均衡器傳遞,資料直
接由WEB Server自己處理。這樣就解決了NAT模式的瓶頸問題。但是,DR模式有一個要求:負載均衡器和WEB Server必須在同一個內部網路(要求在相同的廣播域內),這是因為DR模式下,資料包的目的MAC地址被修改為了WEB Server的MAC地址。

3)IP Tunnel模式

在這裡插入圖片描述

LVS的IP Tunnel模式和DR模式類似,負載均衡器只需要分發使用者的請求,而WEB Server的返回資料並不通過負載均衡器傳遞,資料直接由WEB Server自己處理。這樣就解決了NAT模式的瓶頸問題。和DR模式不同的是,IP Tunnel模式不需要保證分發器和Real Server在同一個網路環境,因為這種模式下,它會把資料包的目的IP地址更改為Real Server的IP地址。這種模式,可以實現跨機房、跨地域的負載均衡。

對於以上三種模式來說,IP Tunnl模式更適合用在高併發的場景下。但有一點需要注意,這臺作為負載均衡器的伺服器無論是自身的網路卡效能,還是它所處的網路環境裡的網路裝置都有很高的要求。

可能你會有疑問,這臺負載均衡器終究只是一個入口,一臺機器頂多支撐10萬併發,對於1000萬、2000萬的併發怎麼實現?答案是:疊加!一臺10萬,100臺就是1000萬,200臺就是2000萬……

還有個問題,如何讓一個域名(如,www.google.com)訪問這200臺負載均衡器?請思考一下上一小節的CDN技術,它就可以讓一個域名指向到成千上萬的邊緣伺服器上。沒錯,智慧DNS解析可以把全國甚至世界各地的請求智慧地解析到最優的邊緣伺服器上。當然,DNS也可以不用智慧,大不了直接新增幾百條A記錄唄,最終也會把使用者的請求均衡地分發到這幾百個節點上。


3. WEB層

如果最前端使用了CDN,那麼在WEB這一層處理的請求絕大多數為動態的請求。什麼是動態的請求?除了靜態的就是動態的,那什麼是靜態的?前面提到過的圖片、js、css、html、音訊、視訊等等都屬於靜態資源,當然另外還有太多,大家可以參考第一篇文章《HTTP掃盲》的MIME Type。

再來說這個動態,你可以這樣理解:凡是涉及到資料庫存取操作的請求都屬於動態請求。比如,一個網站需要註冊使用者才可以正常訪問裡面的內容,你註冊的使用者資訊(使用者名稱、密碼、手機號、郵箱等)存入到了資料庫裡,每次你登入該網站,都需要到資料庫裡查詢使用者名稱和密碼,來驗證你輸入的是否是正確的。

如果到了WEB這一層全都是動態的請求的話,那麼併發量的多少主要取決於WEB層後端的DB層或者Cache層。也就是說要想提升WEB層伺服器的併發效能,必須首先要提升DB層或者Cache層的併發效能。

我們不妨來一個假設:要求架構能支撐1000萬併發(動態),假設單臺WEB Server支撐1000併發,則需要1萬臺伺服器。實際生產環境中,單臺機器支撐1000併發已經非常厲害啦,至少在我的運維生涯裡,單臺WEB Server最大動態併發量並沒有達到過這麼大。

我提供一組資料,你自然就可以估算出併發量。在這我拿PHP的應用舉例:一個PHP的網站,單個PHP-FPM程式耗費記憶體在2M-20M(假設耗費記憶體10M),1000個併發也就意味著同時有1000個PHP-FPM程式,耗費記憶體為1000*10M=10G,再加上留給系統1G記憶體,所以1000併發至少需要11G記憶體。

按照上面的估算,2000併發則需要21G記憶體,10000併發則需要101G記憶體,這個僅僅是理論值。實際上,併發量不僅跟記憶體有關係,跟CPU同樣也有關係。如果伺服器有4核CPU,則理論上僅僅支援4個程式同時佔用CPU計算,也就是說僅能支援4個併發。當然,CPU計算那麼快,程式會來回切換排隊佔用CPU,這樣能夠實現即使只有4核CPU,依然能夠支援幾百甚至上千的併發。但無論如何,CPU的核數越大,該伺服器能支撐的併發也就越大。

對於高併發的架構,WEB Server必然會做負載均衡叢集,單臺WEB Server的配置通常會選擇4核8G這樣的配置(這個配置,最好是根據自己業務的特性選擇合適的,畢竟現在大多企業都使用公有云或者私有云,伺服器的配置可以定製),然後由這樣的機器來組成一個大型叢集,最終實現高併發的需求。

在這裡插入圖片描述


4. Cache層

增加這一層的目的是為了減輕DB層的壓力,Cache層有一個特點:資料的讀寫發生在記憶體裡,跟磁碟並沒有關係。正是因為這個特點,保證了資料的讀寫速度非常快。假如沒有Cache層,併發1000萬的動態請求意味著這1000萬會直接透傳到DB層(如MySQL),1000萬的併發就會造成1000萬對磁碟的讀寫操作。我想大家都明白,磁碟的讀寫速度遠遠低於記憶體的讀寫速度,要想支撐1000萬併發讀寫是不現實的。

當然,Cache層主要針對讀操作,而且它僅僅是快取一部分DB層的熱資料(頻繁讀取的那部分資料)。舉一個下例子:有一次公司的某個業務臨時做了一個推廣活動,結果導致訪問量暴漲10倍,原本的伺服器架構並不能支撐這麼大的量,結果可想而知。當時,我們的解決方案是:把查詢量非常大的資料快取到Memcached裡面,然後在沒有增加硬體的情況下順利抗了過去。可見這一
個Cache層所起到的作用是多麼關鍵。

可以作為Cache的角色通常是NoSQL,如Memcached、Redis等。在第3章《常見WEB叢集架構》中我曾提到過Memcached,架構圖如下:

在這裡插入圖片描述

作為Cache角色時,Redis和Memcached用法基本一致。其實,拋開這個Cache角色,NoSQL也可以獨立作為DB層,這主要取決於業務邏輯是否支援拿NoSQL作為資料儲存引擎,畢竟NoSQL的資料結構和關係型資料庫比還是比較簡單的,有些複雜場景無法實現。但為了實現高併發,我們可以嘗試同時使用傳統的關係型資料庫和NoSQL資料庫儲存資料。

既然Memcached和Redis都可以作為Cache角色,那麼到底用哪一個可以支撐更大的併發量呢?其實這兩者各有千秋,不能盲目地下結論說哪個更快或者更好。得根據你的業務選擇適合的服務。由於Redis屬於單執行緒,故只能使用單核,而Memcached屬於多執行緒的,從而可以使用多核,所以在比較上,平均每一個核上Redis在儲存小資料時比Memcached效能更高。而在100k以
上的資料中,Memcached效能要高於Redis,雖然Redis最近也在儲存大資料的效能上進行優化,但是比起Memcached,還是稍有遜色。如果不考慮儲存資料大小,肯定Memcached效能更好,畢竟它是多執行緒的。

另外你需要了解,Memcached的資料只能存記憶體,不能存到磁碟,而Redis支援把記憶體的資料映象一份到磁碟上,而且還可以記錄日誌(通過這個日誌來獲取資料)。Memcached只能存簡單的K-V格式的資料,而Redis支援更多的資料型別(如,list、hash)。

無論你用哪一種作為Cache,我們都需要為其做一個高可用負載均衡叢集,這樣才可以滿足高併發的需求。

在這裡插入圖片描述


5. DB層

可以說DB層是整個架構體系中非常關鍵的一層,也是瓶頸所在。原因無他,只因它涉及到對磁碟的讀寫。所以,為了提升效能,對伺服器磁碟要求很高,至少是15000r/m的SAS硬碟而且需要做RAID10,如果選擇SSD盤更優。

最簡單暴力提升併發數量的辦法是伺服器的堆積(即,做負載均衡叢集),但DB層跟WEB層不一樣,它涉及到資料儲存到磁碟
裡,伺服器可以累加,但是磁碟在累加的同時,如何保證所有的伺服器能讀寫同一份資料?這是一個很大的問題,所以單純的伺服器堆積只適合小規模的業務,對於併發上千萬的業務並不適用。併發量大的站點,意味著資料量也是非常大的(如,TB級別),如果單個資料庫上TB,那一定是一個災難,無論是讀寫還是備份都將是極大的問題。

那如何解決這個問題呢?既然大了不行,那就將大的庫切割成小的庫即可。你可不要把這個切割單純地想象成切割檔案。我們可以從兩個維度來實現切割。

1)將業務劃分為多個業務模組

大型網站為了應對日益複雜的業務場景,通過使用分而治之的手段將整個網站業務分成不同的產品線,如大型購物網站就會將首頁、商鋪、訂單、買家、賣家、倉儲、物流、售後服務等拆分成不同的產品項,這樣資料庫自然也拆分為了多個資料庫,原來TB級的資料,變成了GB級。如果覺得還不夠細化,我們可以繼續把商鋪進一步拆分,比如個人類的、企業類的、明星類的、普通類的等等。總之,你可以根據業務特性想到幾百種拆分方法,最終一塊大蛋糕變成了幾十甚至幾百塊小蛋糕,吃起來就簡單多了。

在這裡插入圖片描述

2)將資料庫分庫分表

業務拆分是產品經理設計的,但是這個分庫分表只能是DBA操刀。如果一個幾千GB的大庫讀寫很慢的話,但分成1000個幾GB的小庫後,讀寫速度一定是有質的飛躍。同理,表也是可以像庫那樣劃分的。分庫分表需要藉助資料庫中介軟體來完成。比如MySQL分庫分表比較好的中介軟體MyCAT就不錯。

在這裡插入圖片描述

有了以上兩個拆分原則,無論多大的庫,我們都可以劃分為比較小的庫,這樣即使使用傳統的架構依然可以輕鬆應付。最終的DB層架構就成了蜂窩狀的一組一組的小單元,每一個單元獨立做高可用以及負載均衡叢集。

在這裡插入圖片描述


6. 訊息佇列層

一個大型的網站,一定少不了訊息佇列這一層。在前面第3章《常見WEB叢集架構》一文中就提到過它,它主要解決的問題是:解耦合、非同步處理、流量削峰等。以下三個應用場景曾在第3章出現過,也許你現在看會有更深層次的體會。

1)解耦合應用場景示例

使用者上傳圖片到伺服器,要求人臉識別系統識別該上傳圖片,傳統的做法是:使用者上傳圖片 → 服務接收到圖片開始識別圖片 → 系統判斷圖片是否合法 → 反饋給使用者是否成功。這個要涉及兩個系統:

在這裡插入圖片描述

而使用訊息佇列,流程會變成這樣:

在這裡插入圖片描述

使用者上傳圖片後,圖片上傳系統將圖片資訊寫入訊息佇列,直接返回成功;而人臉識別系統則定時從訊息佇列中取資料,完成對新增圖片的識別。

此時圖片上傳系統並不需要關心人臉識別系統是否對這些圖片資訊的處理、以及何時對這些圖片資訊進行處理。事實上,由於使用者並不需要立即知道人臉識別結果,人臉識別系統可以選擇不同的排程策略,按照閒時、忙時、正常時間,對佇列中的圖片資訊進行處理。

2)非同步處理應用場景示例

使用者到一個網站註冊賬號,系統需要傳送註冊郵件並驗證簡訊。傳統的處理流程如下:

在這裡插入圖片描述

這種方式下,需要最終傳送簡訊驗證後再返回給客戶端。

另外一種方式就是非同步處理,即註冊郵件和簡訊同時傳送,流程如下:

在這裡插入圖片描述

當使用者填寫完註冊資訊併成功寫入訊息佇列後,就可以反回成功的資訊給客戶端,從而客戶端不需要再等待系統發郵件和發簡訊,不僅客戶端不用等,而且處理客戶端請求的那個工作程式也不需要等(這個特性非常重要,它是實現高併發的一個重要手段),這個就是非同步處理的優勢。

3)流量削峰應用場景示例

很典型的應用就是購物網站秒殺活動,一般由於瞬時訪問量過大,伺服器接收過大,會導致流量暴增,相關係統無法處理請求甚至崩潰。而加入訊息佇列後,系統可以從訊息佇列中取資料,相當於訊息佇列做了一次緩衝。

在這裡插入圖片描述

該方法可以讓請求先入訊息佇列,而不是由業務處理系統直接處理,極大地減少了業務處理系統的壓力。另外佇列長度可以做限制,比如佇列長度為100,則該系統只能有100人可以秒殺到商品,排在100名後的使用者無法秒殺到商品,而返回活動已結束或商品已售完的資訊。

總之,訊息佇列的引入極大提升了整個架構的併發能力。從WEB層接收到動態的請求後,Cache層過濾掉一部分,然後請求逐一地傳送到DB層,在這個過程中,查詢時間很長的請求可以單獨摘出來,把它搞到訊息佇列裡,這樣WEB層和DB層只處理那種快速有結果的查詢,併發量自然很大。而訊息佇列會慢慢消化掉這些特殊的查詢,或許你有疑問,這種查詢慢的請求也很多怎麼
辦?不也同樣影響到併發量嗎?畢竟最終的查詢到了DB層。不要忘記訊息佇列本身就有削峰的能力,如果有大量的這種查詢,那麼就讓它們排好佇列,慢慢消化,總之不讓它們影響到DB層的正常查詢。

可以提供訊息佇列的服務有那麼多(RabitMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ、Beanstalk、Redis等等),到底選擇哪一種?最好是讓研發同事來定吧,只有研發團隊最瞭解自己程式碼的邏輯架構,適合自己的才是最好的。事實上,無論你用哪一種訊息佇列服務,它都不會成為整個架構的瓶頸點。當然,你最好做一個分散式的叢集,這樣能夠保證它的橫向擴容或者縮容。


7. 儲存層

關於儲存,目前的解決方案我歸類為以下幾種:

1)伺服器本地儲存

就是伺服器自身的磁碟,對於像DB層這樣關鍵的角色,我們通常會用高效能磁碟做RAID10。特點:方便維護、穩定、效能非常好、容量有限、擴容不方便。

2)專業的儲存裝置

主要有三類:NAS、SAN、DAS。

NAS:類似Linux系統做的NFS服務,它是建立在作業系統層面上的一種共享儲存解決方案,它是一種商業產品。NAS比較適合小規模網站。特點:容量大、擴容不方便、吞吐量一般(受網路環境影響)、穩定性好、成本高。

SAN:也是一種商業的共享儲存解決方案,支援普通網路或者光纖接入,比NAS更加底層。特點:容量大、擴容不方便、效能好(比NAS強很多)、穩定性好、成本高昂。

DAS:磁碟陣列,支援RAID,商業的儲存。特點:容量大、擴容不方便、不支援共享、效能好、穩定性好。

3)分散式共享儲存

隨著雲端計算、大資料技術的日益流行,分散式共享儲存技術越來越成熟,無論是商業的還是開源的都有不少優秀的解決方案。比如,開源的有HDFS、FastDFS、MFS、GlusterFS、Ceph、Swift等。這類儲存有一些共同特點:方便擴容、容量可以無限大、效能一般(網路會成為瓶頸)、成本低、穩定性好。

本節的儲存層我也歸類為三大類:WEB層面的儲存(比如儲存程式碼、圖片、js、css、視訊、音訊等靜態檔案)、日誌、DB層面的儲存(即資料庫的資料儲存)。

這三類儲存,最要命的是DB層的儲存,前面我也提到過DB層很關鍵,而決定DB層效能的因素中這個資料儲存(磁碟)效能起到決定性作用。解決方案我也提到了,就是“大變小,一變多,自己管自己”。正常情況下巨量的資料庫必然會使用大容量儲存裝置,這樣最終的結果是—慢!所以,我們需要分模組、分庫分表,最終單臺機器的本地磁碟就可以支撐這些巨量的資料,讀寫速度不會被網路等因素影響。

日誌類和WEB層靜態檔案的儲存可以選擇分散式共享儲存解決方案,因為這類的儲存不需要太高的吞吐量,它們所佔用的空間比較大,而且會越來越大。


總結

當你看完以上內容後,可能你的心中還是沒有一個完整的答案,所以這個總結很關鍵!

1)高併發網站一定會使用CDN,而且需要把靜態檔案儲存在邊緣伺服器上。

2)負載均衡一定要使用四層的,比如LVS,如果是LVS,選擇IP Tunnel模式。

3)WEB層把靜態的請求交給CDN處理,所以只處理動態的請求,要支援橫向擴容,可以方便地通過加機器來增加併發量。

4)增加Cache層,把熱資料搞到這一層,減少對DB層地壓力。對這一層做分散式叢集架構設計,方便擴容。

5)增加訊息佇列,實現解耦合、流量削峰,從而提升整個架構地併發能力。

6)DB層要通過拆分業務、分庫分表來實現大變小、一變多,對單獨模組做高可用負載均衡叢集,從而提升併發能力。

7)DB層的儲存使用本地磁碟,日誌類、靜態檔案類使用分散式檔案儲存。

相關文章