全網最權威:LVS高性能原因揭祕

如果你看過我的一些文章,你應該知道,我一般不會把知識點給你直接列出來;
這樣的文章網上有一大把,你大可不必來我這看;
如果你要看我的文章,那麼,就請你做好思考的準備,跟着我的思路,去一點一點,把這麼一個知識的歷程,把它研究透徹,你會受益匪淺。

OSI網絡分層

我們都應該知道,有 OSI 網絡分層模型這麼一個概念。此外,由於我們學的是軟件工程學,爲什麼有一個工程這倆字在裏面,那就說明了不是純學術上的一個研究,是有具體的工程去做的。
那麼就涉及到分層解耦這麼一個思想,任何一個層,只需要關心它這一層的事,而不用去管其它層具體是怎麼做的。那麼,對於每一層,就只需要專注於這一層的實現、細節,那麼與之帶來的,就是效率和安全上的提高,以及可插拔式的方便。

就比如開發人員就只需要去關注應用層的事,物理層就只要去管物理層的事,不論物理層的傳輸如何,光纖也好、WiFi 也好、4G、5G,這些對於其它層來說都是透明的,不論如何改動,升級,只要接口不變,那麼其它層級,就無須改動。
七層模型

當然,基於方便,業內也將七層模型簡化成了五層模型,也就是將繪畫層、表示層、應用層合併,共同組成了應用層。

這時候,如果基於這五層模型,那麼客戶端所發送一個消息給服務端,那麼首先要肯定的是,不是我們的應用程序,或者一個瀏覽器直接請求服務器,而是一個應用程序,如瀏覽器,在應用層給出一個請求,遞交給下一層,也就是傳輸層,然後傳輸層按照傳輸層的協議,做自己應該做的事,然後交給下一層,網絡層,然後再到數據鏈路層,最後通過物理層的 WiFi、光纖、4G 等等物理介質,將數據送達服務端,
然後,服務端的物理層,數據鏈路層,一直往上到達應用層,服務器應用程序處理完請求,然後返回響應,同樣將響應數據丟到下一層,一直到物理層傳輸到客戶端然後到應用層。
五層模型
不要覺得我文不對題,我爲什麼要先說這個一定有原因,如果你學過網絡,那你一定知道,對於每一層,都有自己對應的協議;
那什麼是協議?可以看做一種規範,就是必須去遵守的一種東西。

假設你和一個外國人要對話,那麼你說中文,他卻說英語,日語,韓語,拉丁語,法語……一堆聽不懂的,那你們怎麼對話?怎麼交流?
所以,必須統一一種語言去交流。

那麼對於計算機的網絡世界,每一層應該怎麼去收發數據,都有協議必須去遵守,那麼數據傳輸,每一層才能理解,另一臺計算機發過來的數據,代表了什麼含義。

應用層

那我們現在就要開始探討,既然我們平時的訪問,都是基於應用層,然後往服務端去訪問,然後服務端的應用層程序來進行處理,
那麼,應用層都是使用什麼協議?
對於我們生活中每天都用的,比如你拿瀏覽器去訪問網站,那用的是 HTTP 協議,你連接你的虛擬機,那用的是 SSH 協議,你要文件傳輸,那走的是 FTP 協議,你要發郵件,那就是 SMTP 協議。
就比如我們 web 應用的應用層協議 HTTP,我簡單舉個例子:

現在,假設我要把百度的主頁給請求到:
我先在 /proc/$$/fd 當前進程打開一個文件描述符,
然後 ll,我們可以看到,任何一個進程都會有 0、1、2,輸入,輸出,還有報錯:
fd
然後,我給出了一個路徑 /dev/tcp/www.baidu.com/80,這在 Linux 當中就是一個磁盤路徑對吧;
而且指定了文件描述符 8,<> 兩個方向表示了輸入輸出兩個方向,既可以輸入,也可以輸出;
回車之後我們就可以看到,其實這個 8,實際上就是指向了一個 socket 套接字;
這就是 Linux 的一切接文件:
看似打開了一個文件,實際上是創建了一個套接字;
這時候,就意味着,和百度的連接就建立好了。
TCP
現在,這個連接是傳輸控制層的事對不對,也就是操作系統在傳輸控制層已經用 TCP 和百度建立起了一個連接;
這時候,我們需要模仿的是什麼,就是應用層的協議對不對?
這時,訪問百度,那就是 HTTP 協議對不對?
所以我們要做的,就是發送,符合 HTTP 協議規範的數據,這樣,百度才能處理接收我們的請求。

