所謂高併發,指的是同一時間能夠處理大量的WEB請求,這個指標用來衡量一個架構的體量和性能。這裏的大量如何評估呢?1000算不算?10000算不算?css
對於中小型的站點來講,可能併發100多就很不錯了,但對於像淘寶這樣的大型站點,單憑一個接口調用的量就有可能達到百萬的併發。在雙11這樣的大型活動場景裏,淘寶的併發請求數都能達到上億次,這樣的體量不管是在國內仍是在國際都是排在前列的。而本章節要講述的內容是如何設計一個能夠承載巨量併發請求的架構。html
要想設計一個高併發的架構,首先要搞清楚架構的分層,由於每個層面都有可能形成影響高併發的瓶頸點。找到瓶頸點後,只要把瓶頸點解除,天然會提高整個架構的併發處理能力。咱們先來看一個綜合分層的架構圖:前端
對於大型網站來講增長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萬個併發(前提是作過優化,而且處理的請求是靜態請求、或者只是轉發請求)。緩存
這一層,其實就是一個反向代理(或者叫作分發器),它的主要做用是把用戶的請求按照預設的算法發送給它後面的WEB服務器。該層在實現上大體分爲兩類:四層和七層(網絡OSI七層模型),Nginx的負載均衡就屬於七層,而LVS屬於四層。從吞吐量上來分析,四層的負載均衡更有優點。服務器
因此,要想實現高併發,負載均衡這一層必需要使用四層技術,其中LVS就是一款不錯的開源負載均衡軟件。LVS有三種實現模式:網絡
在這種模式下,負載均衡器上有設置iptables nat表的規則,實現了把用戶的請求數據包轉發到後端的Real Server(即WEB
Server)上,並且還要把WEB Server的響應數據傳遞給用戶,這樣負載均衡器很容易成爲一個瓶頸,當併發量很大時,必定會
影響整個架構的性能。數據結構
LVS的DR模式和NAT不同,負載均衡器只須要分發用戶的請求,而WEB Server的返回數據並不經過負載均衡器傳遞,數據直
接由WEB Server本身處理。這樣就解決了NAT模式的瓶頸問題。可是,DR模式有一個要求:負載均衡器和WEB Server必須在同一個內部網絡(要求在相同的廣播域內),這是由於DR模式下,數據包的目的MAC地址被修改成了WEB Server的MAC地址。
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記錄唄,最終也會把用戶的請求均衡地分發到這幾百個節點上。
若是最前端使用了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這樣的配置(這個配置,最好是根據本身業務的特性選擇合適的,畢竟如今大多企業都使用公有云或者私有云,服務器的配置能夠定製),而後由這樣的機器來組成一個大型集羣,最終實現高併發的需求。
增長這一層的目的是爲了減輕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,咱們都須要爲其作一個高可用負載均衡集羣,這樣才能夠知足高併發的需求。
能夠說DB層是整個架構體系中很是關鍵的一層,也是瓶頸所在。緣由無他,只因它涉及到對磁盤的讀寫。因此,爲了提高性能,對服務器磁盤要求很高,至少是15000r/m的SAS硬盤並且須要作RAID10,若是選擇SSD盤更優。
最簡單暴力提高併發數量的辦法是服務器的堆積(即,作負載均衡集羣),但DB層跟WEB層不同,它涉及到數據存儲到磁盤
裏,服務器能夠累加,可是磁盤在累加的同時,如何保證全部的服務器能讀寫同一份數據?這是一個很大的問題,因此單純的服務器堆積只適合小規模的業務,對於併發上千萬的業務並不適用。併發量大的站點,意味着數據量也是很是大的(如,TB級別),若是單個數據庫上TB,那必定是一個災難,不管是讀寫仍是備份都將是極大的問題。
那如何解決這個問題呢?既然大了不行,那就將大的庫切割成小的庫便可。你可不要把這個切割單純地想象成切割文件。咱們能夠從兩個維度來實現切割。
大型網站爲了應對日益複雜的業務場景,經過使用分而治之的手段將整個網站業務分紅不一樣的產品線,如大型購物網站就會將首頁、商鋪、訂單、買家、賣家、倉儲、物流、售後服務等拆分紅不一樣的產品項,這樣數據庫天然也拆分爲了多個數據庫,原來TB級的數據,變成了GB級。若是以爲還不夠細化,咱們能夠繼續把商鋪進一步拆分,好比我的類的、企業類的、明星類的、普通類的等等。總之,你能夠根據業務特性想到幾百種拆分方法,最終一塊大蛋糕變成了幾十甚至幾百塊小蛋糕,吃起來就簡單多了。
業務拆分是產品經理設計的,可是這個分庫分表只能是DBA操刀。若是一個幾千GB的大庫讀寫很慢的話,但分紅1000個幾GB的小庫後,讀寫速度必定是有質的飛躍。同理,表也是能夠像庫那樣劃分的。分庫分表須要藉助數據庫中間件來完成。好比MySQL分庫分表比較好的中間件MyCAT就不錯。
有了以上兩個拆分原則,不管多大的庫,咱們均可以劃分爲比較小的庫,這樣即便使用傳統的架構依然能夠輕鬆應付。最終的DB層架構就成了蜂窩狀的一組一組的小單元,每個單元獨立作高可用以及負載均衡集羣。
一個大型的網站,必定少不了消息隊列這一層。在前面第3章《常見WEB集羣架構》一文中就提到過它,它主要解決的問題是:解耦合、異步處理、流量削峯等。如下三個應用場景曾在第3章出現過,也許你如今看會有更深層次的體會。
用戶上傳圖片到服務器,要求人臉識別系統識別該上傳圖片,傳統的作法是:用戶上傳圖片 → 服務接收到圖片開始識別圖片 → 系統判斷圖片是否合法 → 反饋給用戶是否成功。這個要涉及兩個系統:
而使用消息隊列,流程會變成這樣:
用戶上傳圖片後,圖片上傳系統將圖片信息寫入消息隊列,直接返回成功;而人臉識別系統則定時從消息隊列中取數據,完成對新增圖片的識別。
此時圖片上傳系統並不須要關心人臉識別系統是否對這些圖片信息的處理、以及什麼時候對這些圖片信息進行處理。事實上,因爲用戶並不須要當即知道人臉識別結果,人臉識別系統能夠選擇不一樣的調度策略,按照閒時、忙時、正常時間,對隊列中的圖片信息進行處理。
用戶到一個網站註冊帳號,系統須要發送註冊郵件並驗證短信。傳統的處理流程以下:
這種方式下,須要最終發送短信驗證後再返回給客戶端。
另一種方式就是異步處理,即註冊郵件和短信同時發送,流程以下:
當用戶填寫完註冊信息併成功寫入消息隊列後,就能夠反回成功的信息給客戶端,從而客戶端不須要再等待系統發郵件和發短信,不只客戶端不用等,並且處理客戶端請求的那個工做進程也不須要等(這個特性很是重要,它是實現高併發的一個重要手段),這個就是異步處理的優點。
很典型的應用就是購物網站秒殺活動,通常因爲瞬時訪問量過大,服務器接收過大,會致使流量暴增,相關係統沒法處理請求甚至崩潰。而加入消息隊列後,系統能夠從消息隊列中取數據,至關於消息隊列作了一次緩衝。
該方法可讓請求先入消息隊列,而不是由業務處理系統直接處理,極大地減小了業務處理系統的壓力。另外隊列長度能夠作限制,好比隊列長度爲100,則該系統只能有100人能夠秒殺到商品,排在100名後的用戶沒法秒殺到商品,而返回活動已結束或商品已售完的信息。
總之,消息隊列的引入極大提高了整個架構的併發能力。從WEB層接收到動態的請求後,Cache層過濾掉一部分,而後請求逐一地發送到DB層,在這個過程當中,查詢時間很長的請求能夠單獨摘出來,把它搞到消息隊列裏,這樣WEB層和DB層只處理那種快速有結果的查詢,併發量天然很大。而消息隊列會慢慢消化掉這些特殊的查詢,或許你有疑問,這種查詢慢的請求也不少怎麼
辦?不也一樣影響到併發量嗎?畢竟最終的查詢到了DB層。不要忘記消息隊列自己就有削峯的能力,若是有大量的這種查詢,那麼就讓它們排好隊列,慢慢消化,總之不讓它們影響到DB層的正常查詢。
能夠提供消息隊列的服務有那麼多(RabitMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ、Beanstalk、Redis等等),到底選擇哪種?最好是讓研發同事來定吧,只有研發團隊最瞭解本身代碼的邏輯架構,適合本身的纔是最好的。事實上,不管你用哪種消息隊列服務,它都不會成爲整個架構的瓶頸點。固然,你最好作一個分佈式的集羣,這樣可以保證它的橫向擴容或者縮容。
關於存儲,目前的解決方案我歸類爲如下幾種:
就是服務器自身的磁盤,對於像DB層這樣關鍵的角色,咱們一般會用高性能磁盤作RAID10。特色:方便維護、穩定、性能很是好、容量有限、擴容不方便。
主要有三類:NAS、SAN、DAS。
NAS:相似Linux系統作的NFS服務,它是創建在操做系統層面上的一種共享存儲解決方案,它是一種商業產品。NAS比較適合小規模網站。特色:容量大、擴容不方便、吞吐量通常(受網絡環境影響)、穩定性好、成本高。
SAN:也是一種商業的共享存儲解決方案,支持普通網絡或者光纖接入,比NAS更加底層。特色:容量大、擴容不方便、性能好(比NAS強不少)、穩定性好、成本高昂。
DAS:磁盤陣列,支持RAID,商業的存儲。特色:容量大、擴容不方便、不支持共享、性能好、穩定性好。
隨着雲計算、大數據技術的日益流行,分佈式共享存儲技術愈來愈成熟,不管是商業的仍是開源的都有很多優秀的解決方案。好比,開源的有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層的存儲使用本地磁盤,日誌類、靜態文件類使用分佈式文件存儲。