上一節,我們封裝了一個長長的網路包,“大炮”準備完畢,開始發送,
發送的時候可以說是重重關隘,從手機到移動網路、互聯網,還要經過多個運營商才能到達資料中心,到了資料中心就進入第二個復雜的程序,從網關到 VXLAN 隧道,到負載均衡,到 Controller 層、組合服務層、基礎服務層,最終才下單入庫,今天,我們就來看這最后一段程序,
7. 一座座城池一道道關,流控擁塞與重傳
網路包已經組合完畢,接下來我們來看,如何經過一道道城關,到達目標公網 IP,
對于手機來講,默認的網關在 PGW 上,在移動網路里面,從手機到 SGW,到 PGW 是有一條隧道的,在這條隧道里面,會將上面的這個包作為隧道的乘客協議放在里面,外面 SGW 和 PGW 在核心網機房的 IP 地址,網路包直到 PGW(PGW 是隧道的另一端)才將里面的包解出來,轉發到外部網路,
所以,從手機發送出來的時候,網路包的結構為:
- 源 MAC:手機也即 UE 的 MAC;
- 目標 MAC:網關 PGW 上面的隧道端點的 MAC;
- 源 IP:UE 的 IP 地址;
- 目標 IP:SLB 的公網 IP 地址,
進入隧道之后,要封裝外層的網路地址,因而網路包的格式為:
- 外層源 MAC:E-NodeB 的 MAC;
- 外層目標 MAC:SGW 的 MAC;
- 外層源 IP:E-NodeB 的 IP;
- 外層目標 IP:SGW 的 IP;
- 內層源 MAC:手機也即 UE 的 MAC;
- 內層目標 MAC:網關 PGW 上面的隧道端點的 MAC;
- 內層源 IP:UE 的 IP 地址;
- 內層目標 IP:SLB 的公網 IP 地址,
當隧道在 SGW 的時候,切換了一個隧道,會從 SGW 到 PGW 的隧道,因而網路包的格式為:
- 外層源 MAC:SGW 的 MAC;
- 外層目標 MAC:PGW 的 MAC;
- 外層源 IP:SGW 的 IP;
- 外層目標 IP:PGW 的 IP;
- 內層源 MAC:手機也即 UE 的 MAC;
- 內層目標 MAC:網關 PGW 上面的隧道端點的 MAC;
- 內層源 IP:UE 的 IP 地址;
- 內層目標 IP:SLB 的公網 IP 地址,
在 PGW 的隧道端點將包解出來,轉發出去的時候,一般在 PGW 出外部網路的路由器上,會部署 NAT 服務,將手機的 IP 地址轉換為公網 IP 地址,當請求回傳的時候,再 NAT 回來,
因而在 PGW 之后,相當于做了一次歐洲十國游型的轉發,網路包的格式為:
- 源 MAC:PGW 出口的 MAC;
- 目標 MAC:NAT 網關的 MAC;
- 源 IP:UE 的 IP 地址;
- 目標 IP:SLB 的公網 IP 地址,
在 NAT 網關,相當于做了一次玄奘西游型的轉發,網路包的格式變成:
- 源 MAC:NAT 網關的 MAC;
- 目標 MAC:A2 路由器的 MAC;
- 源 IP:UE 的公網 IP 地址;
- 目標 IP:SLB 的公網 IP 地址,