我們都知道在 Linux 當中,有命令重定向這麼一個概念,此時,我們輸入:
echo -e ‘GET / HTTP/1.1\n’ >& 8
就意味着,我們把 GET / HTTP/1.1\n 這麼一行請求,重定向到文件描述符 8,
此時,文件描述符 8,正代表着這一個 socket 套接字,
而 echo,代表着打印的意思,就相當於我們的 System.out.print() 這麼一個方法,
那就意味着,我們打印的話,重定向到了我們的百度:
百度
這時回車,就意味着這行請求已經發出去了對不對,那麼百度接收到我們請求,就會發送回響應,到我們的文件描述符 8 裏面;
這時,我們只要去讀我們的文件描述符 8,就可以看到百度對我們的響應了。
百度響應
剛纔我們看到的,就是基於 HTTP 的應用層協議,說白了就是一堆字符串對不對。
不過剛剛我們也說了,HTTP 是基於七層模型的應用層協議,也就是最頂層的;
我們的發送的數據,不過是基於字符串的,要有一種規範的格式,不管是亂輸錯了什麼內容,那麼請求、響應的解析就都不會成功。

那遵循了 HTTP 協議之後,那麼接下來,纔是進入操作系統內核態的傳輸控制層協議,去進一步,來把我們的請求,也就是一堆字符,發送給服務端,
以及,接收服務端返回的響應。

傳輸控制層

所以對於 HTTP 來說,我們只需要發送遵守協議的數據即可,然後,數據就會被丟入傳輸控制層,就會經歷耳熟能詳的 TCP 三次握手。
對於 TCP 三次握手大家應該都明白吧,如果不明白的話我簡單描述一下;
(這裏只是簡單描述,如果要了解詳細些,可以看我之前的博客)
TCP三次握手四次揮手詳解

就是,我們的客戶端,首先會想服務端發送一次 TCP 建立連接的請求,然後服務端響應應答,然後客戶端確認應答,TCP 連接就建立成功了。
當然,這麼描述比較抽象,我們可以先想一想,爲什麼 TCP 建立連接要三次握手,兩次不行嗎?
那麼我們就得先明白,這三次握手的目的是什麼!

首先,一端(通常是客戶端)先發起一次連接請求,意思是高速服務端,我要和你建立連接啦;
然後,服務端要響應客戶端,我收到你的請求啦;
不過,由於連接肯定是雙向互通的,所以肯定也得有服務端也說,我也要和你建立連接纔行;
然後客戶端再響應給服務端,我收到你的請求啦。

這樣,一次完整的 TCP 連接才能建立。
要是隻有兩次,就無法完成雙方同時都能發出請求和應答。
但是乍一看,怎麼有 4 步,不是說好三次握手嗎?

你要是仔細看看,就會發現,第二步和第三步,實際上是可以合二爲一的;
服務端響應客戶端的同時,不也可以同時告訴客戶端,我也要和你建立連接嗎?

就相當於你要去拜訪一戶人家:
你先說:在嗎?我要去你家啦;
然後他迴應:在啊,你來吧(這句話就既迴應了在嗎,又同時告訴了你請來吧);
然後,你就可以回覆說,那我來了哦。

這樣,傳輸控制層,就只需要關心,數據之間要以如何的方式去發送和接收。

所以實際上,對應於 TCP 三次握手,傳輸控制層要做的只是:
讓自己下一層發送一個握手的數據包;
然後就等着下一層,把握手的包一點點發到服務端,等應答包再傳回來,丟回給傳輸層自己;
然後再讓下一層再發送第三次握手的包,就建立起了 TCP 三次握手。

也就是,我們的傳輸控制層,對於 TCP 連接,只管着不斷給下一層丟數據包,
以及接收下面一層傳上來的數據包,
來判斷,是否是連接建立,是否已經分手,是否網絡擁塞,是否需要重傳等等等等……
也就是隻負責數據的發放與接收,
而數據在網絡中的傳輸過程,則不聞不問,全由下面的層級來做。

四層負載

不過說到傳輸控制層,那就得順便提一下,從傳輸控制層開始,就都是屬於內核空間的了。
也就是說,我們的應用程序,是跑在我們的應用層用戶空間的;
而往下的傳輸控制層,一直到物理層,則都是在內核空間的。
所以,這也就涉及到了用戶態內核態的切換。

所以,我們的應用程序,實際上也是不能夠參與和管理數據的傳輸控制的;
應用層所能做的,就是讓我們的操作系統內核,去發送和接收數據,至於數據到底怎麼發送傳輸,要怎麼處理,什麼時候響應,應用程序完全做不了主,全部由操作系統來決定。

