你管這破玩意兒叫負載均衡?

相信大家都聽過這樣的一道經典面試題:「請說出在淘寶網輸入一個關鍵詞到最終展示網頁的整個流程,越詳細越好」

這個問題很難,涉及到 HTTP,TCP,網關,LVS 等一系列相關的概念及諸多協議的工作機制,如果你能掌握到這其中的每個知識點,那將極大地點亮你的技能樹, 對於網絡是如何運作也會了然於胸,即便不能完全掌握,但知道流量怎麼流轉的對你排查定位問題會大有幫助,我之前就利用這些知識定位到不少問題,爲了弄清楚整個流程,我查閱了很多資料,相信應該可以把這個問題講明白,不過寫着寫着發現篇幅實在太長,所以分爲上下兩篇來分別介紹一下,本篇先介紹流量在後端的的整體架構圖,下一篇會深入剖析各個細節點,如 LVS ,NAT 的工作細節等,這其中會涉及到交換機,路由器的工作機制等知識點,相信大家看了肯定有幫助

李大牛創業了,由於前期沒啥流量,所以他只部署了一臺 tomcat server,讓客戶端將請求直接打到這臺 server 上

這樣部署一開始也沒啥問題,因爲業務量不是很大,單機足以扛住,但後來李大牛的業務踩中了風口,業務迅猛發展,於是單機的性能逐漸遇到了瓶頸,而且由於只部署了一臺機器,這臺機器掛掉了業務也就跌零了,這可不行,所以爲了避免單機性能瓶頸與解決單點故障的隱患,李大牛決定多部署幾臺機器(假設爲三臺),這樣可以讓 client 隨機打向其中的一臺機器,這樣就算其中一臺機器掛了,另外的機器還存活,讓 client 打向其它沒有宕機的機器即可

現在問題來了,client 到底該打向這三臺機器的哪一臺呢,如果讓 client 來選擇肯定不合適,因爲如果讓 client 來選擇具體的 server,那麼它必須知道有哪幾臺 server,然後再用輪詢等方式隨機連接其中一臺機器,但如果其中某臺 server 宕機了,client 是無法提前感知到的,那麼很可能 client 會連接到這臺掛掉的 server 上,所以選擇哪臺機器來連接的工作最好放在 server 中,具體怎麼做呢,在架構設計中有個經典的共識:沒有什麼是加一層解決不了的,如果有那就再加一層,所以我們在 server 端再加一層,將其命名爲 LB(Load Balance,負載均衡),由 LB 統一接收 client 的請求,然後再由它來決定具體與哪一個 server 通信,一般業界普遍使用 Nginx 作爲 LB

採用這樣的架構設計總算支撐了業務的快速增長,但隨後不久李大牛發現這樣的架構有點問題:所有的流量都能打到  server 上,這顯然是有問題的,不太安全,那能不能在流量打到 server 前再做一層鑑權操作呢,鑑權通過了我們才讓它打到 server 上,我們把這一層叫做網關(爲了避免單點故障,網關也要以集羣的形式存在)

這樣的話所有的流量在打到  server 前都要經過網關這一層,鑑權通過後才把流量轉發到 server 中,否則就向 client 返回報錯信息,除了鑑權外,網關還起到風控(防止羊毛黨),協議轉換(比如將 HTTP 轉換成 Dubbo),流量控制等功能,以最大程度地保證轉發給 server 的流量是安全的,可控的。

這樣的設計持續了很長一段時間,但是後來李大牛發現這樣的設計其實還是有問題,不管是動態請求,還是靜態資源(如 js,css 文件)請求都打到 tomcat 了,這樣在流量大時會造成 tomcat 承受極大的壓力,其實對於靜態資源的處理 tomcat 不如 Nginx,tomcat 每次都要從磁盤加載文件比較影響性能,而 Nginx 有 proxy  cache 等功能可以極大提升對靜態資源的處理能力。

畫外音:所謂的 proxy cache 是指 nginx 從靜態資源服務器上獲取資源後會緩存在本地的內存 + 磁盤中,下次請求如果命中緩存就從 Nginx 本機的 Cache 中直接返回了

所以李大牛又作了如下優化:如果是動態請求,則經過 gateway 打到 tomcat,如果是 Nginx,則打到靜態資源服務器上

這就是我們所說的動靜分離,將靜態請求與動態請求分開,這樣 tomcat 就可以專注於處理其擅長的動態請求,而靜態資源由於利用到了 Nginx 的  proxy cache 等功能,後端的處理能力又上了一個臺階。

另外需要注意的是並不是所有的動態請求都需要經過網關,像我們的運營中心後臺由於是內部員工使用的,所以它的鑑權與網關的 api 鑑權並不相同,所以我們直接部署了兩臺運營中心的 server ,直接讓 Nginx 將運營中心的請求打到了這兩臺 server 上,繞過了網關。

當然爲了避免單點故障 Nginx 也需要部署至少兩臺機器,於是我們的架構變成了下面這樣,Nginx 部署兩臺,以主備的形式存在,備 Nginx 會通過 keepalived 機制(發送心跳包) 來及時感知到主 Nginx 的存活,發現宕機自己就頂上充當主 Nginx 的角色