出了 NAT 網關,就從核心網到達了互聯網,在網路世界,每一個運營商的網路成為自治系統 AS,每個自治系統都有邊界路由器,通過它和外面的世界建立聯系,
對于云平臺來講,它可以被稱為 Multihomed AS,有多個連接連到其他的 AS,但是大多拒絕幫其他的 AS 傳輸包,例如一些大公司的網路,對于運營商來說,它可以被稱為 Transit AS,有多個連接連到其他的 AS,并且可以幫助其他的 AS 傳輸包,比如主干網,
如何從出口的運營商到達云平臺的邊界路由器?在路由器之間需要通過 BGP 協議實作,BGP 又分為兩類,eBGP 和 iBGP,自治系統之間、邊界路由器之間使用 eBGP 廣播路由,內部網路也需要訪問其他的自治系統,
邊界路由器如何將 BGP 學習到的路由匯入到內部網路呢?通過運行 iBGP,使內部的路由器能夠找到到達外網目的地最好的邊界路由器,
網站的 SLB 的公網 IP 地址早已經通過云平臺的邊界路由器,讓全網都知道了,于是這個下單的網路包選擇的下一跳是 A2,也即將 A2 的 MAC 地址放在目標 MAC 地址中,
到達 A2 之后,從路由表中找到下一跳是路由器 C1,于是將目標 MAC 換成 C1 的 MAC 地址,到達 C1 之后,找到下一跳是 C2,將目標 MAC 地址設定為 C2 的 MAC,到達 C2 后,找到下一跳是云平臺的邊界路由器,于是將目標 MAC 設定為邊界路由器的 MAC 地址,
你會發現,這一路,都是只換 MAC,不換目標 IP 地址,這就是所謂下一跳的概念,
在云平臺的邊界路由器,會將下單的包轉發進來,經過核心交換,匯聚交換,到達外網網關節點上的 SLB 的公網 IP 地址,
我們可以看到,手機到 SLB 的公網 IP,是一個端到端的連接,連接的程序發送了很多包,所有這些包,無論是 TCP 三次握手,還是 HTTPS 的密鑰交換,都是要走如此復雜的程序到達 SLB 的,當然每個包走的路徑不一定一致,
網路包走在這個復雜的道路上,很可能一不小心就丟了,怎么辦?這就需要借助 TCP 的機制重新發送,
既然 TCP 要對包進行重傳,就需要維護 Sequence Number,看哪些包到了,哪些沒到,哪些需要重傳,傳輸的速度應該控制到多少,這就是 TCP 的滑動視窗協議,

整個 TCP 的發送,一開始會協商一個 Sequence Number,從這個 Sequence Number 開始,每個包都有編號,滑動視窗將接收方的網路包分成四個部分:
- 已經接收,已經 ACK,已經交給應用層的包;
- 已經接收,已經 ACK,未發送給應用層;
- 已經接收,尚未發送 ACK;
- 未接收,尚有空閑的快取區域,
對于 TCP 層來講,每一個包都有 ACK,ACK 需要從 SLB 回復到手機端,將上面的那個程序反向來一遍,當然路徑不一定一致,可見 ACK 也不是那么輕松的事情,
如果發送方超過一定的時間沒有收到 ACK,就會重新發送,只有 TCP 層 ACK 過的包,才會發給應用層,并且只會發送一份,對于下單的場景,應用層是 HTTP 層,
你可能會問了,TCP 老是重復發送,會不會導致一個單下了兩遍?是否要求服務端實作冪等?從 TCP 的機制來看,是不會的,只有收不到 ACK 的包才會重復發,發到接收端,在視窗里面只保存一份,所以在同一個 TCP 連接中,不用擔心重傳導致二次下單,
但是 TCP 連接會因為某種原因斷了,例如手機信號不好,這個時候手機把所有的動作重新做一遍,建立一個新的 TCP 連接,在 HTTP 層呼叫兩次 RESTful API,這個時候可能會導致兩遍下單的情況,因而 RESTful API 需要實作冪等,
當 ACK 過的包發給應用層之后,TCP 層的快取就空了出來,這會導致上面圖中的大三角,也即接收方能夠容納的總快取,整體順時針滑動,小的三角形,也即接收方告知發送方的視窗總大小,也即還沒有完全確認收到的快取大小,如果把這些填滿了,就不能再發了,因為沒確認收到,所以一個都不能扔,
8. 從資料中心進網關,公網 NAT 成私網
包從手機端經歷千難萬險,終于到了 SLB 的公網 IP 所在的公網網口,由于匹配上了 MAC 地址和 IP 地址,因而將網路包收了進來,