所以實際上,應用程序要發送數據,也都是往操作系統指定的一塊內存空間,將數據寫入,然後操作系統驅動硬件,將數據代爲發送;
而數據接收的時候,我們的應用程序,也是無法直接接收的,數據只有通過網卡,被傳入計算機指定的一處數據緩衝區存着,然後由應用程序來把它取走。

那麼,要理解爲什麼會出現四層的負載均衡,那麼就得明白,爲什麼其它負載會慢?

我們都知道,Nginx 也是常見的負載均衡軟件,而且性能很高,當初設計出來就是爲了解決 c10k 問題。
然而,Nginx 與 四層負載一比,其負載效率就又差了一個臺階。

首先,我們都知道的一點,那就是 Nginx 是一個軟件,是一個基於應用層跑在用戶態的一個軟件;
那麼就對應上了之前我說的兩點:

  • 首先,由於是應用層的負載均衡軟件,它每次接收到請求,都需要讀出用戶請求,分析請求,然後按照配置的邏輯進行負載;
  • 其次,由於是用戶態的應用軟件,那麼就涉及到了用戶態和內核態的切換,這是一個大的開銷;
  • 再有,由於是處於應用層,所以必須完成傳輸層,那麼就必須建立起三次握手,這又是一個大的開銷。

既然知道了 Nginx 爲什麼慢,那麼我們就可以想到辦法,去解決這些問題。

  • 既然 Nginx 在應用層,需要分析用戶請求進行負載,
    那麼,就採取降層級的方式,不考慮用戶發的是什麼,直接去往後端服務器負載;
  • 這樣,降了層級之後,就不會處於用戶態,又節省了用戶態和內核態切換的開銷;
  • 此外,由於 Nginx 要建立起三次握手,數據包首先就要來回傳遞 3 次,
    那麼,就選擇,不在負載處握手,而是直接將請求丟到後端真實服務器,讓真實服務器與用戶三次握手。

快速已經可以由此體現出來了,
它在收到用戶的數據包的時候,什麼都沒做,只是看一下端口號,確定是不是要轉發給後端服務器的,然後就“譁”地一下直接丟給後面,
所以是到 4 層,卻沒握手,所以又不到 4 層的這麼一個負載均衡。
四層負載均衡
不過這也同時帶來了一點注意點,那就是後端的真實服務器,它必須是鏡像。
就是對於用戶來說,不管訪問了哪一個服務器,它都必須是無感知的,看起來必須是一樣的。
(你訪問網站的時候,總不會刷新一下就感覺變了一個網站吧)

它不能像 Nginx 那樣,可以根據請求頭,去判斷應該負載給哪一個服務器,去定向負載,
基於四層的負載均衡看不到請求頭,所以它只能無視用戶的請求,直接往後邊仍。
鏡像

網絡層/IP層

那麼雖然理清了數據是如何被髮送的,但是,基於應用層和傳輸控制層,還無法決定數據要如何在網絡中傳輸,
傳輸控制層,只是負責,按照協議,不斷將數據,丟給下一層發送。

那麼,數據如何一步一步,從我們的客戶端,一直跳啊跳啊跳,到服務端那裏,就得看我們的網絡層。

我們可以先看一下機器上的網絡配置:
cat /etc/sysconfig/network-scripts/ifcfg-ens32
後面的 if,就是 interface,cfg 就是 config,就是接口配置,然後就會顯示如下信息:
我們一般配置網絡,都有 4 個維度:

  • 一個是計算機的 IP 地址,比如這裏的 192.168.177.204
  • 一個是掩碼,255.255.255.0
  • 還有網關:192.168.177.2
  • 以及 DNS 域名解析的地址
    在這裏插入圖片描述
    想來這 4 個維度,大部分都是知道的,不過它們又分別代表了什麼意思:
  • 首先我們要通信對不對,那麼我們的網絡中就得有一個來描述自己的東西,別人才能通過這個名字找到它對不對,這就是 ip 地址。
  • 還有 NDS,大家都知道,是一個用來解釋域名的這麼一個地址
  • 那麼,什麼又是掩碼,我們經常看到的 3 個 255 一個 0 又是什麼?
  • 以及,網關又是什麼?
    我來詳細解釋

首先要明白,這個世界有網絡沒錯,但是有一個很重要的概念叫互聯網。

我先來做一個假設,假如你要給一個張三送一封信,你在信封上,什麼也不寫,就寫一個張三收,那麼假如有一個快遞員,他膽敢幫你送這封信,
那麼他有可能窮其一生,都沒法幫你把這封信送到張三手裏,
因爲他不知道張三這個人,在哪,哪個省,哪個市,哪個村等等,那他就得一個一個地方去遍歷,每個人都去問一遍,纔有肯能找到這個人。

