用雙十一的故事串起碎片的網絡協議(下)

上一節,我們封裝了一個長長的網絡包,“大炮”準備完畢,開始發送

發送的時候可以說是重重關隘,從手機到移動網絡、互聯網,還要經過多個運營商才能到達數據中心,到了數據中心就進入第二個複雜的過程,從網關到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要考慮三個方面,當前的庫存數、預期原來的庫存數和版本,以及新的庫存數。在操作之前,查詢出原來的庫存數和版本,真正扣減庫存的時候,判斷如果當前庫存的值與預期原值和版本相匹配,則將庫存值更新爲新值,否則不做任何操作。

這是一種基於狀態而非基於動作的設計,符合REST的架構設計原則。這樣的設計有利於高併發場景。當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

最終,當下單更新到分佈式數據庫中之後,整個下單過程纔算真正告一段落。

好了,經過了十個過程,下單終於成功了,你是否對這個過程瞭如指掌了呢?如果發現對哪些細節比較模糊,可以回去看一下相應的章節,相信會有更加深入的理解。

到此,我帶着你用下單過程把網絡協議的知識都複習了一遍。授人以魚不如授人以漁。其餘兩篇內容可以通過下方鏈接查看:

另外,歡迎你在專欄留言和我討論網絡協議相關內容,點擊這裏免費試讀《趣談網絡協議》

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