上一節,我們封裝了乙個長長的網路包,「大炮」準備完畢,開始傳送。
傳送的時候可以說是重重關隘,從手機到流動網路、網際網路,還要經過多個運營商才能到達資料中心,到了資料中心就進入第二個複雜的過程,從閘道器到vxlan隧道,到負載均衡,到controller層、組合服務層、基礎服務層,最終才下單入庫。今天,我們就來看這最後一段過程。
7.一座座城池一道道關,流控擁塞與重傳
網路包已經組合完畢,接下來我們來看,如何經過一道道城關,到達目標公網ip。
對於手機來講,預設的閘道器在pgw上。在流動網路裡面,從手機到sgw,到pgw是有一條隧道的。在這條隧道裡面,會將上面的這個包作為隧道的乘客協議放在裡面,外面sgw和pgw在核心網機房的ip位址。網路包直到pgw(pgw是隧道的另一端)才將裡面的包解出來,**到外部網路。
所以,從手機傳送出來的時候,網路包的結構為:
當隧道在sgw的時候,切換了乙個隧道,為從sgw到pgw的隧道,因而網路包的格式為:
在pgw的隧道端點將包解出來,**出去的時候,一般在pgw出外部網路的路由器上,會部署nat服務,將手機的ip位址轉換為公網ip位址,當請求返回的時候,再nat回來。
因而在pgw之後,相當於做了一次歐洲十國遊型的**,網路包的格式為:
在nat閘道器,相當於做了一次玄奘西遊型的**,網路包的格式變成:
出了nat閘道器,就從核心網到達了網際網路。在網路世界,每乙個運營商的網路成為自治系統as。每個自治系統都有邊界路由器,通過它和外面的世界建立聯絡。
對於雲平台來講,它可以被稱為multihomed as,有多個連線連到其他的as,但是大多拒絕幫其他的as傳輸包。例如一些大公司的網路。對於運營商來說,它可以被稱為transit as,有多個連線連到其他的as,並且可以幫助其他的as傳輸包,比如主幹網。
如何從出口的運營商到達雲平台的邊界路由器?在路由器之間需要通過bgp協議實現,bgp又分為兩類,ebgp和ibgp。自治系統間,邊界路由器之間使用ebgp廣播路由。內部網路也需要訪問其他的自治系統。
邊界路由器如何將bgp學習到的路由匯入到內部網路呢?通過執行ibgp,使內部的路由器能夠找到到達外網目的地最好的邊界路由器。
到達a2之後,從路由表中找到下一跳是路由器c1,於是將目標mac換成c1的mac位址。到達c1之後,找到下一跳是c2,將目標mac位址設定為c2的mac。到達c2後,找到下一跳是雲平台的邊界路由器,於是將目標mac設定為邊界路由器的mac位址。
在雲平台的邊界路由器,會將下單的包**進來,經過核心交換,匯聚交換,到達外網網關節點上的slb的公網ip位址。
我們可以看到,手機到slb的公網ip,是乙個端到端的連線,連線的過程傳送了很多包。所有這些包,無論是tcp三次握手,還是https的金鑰交換,都是要走如此複雜的過程到達slb的,當然每個包走的路徑不一定一致。
當網路包走在這個複雜的道路上,很可能一不小心就丟了,怎麼辦?這就需要借助tcp的機制重新傳送。
既然tcp要對包進行重傳,就需要維護乙個sequence number,看哪些包到了,哪些沒到,哪些需要重傳,傳輸的速度應該控制到多少,這就是tcp的滑動視窗協議。
整個tcp的傳送,一開始會協商乙個sequence number,從這個sequence number開始,每個包都有編號。滑動視窗將接收方的網路包分成四個部分:
對於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層,也是部署在虛擬機器裡面的。
9.進入隧道打標籤,rpc遠端呼叫下單
在虛擬路由節點上,也會有ovs,將網路包封裝在vxlan隧道裡面,vxlan id就是給你的租戶建立vpc的時候分配的。包的格式為:
在物理機a上,ovs會將包從vxlan隧道裡面解出來,發給haproxy所在的虛擬機器。haproxy所在的虛擬機器發現mac位址匹配,目標ip位址匹配,就根據tcp埠,將包發給haproxy程序,因為haproxy是在監聽這個tcp埠的。因而haproxy就是這個tcp連線的服務端,客戶端是手機。對於tcp的連線狀態,滑動視窗等,都是在haproxy上維護的。
在這裡haproxy是乙個四層負載均衡,也即他只解析到tcp層,裡面的http協議他不關心,就將請求**給後端的多個controller層的乙個。
haproxy發出去的網路包就認為haproxy是客戶端了,看不到手機端了。網路包格式如下:
當然這個包發出去之後,還是會被物理機上的ovs放入vxlan隧道裡面,網路包格式為:
在物理機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都不感知,它只要做好它的非同步通訊即可。
下單的業務邏輯比較複雜,往往要呼叫基礎服務層裡面的庫存服務、優惠券服務等,將多個服務呼叫完畢,才算下單成功。下單服務呼叫庫存服務和優惠券服務,也是通過dubbo的框架,通過註冊中心拿到庫存服務和優惠券服務的列表,然後選乙個呼叫。
呼叫的時候,統一使用hessian2進行序列化,使用netty進行傳輸,底層如果跨物理機,仍然需要通過vxlan的封裝和解封裝。
咱們以庫存為例子的時候,講述過冪等的介面實現的問題。因為如果扣減庫存,僅僅是誰呼叫誰減一。這樣存在的問題是,如果扣減庫存因為一次呼叫失敗,而多次呼叫,這裡指的不是tcp多次重試,而是應用層呼叫的多次重試,就會存在庫存扣減多次的情況。
這裡常用的方法是,使用樂觀鎖(compare and set,簡稱cas)。cas要考慮三個方面,當前的庫存數、預期原來的庫存數和版本,以及新的庫存數。在操作之前,查詢出原來的庫存數和版本,真正扣減庫存的時候,判斷如果當前庫存的值與預期原值和版本相匹配,則將庫存值更新為新值,否則不做任何操作。
這是一種基於狀態而非基於動作的設計,符合rest的架構設計原則。這樣的設計有利於高併發場景。當多個執行緒嘗試使用cas同時更新同乙個變數時,只有其中乙個執行緒能更新變數的值,而其它執行緒都失敗,失敗的執行緒並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。
最終,當下單更新到分布式資料庫中之後,整個下單過程才算真正告一段落。
好了,經過了十個過程,下單終於成功了,你是否對這個過程瞭如指掌了呢?如果發現對哪些細節比較模糊,可以回去看一下相應的章節,相信會有更加深入的理解。
網易雲物件儲存服務
nos(netease object storage)是高效能、高可用、高可靠的雲儲存服務。
點選可免費體驗
。
用進化的觀點學習網路協議
一般而言,我們比較習慣於用分層的觀點來學習網路協議,這也是大學的教學方法。然而這種方式在前期如果有個地方理解不了只有死記硬背了。如果我們帶著問題,並且試圖去解決這些問題,最終恍然大悟,得到 原來xx協議就是解決這個問題的啊 這種感嘆,那麼學習效果一定事半功倍。所謂帶著問題學習,其實就是拋開一切知識,...
用C 實現基於用C 實現基於TCP協議的網路通訊
tcp 協議是乙個基本的網路 協議,基本上所有的網路服務都是基於 tcp協議的,如http,ftp等等,所以要了解網路程式設計就必須了解基於 tcp協議的程式設計。然而 tcp協議是乙個龐雜的體系,要徹底的弄清楚它的實現不是一天兩天的功夫,所幸的是在.net framework環境下,我們不必要去追...
用C 實現基於TCP協議的網路通訊
在下面的例子中,我們將建立乙個時間伺服器,包括伺服器端程式和客戶端程式。伺服器端監聽客戶端的連線請求,建立連線以後向客戶端傳送當前的系統時間。先執行伺服器端程式,下面截圖顯示了伺服器端程式執行的狀況 然後執行客戶端程式,客戶端首先傳送連線請求到伺服器端,伺服器端回應後傳送當前時間到客戶端,這是客戶端...