ip 地址就是這麼一個道理。
ip 地址,它又叫點分字節,我們平常看到的,就是 192.168.1.103 等等,每個 ip,都有 3 個點,分割成了 4 段數字,每個數字,都是一個字節;
一個字節,不就是有 8 個二進制位,也就都是 0 ~ 255 的大小。

而子網掩碼,就是拿來,和 ip 地址,按位做與運算,最後得到的,就是網絡號。

所以 ip 地址,就相當於前面所說的,那封信的 國家.省市.街道.然後就是門牌號,
通過 ip 地址,就能夠從網絡中,找到對應的計算機。

就拿我上面的 ip 來說,192.168.177.204,和 255.255.255.0,按位做與運算,
255 代表的 8 位二進制就是全 1 對不對,那麼不管誰和它做與運算,那麼結果就都是自身,
而 0 代表的 8 進制,就是 8 個 0,那麼無論誰和它做與運算,得到的結果就都是 0。

從而,我的虛擬機的網絡號,就是 192.168.177.0,
204,就是主機位,這臺機器,就是這個網絡的第 204 號。

瞭解了這個之後,我們就還得知道,計算機中還存着一張表,叫路由表。
我們可以看到,這張表有目標、網關、掩碼,還有 Iface網卡:
路由表
那麼,這代表什麼意思?
所以還得從 TCP/IP 開始講起。

假設,我們現在,要找一個唯一的 ip 標識的主機,那麼最先想到的方法就是:
把所有的 ip 的結點位置,全部記錄下來;
然後,根據這麼一張互聯網 ip 結點的圖,去計算出它的最短路徑;
然後,跟着這條路徑,去傳輸我們的數據。

那麼,這就意味着,這個世界上的每一臺機器,都需要記錄整個世界的全量的結點信息;
而且,假設任意一個結點出現了變化,全世界所有的機器都得跟着改動自己的數據。

不僅如此,全世界的結點數量有多少,那肯定是浩瀚星海,假設要在這麼大的數據之下,做一個圖的路徑計算,那消耗的時間,一定是巨大的。
也就是計算路徑慢,雖然傳輸速度很快,但還是會造成一定的延時。

所以,最終是沒有采取這種方案的,
而採取的叫:下一跳機制

假設現在,一臺路由器,接入到一個網段中,然後,這個網段,又有其他的路由器,其他的路由器,又分別接入了其他不同的網段中。
在整個互聯網當中,就被分割成了到處一小塊一小塊,
然後,當有一段數據包到達,路由器,只需要去判斷,這個數據包,要發送給這個網段中的誰,也就是下一跳;
然後下一個路由器,再接着判斷,要扔給哪一個路由器去繼續發送。

這樣,每個路由器,就不用存儲全量的數據,它只需要知道,自己這個網段,有哪些其它的路由器,我應該把這個包扔給這個網段裏的誰,讓它去接着扔。
這樣,每個路由器所需要的計算就非常小,由於它只要負責一個小範圍的數據包發送,所以設計也會相比而言更加簡單。

現在明白了這個機遇下一跳的概念,我們就能理解,這個路由表,它到底是有什麼用途了;
它就記錄着,要訪問的那個 ip,它的下一跳,在哪裏。

上面的路由表,具體的字段就意味着:
我們在訪問目標 ip 的時候,拿着目標 ip,和 Genmask 掩碼做一次與運算,就能得到 Destination 目標網段;
如果和目標網段匹配,就走 Gateway 網關,就能去訪問目標主機。

不過,這裏面有兩行記錄,假設我們要訪問統一網段的另一臺主機,
比如:192.168.177.203
我們就會先拿 192.168.177.203 ip地址,去和 255.255.255.0 做與運算,得到 192.168.177.0 這個目標網段,然後就能走 Gateway 網關 0.0.0.0,在本地局域網內部,找到目標主機。

這個 0.0.0.0,表示的就是局域網,因爲只有網與網之間的通信,才需要下一跳這麼一個機制,
要是都在局域網之內了,還要什麼下一跳,直接找到那個地址去訪問就可以了。

但是,假設,我們訪問外網,6.6.6.6
那麼,先和掩碼 255.255.255.0 做與運算,得到 6.6.6.0 這個網段;
但是一比,和 192.168.177.0 這個網段不一樣,說明走不通;
然後走上面一行,6.6.6.6 和掩碼 0.0.0.0 做與運算,得到 0.0.0.0 這個網段,
然後和前面 0.0.0.0 這個目標網段一比較,發現相等;
這時候,就走網關,192.168.177.2,就代表通過我們路由器的網關,跑到外網去訪問了。

