本文來自網易雲社區web
做者:劉超數據庫
上一節,咱們封裝了一個長長的網絡包,「大炮」準備完畢,開始發送。編程
發送的時候能夠說是重重關隘,從手機到移動網絡、互聯網,還要通過多個運營商才能到達數據中心,到了數據中心就進入第二個複雜的過程,從網關到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同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。
最終,當下單更新到分佈式數據庫中以後,整個下單過程纔算真正告一段落。
好了,通過了十個過程,下單終於成功了,你是否對這個過程瞭如指掌了呢?若是發現對哪些細節比較模糊,能夠回去看一下相應的章節,相信會有更加深刻的理解。
網易雲對象存儲服務 NOS(Netease Object Storage)是高性能、高可用、高可靠的雲端存儲服務。點擊可免費體驗。
相關閱讀:用雙十一的故事串起碎片的網絡協議(上)
相關文章:
【推薦】 瞄一眼,帶你走進SparkSQL的世界
【推薦】 SpringBoot入門(五)——自定義配置