看起來這樣的架構確實不錯,但要注意的是 Nginx 是七層(即應用 層)負載均衡器 ,這意味着如果它要轉發流量首先得和 client 建立一個 TCP 連接,並且轉發的時候也要與轉發到的上游 server 建立一個 TCP 連接,而我們知道建立 TCP 連接其實是需要耗費內存(TCP Socket,接收 / 發送緩存區等需要佔用內存)的,客戶端和上游服務器要發送數據都需要先發送暫存到到 Nginx 再經由另一端的 TCP 連接傳給對方。

所以 Nginx 的負載能力受限於機器 I/O,CPU 內存等一系列配置,一旦連接很多(比如達到百萬)的話,Nginx 抗負載能力就會急遽下降。

經過分析可知 Nginx 的負載能力較差主要是因爲它是七層負載均衡器必須要在上下游分別建立兩個 TCP 所致,那麼是否能設計一個類似路由器那樣的只負載轉發包但不需要建立連接的負載均衡器呢,這樣由於不需要建立連接,只負責轉發包,不需要維護額外的 TCP 連接,它的負載能力必然大大提升,於是四層負載均衡器 LVS 就誕生了,簡單對比下兩者的區別

可以看到  LVS 只是單純地轉發包,不需要和上下游建立連接即可轉發包,相比於 Nginx 它的抗負載能力強、性能高(能達到 F5 硬件的 60%),對內存和 cpu 資源消耗比較低

那麼四層負載均衡器是如何工作的呢

負載均衡設備在接收到第一個來自客戶端的 SYN 請求時,即通過負載均衡算法選擇一個最佳的服務器,並對報文中目標 IP 地址進行修改 (改爲後端服務器 IP ),直接轉發給該服務器。TCP 的連接建立,即三次握手是客戶端和服務器直接建立的,負載均衡設備只是起到一個類似路由器的轉發動作。在某些部署情況下,爲保證服務器回包可以正確返回給負載均衡設備,在轉發報文的同時可能還會對報文原來的源地址進行修改。

綜上所述,我們在 Nginx 上再加了一層 LVS,以讓它來承接我們的所有流量,當然爲了保證 LVS 的可用性,我們也採用主備的方式部署 LVS,另外採用這種架構如果 Nginx 容量不夠我們可以很方便地進行水平擴容,於是我們的架構改進如下:

當然只有一臺 LVS 的話在流量很大的情況下也是扛不住的,怎麼辦,多加幾臺啊,使用 DNS 負載均衡,在 DNS 服務器解析域名的時候隨機打到其中一臺 LVS 不就行了

通過這樣的方式終於可以讓流量穩定流轉了,有個點可能一些朋友會有疑問,下面我們一起來看看

既然 LVS 可以採用部署多臺的形式來避免單點故障,那 Nginx 也可以啊,而且 Nginx 在 1.9 之後也開始支持_四層負載_均衡了,所以貌似 LVS 不是很有必要?

如果不用 LVS 則架構圖是這樣的

通過部署多臺 Nginx 的方式在流量不是那麼大的時候確實是可行,但 LVS 是 Linux 的內核模塊,工作在內核態,而 Nginx 工作在用戶態,也相對比較重,所以在性能和穩定性上 Nginx 是不如 LVS 的,這就是爲什麼我們要採用 LVS + Nginx 的部署方式。

另外相信大家也注意到了,如果流量很大時,靜態資源應該部署在 CDN 上, CDN 會自動選擇離用戶最近的節點返回給用戶,所以我們最終的架構改進如下

總結

架構一定要結合業務的實際情況來設計,脫離業務談架構其實是耍流氓,可以看到上文每一個架構的衍化都與我們的業務發展息息相關,對於中小型流量沒有那麼大的公司,其實用 Nginx 作爲負載均衡足夠,在流量迅猛增長後則考慮使用 lvs+nginx,當然像美團這樣的巨量流量(數十 Gbps 的流量、上千萬的併發連接),lvs 也不管用了(實測雖然使用了 lvs 但依然出現了不少丟包的現象)所以它們開發出了自己的一套四層負載均衡器 MGW

另外看了本文相信大家對分層的概念應該有更透徹的理解,沒有什麼是分層解決不了的事,如果有,那就再多加一層,分層使每個模塊各司其職,功能解藕,而且方便擴展,大家很熟悉的 TCP/IP 就是個很好的例子,每層只管負責自己的事,至於下層是什麼實現的上層是不 care 的

以上就是本文的全部內容,希望大家看了有收穫 ^^,下一篇我們再繼續深入探究一個請求的往返鏈路,會深入剖析 LVS,交換機,路由器等的工作原理,敬請期待 ^^

大家好,我是坤哥,前獨角獸技術專家,現創業者,持續分享個人的成長收穫,關注我一定能提升你的視野,讓我們一起進階吧!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Rngy_zCopDWFHq4ZUo3m9w