其實我們認真點觀察的話,也是可以看出,實際上,任何的 ip,和 0.0.0.0 去做與運算,得到的結果都是 0.0.0.0,也就是一定和目標網段 0.0.0.0 匹配,那麼,網關就會是 192.167.177.2,
也就是,除了 192.168.177.0 這個自身所在的網段,其他所有的 ip,都會從這個網關出去,也就是我們的默認網關 192.168.177.2,就會從路由器提供的這個網關出去,訪問外網。

所以,這樣的所有的數據包都會被扔給我們的路由器,然後我們的路由器,再根據自己的路由表,去找到要發送的下一個路由器,然後再傳遞給下一個路由器……

在這裏,由於我用的是虛擬機,所以網關,就是我的 VM 虛擬出來的一個連接我的 Windows 主機的一個網關。

現在瞭解了下一跳這麼一個概念,我就能拋出一個問題,怎麼才能訪問到下一跳,也就是我們的目標 ip 寫什麼纔對?
現在,假設我們訪問百度:
我們可以發現,百度的 ip 是 112.80.248.76
ping百度
那麼,根據上一個路由條目規則,我們的數據包就會匹配到 0.0.0.0 這一條,
所以,數據就要往我們的網關,192.168.177.2 去發送。

數據鏈路層

那麼,問題來了,要發送給網關,我們的目標 ip 寫什麼?

不急着回答,先做個假設

  • 假設目標 ip 寫網關 ip,那麼網關接收到數據包之後一看:好的,是發送給我的。
    那麼,它還會去給百度繼續發送嗎?
    明顯不會了,因爲目標 ip 就是這個網關,而不是百度。
  • 那麼,假設,目標 ip 要是不改怎麼辦?
    那麼它要怎樣才能把這個數據發送給網關?目前局域網裏面又沒有百度這個 ip 地址。
    所以因爲目標 ip 還是百度啊,怎麼才能轉發給網關呢?網關即使收到,又怎麼知道,是轉發給它的呢?

我們輸入 arp -a
可以看到,兩條 ip 地址,都指向了另一個地址,就是 mac 地址。
arp
我們知道 DNS 是域名到 ip 的解析,而 arp 就是在一個網段內,ip 到 mac 地址的解析。

所以,接下來,解決的就是鏈路層。
七層網絡模型,總不可能在網絡層,或者說 IP層,就全部解決了吧,不然還要下面兩層幹什麼。

所以,我們的 ip,就是負責,在茫茫人海中,找到傳輸路徑,找到下一跳;
而我們的數據鏈路層,負責的就是,定位下一跳的準確位置。
所以,在傳輸給下一跳的時候,不修改 ip 地址,而是通過具體的 mac 地址,將數據傳輸給下一跳。

數據,通過兩個 ip 端,不斷在網絡中跳啊跳,傳過去;
ip 就是負責,不斷找到傳輸的路徑;
而 mac 地址,則是定位具體的機器是哪一個。

ARP協議

既然瞭解了,數據是如何,通過七層模型,一層層傳遞發送出去的,那麼接下來,就給出一個實際的例子。

假設,現在有這麼兩個網段,192.168.1.0 和 192.168.2.0;
它們內部都有交換機,以及分別連接在了路由器的一端。
現在假設,192.168.1.6 這個機器,要給 192.168.2.9 這臺機器發送消息。
在這裏插入圖片描述
首先我們已經知道,先是應用層定好數據的格式,然後交給傳輸控制層,去發送數據包,然後,就是交給網絡層,也就是 ip 層,去尋找下一跳的路線,然後由數據鏈路層封裝 mac 地址從物理層發送。

不過,它要具體怎麼一步一步找到那個 ip 對應的主機呢?

之前,我提到過 arp 協議,在我們的機器上,會記錄着在同一個局域網內其他機器的 ip 對應的 mac 地址。
不過,我不知道你有沒有去想,這些地址是如何被機器記錄下來的?
當一臺機器新進入局域網之後,如何發現其它的機器?又如何被其它機器發現?

首先,一臺機器新啓動的時候,由於它在這個局域網還沒有身份,別人不知道它的存在,它也不知道別人的存在,
那麼,它要進入這個局域網,和它人建立通信應該要怎麼做?

這就涉及到 arp 協議了。
一個機器,當它接入局域網,就會發一個數據包,基於 arp 協議的;
但是,這就回到了那個先有雞還是先有蛋的問題:
要發送一個數據包,那就得有數據包的格式,就要有目標 mac 地址。