在虛擬網關節點的外網網口上,會有一個 NAT 規則,將公網 IP 地址轉換為 VPC 里面的私網 IP 地址,這個私網 IP 地址就是 SLB 的 HAProxy 所在的虛擬機的私網 IP 地址,
當然為了承載比較大的吞吐量,虛擬網關節點會有多個,物理網路會將流量分發到不同的虛擬網關節點,同樣 HAProxy 也會是一個大的集群,虛擬網關會選擇某個負載均衡節點,將某個請求分發給它,負載均衡之后是 Controller 層,也是部署在虛擬機里面的,
當網路包里面的目標 IP 變成私有 IP 地址之后,虛擬路由會查找路由規則,將網路包從下方的私網網口發出來,這個時候包的格式為:
- 源 MAC:網關 MAC;
- 目標 MAC:HAProxy 虛擬機的 MAC;
- 源 IP:UE 的公網 IP;
- 目標 IP:HAProxy 虛擬機的私網 IP,
9. 進入隧道打標簽,RPC 遠程呼叫下單
在虛擬路由節點上,也會有 OVS,將網路包封裝在 VXLAN 隧道里面,VXLAN ID 就是給你的租戶創建 VPC 的時候分配的,包的格式為:
- 外層源 MAC:網關物理機 MAC;
- 外層目標 MAC:物理機 A 的 MAC;
- 外層源 IP:網關物理機 IP;
- 外層目標 IP:物理機 A 的 IP;
- 內層源 MAC:網關 MAC;
- 內層目標 MAC:HAProxy 虛擬機的 MAC;
- 內層源 IP:UE 的公網 IP;
- 內層目標 IP:HAProxy 虛擬機的私網 IP,
在物理機 A 上,OVS 會將包從 VXLAN 隧道里面解出來,發給 HAProxy 所在的虛擬機,HAProxy 所在的虛擬機發現 MAC 地址匹配,目標 IP 地址匹配,就根據 TCP 埠,將包發給 HAProxy 行程,因為 HAProxy 是在監聽這個 TCP 埠的,因而 HAProxy 就是這個 TCP 連接的服務端,客戶端是手機,對于 TCP 的連接狀態、滑動視窗等,都是在 HAProxy 上維護的,
在這里 HAProxy 是一個四層負載均衡,也即它只決議到 TCP 層,里面的 HTTP 協議它不關心,就將請求轉發給后端的多個 Controller 層的一個,
HAProxy 發出去的網路包就認為 HAProxy 是客戶端了,看不到手機端了,網路包格式如下:
- 源 MAC:HAProxy 所在虛擬機的 MAC;
- 目標 MAC:Controller 層所在虛擬機的 MAC;
- 源 IP:HAProxy 所在虛擬機的私網 IP;
- 目標 IP:Controller 層所在虛擬機的私網 IP,
當然這個包發出去之后,還是會被物理機上的 OVS 放入 VXLAN 隧道里面,網路包格式為:
- 外層源 MAC:物理機 A 的 MAC;
- 外層目標 MAC:物理機 B 的 MAC;
- 外層源 IP:物理機 A 的 IP;
- 外層目標 IP:物理機 B 的 IP;
- 內層源 MAC:HAProxy 所在虛擬機的 MAC;
- 內層目標 MAC:Controller 層所在虛擬機的 MAC;
- 內層源 IP:HAProxy 所在虛擬機的私網 IP;
- 內層目標 IP:Controller 層所在虛擬機的私網 IP,
在物理機 B 上,OVS 會將包從 VXLAN 隧道里面解出來,發給 Controller 層所在的虛擬機,Controller 層所在的虛擬機發現 MAC 地址匹配,目標 IP 地址匹配,就根據 TCP 埠,將包發給 Controller 層的行程,因為它在監聽這個 TCP 埠,
在 HAProxy 和 Controller 層之間,維護一個 TCP 的連接,
Controller 層收到包之后,它是關心 HTTP 里面是什么的,于是解開 HTTP 的包,發現是一個 POST 請求,內容是下單購買一個課程,
10. 下單扣減庫存優惠券,資料入庫回傳成功
下單是一個復雜的程序,因而往往在組合服務層會有一個專門管理下單的服務,Controller 層會通過 RPC 呼叫這個組合服務層,
假設我們使用的是 Dubbo,則 Controller 層需要讀取注冊中心,將下單服務的行程串列拿出來,選出一個來呼叫,
Dubbo 中默認的 RPC 協議是 Hessian2,Hessian2 將下單的遠程呼叫序列化為二進制進行傳輸,
Netty 是一個非阻塞的基于事件的網路傳輸框架,Controller 層和下單服務之間,使用了 Netty 的網路傳輸框架,有了 Netty,就不用自己撰寫復雜的異步 Socket 程式了,Netty 使用的方式,就是咱們講Socket 編程的時候,一個專案組支撐多個專案(IO 多路復用,從派人盯著到有事通知)這種方式,
Netty 還是作業在 Socket 這一層的,發送的網路包還是基于 TCP 的,在 TCP 的下層,還是需要封裝上 IP 頭和 MAC 頭,如果跨物理機通信,還是需要封裝的外層的 VXLAN 隧道里面,當然底層的這些封裝,Netty 都不感知,它只要做好它的異步通信即可,
在 Netty 的服務端,也即下單服務中,收到請求后,先用 Hessian2 的格式進行解壓縮,然后將請求分發到執行緒中進行處理,在執行緒中,會呼叫下單的業務邏輯,
下單的業務邏輯比較復雜,往往要呼叫基礎服務層里面的庫存服務、優惠券服務等,將多個服務呼叫完畢,才算下單成功,下單服務呼叫庫存服務和優惠券服務,也是通過 Dubbo 的框架,通過注冊中心拿到庫存服務和優惠券服務的串列,然后選一個呼叫,
呼叫的時候,統一使用 Hessian2 進行序列化,使用 Netty 進行傳輸,底層如果跨物理機,仍然需要通過 VXLAN 的封裝和解封裝,
咱們以庫存為例子的時候,講述過冪等的介面實作的問題,因為如果扣減庫存,僅僅是誰呼叫誰減一,這樣存在的問題是,如果扣減庫存因為一次呼叫失敗,而多次呼叫,這里指的不是 TCP 多次重試,而是應用層呼叫的多次重試,就會存在庫存扣減多次的情況,
這里常用的方法是,使用樂觀鎖(Compare and Set,簡稱 CAS),CAS 要考慮三個方面,當前的庫存數、預期原來的庫存數和版本,以及新的庫存數,在操作之前,查詢出原來的庫存數和版本,真正扣減庫存的時候,判斷如果當前庫存的值與預期原值和版本相匹配,則將庫存值更新為新值,否則不做任何操作,
這是一種基于狀態而非基于動作的設計,符合 RESTful 的架構設計原則,這樣的設計有利于高并發場景,當多個執行緒嘗試使用 CAS 同時更新同一個變數時,只有其中一個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試,
最終,當下單更新到分布式資料庫中之后,整個下單程序才算真正告一段落,
好了,經過了十個程序,下單終于成功了,你是否對這個程序了如指掌了呢?如果發現對哪些細節比較模糊,可以回去看一下相應的章節,相信會有更加深入的理解,
到此,我帶著你用下單程序把網路協議的知識都復習了一遍,授人以魚不如授人以漁,下一節,我將會帶你來搭建一個網路實驗環境,配合實驗來說明理論,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/211570.html
標籤:其他