那這該怎麼辦?
實際上,arp 協議所指定的目標地址很特殊:
就是目標mac 地址:FFFFFFF(全F)
目標 IP:192.168.1.1

然後,數據包會被髮送到交換機。
不過交換機有一個機制,就是如果看到 mac 地址全 f,就會把這個數據包廣播。
廣播給所有除了發送方自己外的其它所有機器。
arp廣播
這個時候,192.168.1.8 就會收到一份數據,然後路由器也會收到一份廣播的數據。
然後,如果是局域網的另一臺機器收到了,就會比對目標 IP 地址,發現不是給自己的,就會丟棄數據包。
路由器收到了,發現這是一個 arp 數據包,就會響應這個數據包。

然後路由器此時返回的數據包,會封裝準確的目標 mac 地址,就是之前發送給它的那臺機器的 mac 地址,
然後發送給交換機;
不過此時,由於交換機之前已經收到過那臺機器發來的一個數據包,它可以記住哪臺機器對應着哪一個 mac 地址,所以此時路由器發出的數據包不會被廣播,而是直接準確地發送給之前的計算機。
然後,那臺計算機就習得了 IP 到 mac 地址的記錄。
路由器響應

所以,一臺計算機要和另一臺計算機通信,那麼數據包一定要封裝這三條信息:

  • 原端口號和目標端口號
  • 原 IP 地址和目標 IP 地址
  • 原 mac 地址和下一跳 mac 地址

現在,這臺機器已經習得了路由器的 mac 地址,所以,它在發送給 192.168.2.9 這個 IP 的機器時,
由於通過掩碼與運算,發現並不匹配自己的局域網段;
所以最終會匹配到 0.0.0.0,走它自己的網關,將自己的數據包發送出去;
於是,封裝自己 IP,目標 IP,以及下一跳的 mac 地址,也就是網關的 mac 地址。

數據包被髮送到網關,然後網關根據自己的路由條目,原IP 和 目標IP 都不變,僅僅修改下一跳的 mac 地址,然後將數據包發送給 192.168.2.9 這臺機器。
在這裏插入圖片描述

NAT模型

現在,假設你在 192.168.1.6 這臺機器上,假設你要訪問百度。
那麼,根據上面學過的知識,你就會知道,首先會根據本機的路由條目,匹配不到局域網內的主機地址,
於是,最終就會匹配到 0.0.0.0,走默認網關:192.168.1.1,然後前往 Internet。

那麼,就涉及到了一個概念,私有IP,和公網IP。
首先,我們的私有IP肯定不會出現在互聯網上,
你想一想,我們每個人回到家,家裏的那一片局域網都是 192.168.1.xxx,放到公網,那就是一大片一大片的相同IP地址,那互聯網就亂套了。

所以,如果私有IP出現在互聯網上,就會被直接丟棄。
而在互聯網上,每一個IP都是唯一公開的。
在這裏插入圖片描述
現在假設,你發送了一個請求給百度,那麼就會走默認網關,也就是走到你的路由器(家用路由器都是集交換機路由器功能爲一體的)。
你的IP是192.168.1.6,端口是 33333。百度假設是 112.80.242.76,端口是 80。

然後,路由器能把你的請求的原IP直接發送到互聯網上嗎?
肯定是不行的。

所以,這裏,路由器做了一次替換,將你的IP地址從 192.168.1.6 替換成了路由器接入公網的IP;
然後,你的請求就被路由器代爲發送,進入 Internet;
然後,一路到達百度,百度處理完請求,然後發送響應,原IP就是百度IP,目標IP就是路由器的 6.6.6.6,端口還是 33333;
然後,路由器接收到百度的響應,這時一看,是我剛剛發出去的請求;
於是,就把這個響應的目標IP再改回 192.168.1.6,然後再發給你。
發送百度
百度回覆

這很簡單,但是,現在有這麼一個問題:
由於你目前的互聯網有不止一臺主機,那麼,你們有沒有這麼一種可能,同時去訪問百度,
去百度一下,什麼不可告人的小祕密。

現在,假設有一件很碰巧的事,假如,你訪問時,使用的端口號是 33333;
另一個人,訪問時,使用的端口號,也是 33333;
那麼,路由器,替換你們的IP之後,就會產生兩個發送方一模一樣的數據包;
等到百度返回響應的時候,路由器該怎麼區分,哪個數據包是誰的?
在這裏插入圖片描述
所以,在路由器去替換原地址的時候,會把IP和端口一塊替換掉:
比如你的端口被替換成了 11111,他的端口被替換成了 22222,這樣,就保證了發送到公網,雖然IP相同,但是由於端口不同,也是可以區分出哪個包是誰發出來的,
這樣,等到百度返回響應,也可以將響應準確的歸還給你,不會出錯。
端口替換
端口替換

明白了這個,那下面我們就能繼續去思考,負載均衡器,如何將請求去分發給後面的真實服務器。

首先,客戶端(Client)有一個 IP,就叫 CIP;
負載均衡器有一個IP,就是虛擬IP,就叫做 VIP;
然後,真實服務器也有IP,就叫做 Real IP,RIP;
負載均衡器有一個分發的 IP,就叫做 DIP;
在這裏插入圖片描述

然後,客戶端給服務端發送請求,肯定是往這個 VIP 上去發,
因爲肯定,百度就算要加服務器,也不可能給所有的客戶去說:我們加服務器啦,現在我們有 … 一堆IP,請去訪問它們。
這是不合理的,所以,暴露給用戶的那一個IP就是固定的,後邊成百上千的服務器不斷去加,都要通過那一個入口,去抵達後邊的服務器。

所以,用戶在訪問的時候,數據包就是從 CIP -> VIP;
然後,負載均衡器,就能做一個轉換,讓數據包從 DIP -> RIP,然後就能發送到真實的服務器上。
NAT負載

DR模型

但是,你覺得這樣就已經可以了嗎?
是否還有什麼問題?

假設就是這樣,那麼當請求,經過 4 層負載均衡器,然後抵達真實服務端之後;
服務端返回的響應,應該怎樣再發回給我們的用戶?

剛纔經過我們的學習,應該能想到,直接把響應再打回給我們的 4 層負載,
然後通過它,再把所有的響應,發送給用戶。

但是,這樣會不會有什麼問題?
首先,我們可以確保的是,這麼做一定不會出錯。
但是,我們既然要用到這樣的四層負載,那就意味着,服務端的併發已經極高,連 Nginx 都已經無法應對,所以纔會應用到基於四層的負載均衡器。

但是,就拿我最上面訪問百度的例子,
我輸入的請求,只有一行,
然後百度,返回的響應,有我一整個頁面都顯示不下的內容。

也就是說,往往,服務端返回的響應,要佔據更大部分的帶寬,
所以,如果響應仍然走負載均衡器返回,
那麼勢必會極大增加負載均衡的網絡帶寬開銷,同時,對負載均衡器的性能影響,也是很大的。

那麼,我們要想,能不能,不讓響應再走負載均衡器返回,而是直接真實服務器直接將響應回覆給用戶呢?
直接返回
現在開始,回想我上面說的知識:
你可以發現,曾經,客戶是 CIP 發送給服務端的 VIP,
而現在呢,是 RIP 返回給客戶 CIP。

那麼,當客戶收到這個數據包,就會發現:
這玩意哪來的?我什麼時候給它發過東西了!
所以數據包就會被丟棄。。

所以,看來直接返回時不行的,但是,爲了追求效率,我們直接走負載均衡器返回,會降低性能,好像進入了一個進退兩難的境地。

那麼,現在假設我們要解決這個問題,那麼第一步要做的,就是讓返回的 IP 是 VIP 對不對,
因爲這樣,客戶端纔會認這個數據包,他纔會收下對不對。

那麼,既然如此,我們就必須給真實的服務器,也配一個 IP,叫 VIP,
這樣才能返回 VIP -> CIP 的數據包對不對。
VIP
但是,在一個局域網內,如果有多個相同的 IP,就會混亂對不對,
我們就得解決這個問題。

現在,我們假設,每個真實服務器都有一個 VIP,然後,它也用這個 VIP 給用戶返回響應。
但是,局域網內,其他機器都知道它有一個 RIP,但是都不知道它有一個 VIP,
也就是隻有它自己知道自己有一個 VIP。

這樣,由於只有負載均衡器暴露了一個 VIP,所以,客戶的所有請求,跳啊跳,最終都只會跳到這個負載上,
而不會亂跑,跑到其他機器上,就不會產生混亂;
然後負載,把請求給到後邊服務器,由於它有一個 VIP,所以能直接收下對不對;
然後因爲有這個 VIP,所以能直接返回個用戶,對不對!
VIP
要暴露一個地址很容易,但是難的是,讓它有了這個地址,但是還不能讓別人知道。

現在,假設,我們的負載,要直接把請求丟給後邊的真實服務器,
如果,它不把IP改成RIP,根據我們之前學習的理論知識,那麼請求是不是就會一直卡在這個負載這裏,一直丟不出去對不對?
從網絡層,就是這樣的。

但是,我們還記得,還有一個數據鏈路層對不對?
就是負責一跳與一跳之間的具體位置的請求的轉發,那就是有個 mac 地址對吧。

那麼,假設,我們的IP不變,但是,把數據鏈路層的 mac 地址,偷偷改成下一個真實服務器的 mac 地址,
那麼,這個數據包是不是最終也可以發送到我們的真實服務端!
然後服務端一看,mac 是自己的地址,然後 VIP,自己也有,那就會收下這個數據包。
在這裏插入圖片描述
然後,如果是這樣,那就能發現,這樣是不是更快?
因爲 mac 地址是二層的,是數據鏈路層,不像 NAT,要在 3-4 層。
它只需要修改一下 mac 地址,只在二層做了一下手腳,所以速度是極快的,數據包刷得一下就過去了。

這就是:mac 地址欺騙。

不過,mac 地址欺騙有一個小的侷限性:
就是必須在統一網段纔行對不對?
因爲 IP 沒變,只是變了一個 mac 地址,而 mac 地址,則是在每一個網段進行一跳與一跳之間傳輸的。

TUN

所以,要是負載與真實服務器不在同一個網段怎麼辦?
那麼就得用 NAT?
那不就是會受到出亡的帶寬,和負載性能的瓶頸制約?

所以,現在我們就要嘗試實現這麼一個技術,即使負載與真實服務器不在一個網段,也能將數據包丟到後端的服務器上。

現在,假設你想要去車站;
然後,由於你還不在車站,你又不認識去車站的路,你碰巧遇到了一輛滴滴;
你告訴司機,把我送去車站;
然後,司機就一股腦兒隨便把你載到一個車站,把你往那一丟,你就到了。

現在,你看完這個例子,不知道你能不能想出來我講這個例子可以說明一個什麼問題。

首先,你知道要去車站;
其次,你不知道怎麼去車站;
第三,你不用關心怎麼去車站。

這其實很像剛纔的 DR 模型,
CIP 要去 VIP,但是到了負載還不是真正的服務器,所以要去另一個隱藏的 VIP。
但是,由於那個 VIP 不知道怎麼去,所以,就需要一輛滴滴,幫忙載着這個數據包,把它送過去。
而具體怎麼過去,原數據包不用管。

所以,實際上,想要實現,
就只需要,在原有的 CIP -> VIP 的數據包,外面再包上一層 DIP -> RIP 的數據包;
這樣,數據包就會被完好送達,然後真實服務器只要把外面那一層包一拆,就能看到裏面的 CIP -> VIP,然後自己就會收下。
DR
既然解決了這個問題,下面我們繼續思考:

我們既然需要每一臺虛擬主機都配置一個 VIP,但是又不能讓它暴露,
那怎麼才能讓它不暴露呢?
因爲我們知道,協議是死的,只要這個網卡一接通,那它就一定會把自己暴露出去。

那看起來好像又沒有辦法了,
不過,下面帶出一個小知識,就是我們的計算機的網卡上,是可以配置多個 IP 地址的。

光這樣還不行,假設我們往物理網卡上配多個 IP 地址,那麼這個 IP 還是會被暴露,
不過還有一個點,我們平時也都在用,
就是:localhost

你永遠無法通過這個 localhost 訪問別人的主機,但是可以用來訪問自己的主機;
也就是,我們的計算機自帶一個虛擬網卡,這個網卡除了自己,沒有任何人可以訪問到。
localhost
那這樣,我們的每一個真實的物理主機,就只需要,在這塊虛擬網卡上配上 VIP,
那麼就可以做到,對外隱藏,對內可見。

然而實際上,我之前還隱藏了一個問題,不知道你有沒有發現,
就是當一個請求到達的時候,負載均衡器是直接把這個請求丟到後邊的真實服務器上,由真實的服務器,去和用戶建立三次握手。
但是,假設第一次,用戶的數據包丟給了服務器 1,然後服務器 1 給用戶返回了第二個握手包;
然後,用戶發來第三個握手包,負載均衡服務器要是把這個握手包丟給了其他服務器怎麼辦?
那這樣,用戶連三次握手建立連接都做不到。

所以,這臺負載均衡服務器就得具備一個能力,就是它必須得去偷窺這個IP,
什麼叫偷窺?
就是,只看,不動手。

所以,當第一個握手包發過來之後,負載均衡服務器就會去偷窺一下客戶的IP,然後偷偷拿小本本記錄下來,
然後,當這個用戶再一次發送數據過來時,它就能知道,該把這個包丟給誰,而不是不小心給了另一臺服務器。

你會發現我寫到這裏,整片文章沒有出現過三個字母,就是你想看的:
這個負載均衡,就叫 LVS

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