目標需求
由于我們現在開發的云平臺項目是一個跨云調度的重型計算平臺,所以會用到不同的云服務廠商的計算實例服務器,比如阿里云的ECS、亞馬遜的EC2或者谷歌云的compute engine等,同時也會在這些計算實例之間進行數據傳輸。
這些服務器之間的傳輸速度通常是不同的,即使是同一個云服務廠商內的不同區域服務器之間傳輸數據,帶寬也會有所不同。
所以需要對這些服務器之間的帶寬速度進行測量,以供調度進程分配任務和傳輸數據。
總結起來這個功能實現起來有3個要求:
對不同區域內的云服務器的TCP/UDP傳輸速度進行檢測。
必須是滿帶寬的測速,也就是說兩臺服務器在測速的時候不能有其它網絡程序運行。
多臺服務器之間需要高效地建立兩兩連接。
針對這3個問題,我實行了以下解決方案。
基本結構
一種簡單的設想就是啟動測速所需的客戶端和服務端,讓這些程序之間互相爭搶進行測速。
但是這種方式在進程的管理上很容易出現問題,因為每個進程既要操作自己的狀態還需要操作其它進程的狀態,同時一下子起4個進程也比較浪費,因為事實上每次測速只會有一個進程工作。
所以更好的方式是用主從結構,由一個主進程來啟停負責測速的客戶/服務端子進程。
這樣不但能有效地避免資源的浪費和爭搶,同時主進程中也可以集成很多邏輯功能,比如對外提供 REST API,記錄日志、備份測試結果等。
當然為了方便部署和程序控制,所有的進程都是部署在 Docker 容器中。
由于主進程需要訪問宿主機的 Docker 服務,所以需要開啟 Docker 的 remote API 服務,對容器提供REST API進行操作。
功能實現
基本測速
TCP與UDP
網絡協議是一層一層封裝起來的,而TCP和UDP屬于同一層的兩種協議。
其中TCP協議在前后端開發中非常常用,因為REST API請求依賴的HTTP(S)協議就是TCP的上層協議,而UDP協議在視頻、游戲、下載等業務中使用也非常多。
它們有一些共同點:請求的發起方稱為客戶端,請求的接收方稱為服務端,服務端和客戶端可以雙向通信。
而它們的側重點有所區別。TCP更注重穩定,客戶端和服務端之間需要建立連接之后才能互相發送數據。
UDP則更注重速度,客戶端不需要和服務端建立連接即可直接發送數據,但是如果發送速度太快或者網絡不穩定可能會造成丟包,導致對方接收的數據部分丟失。
測速工具
常用的命令行測速工具有iperf和speedtest,相較之下選擇了功能更強大的iperf。
iperf是一個比較理想的測速工具,支持TCP、UDP協議,還可以通過參數來制定傳輸數據大小、傳輸次數或者傳輸時間,以及輸出結果的格式。
但是由于前面UDP協議的特性,測速會略微麻煩一些,需要找到合適的帶寬。
比如按照1Gbps的速度發送數據,丟包率是70%和按照10Mbps的速度發送數據,丟包率是0,那么對數據完整性有要求的話肯定更偏向于后者。
當然實際情況并不是對于丟包率為0就是最好的,而是在可容忍的范圍內采用最大速度傳輸(數據丟了還可以重傳不是~)。
這就意味著需要根據實際網絡狀況不斷調整和嘗試。
而iperf并沒有這么智能,所以UDP這一塊采用團隊內部開發的一款UDP傳輸工具,來找到理想的傳輸速度。
滿帶寬
要保證滿帶寬只需要保證測速時沒有其它程序占用帶寬即可。
由于我們可以啟動一臺獨立的搶占式服務器來運行測速程序,所以其它非測速程序的進程不太可能占用帶寬,而容易爭搶帶寬的是用來測速的子程序。
所以需要讓子程序之間是互斥運行,甚至是互斥存在的。
采用狀態管理基本上就可以實現,主程序在每次有進程啟動的時候將狀態置為”connecting”,測速完成后置為”waiting”,只有在”waiting”狀態下才可以啟動新的子程序進行測速。
但是這只是從代碼邏輯層面控制,對于穩定健壯的程序而言,最好還有其它的硬性控制方式。
這時候使用容器的話就可以輕松辦到。
凡是需要進行測速的進程都在容器中啟動,同時容器的名稱都統一,那么一旦程序出現bug,同時啟動多個子程序時,Docke r服務則會報錯,告知容器名稱沖突,從而創建失敗。
當然這種方式也有一定的風險,比如上一個進程測速過程中出現問題沒有按時退出,那么則無法進行新的測速,所以需要需要設定一個超時時間,超過一段時間后主動停止當前測速子程序。
同時如果主程序意外退出,導致停止失敗的話,也要進行處理:在每次啟動主程序的時候進行檢查,及時銷毀未停止的子程序。
多節點
多節點算是非常棘手的問題。試想如果在一段時間內同時在多個云服務器上啟動多個測速程序,如何保證他們有序的進行測速呢?
要解決這個問題,先思考一個簡單些的問題:
在一段時間內,如何決定哪些云服務器啟動服務端子程序哪些云服務器啟動客戶端子程序呢?
如果按照“主-從”模式的話需要建立一個中心節點來進行控制,但是這樣的缺點很多,最重要的一個缺點是如果某個節點與中心節點無法通信那么就無法獲得與其它節點通信的機會,及時它和其它節點之間網絡暢通。
同時中心節點和其它節點之間也存在多節點通信的問題。
總而言之這種方式下通信的成本太高,服務端與客戶端傳輸數據需要的中間環節太多,很容易出現問題。
所以簡單的方式是讓云服務器之間互相發起測速請求并響應。
這樣的話,主程序的邏輯要分為兩個模塊,一個模塊用來響應請求、、分配端口、啟動服務端容器。
另一個用來輪詢帶測速隊列并發起請求、啟動客戶端容器建立連接。
工作流程大致如下:
這種處理方式還有一種極端情況,就是兩個云服務器之間互相請求進行測試,如果雙方請求到達時間一致,那么就會同時給對方分配端口,然后同時受到對方分配的端口之后發現服務端已啟動于是放棄連接。
于是出現了類似進程“死鎖”的狀態。
對于這種情況的處理方式是使用時間戳來記錄請求發起的時刻,雙方通過時間戳的先后來決定是否啟動客戶端或服務端。
即使更極端的情況出現——雙方時間戳相同。那么通過超時回收或者發消息釋放端口來建立下一次連接。
弱網絡下的處理
弱網絡指的是網絡不穩定或者帶寬較小的情況。
這種情況的處理方式原則上就是重測,但是關于重測有幾個需要注意的地方:
對于帶寬較小的情況需要考慮減小傳輸數據的體積以保證在制定的超時時間內完成測速。
對于測速失敗的情況進行判斷并重測,同時限定重測次數,避免無限重測。
重測時可以讓客戶端與服務端進行互換測試,通過限定一方發起重測可實現。
總結
總體結構圖如下:
很多時候實現一個功能并不困難,但是要把功能實現好卻是一件不簡單的事。
雖然理論上實現起來只是簡單的調用測速工具就可以得到結果,但在實際場景下可用性會變得很低。
比如沒有對弱網絡的重測機制,那么偶然的網絡抖動就會影響到測速結果。
如果沒有考慮到多節點爭搶連接的問題,那么實際運行在多個云服務器上可能會造成程序錯誤或測速結果不準確的問題。
要怎么樣把功能實現好呢?
至少有兩個考慮方向:
倍數思維。比如當前框架支持10個頁面沒問題,那么如果100個、1000個會不會有性能問題?
極限思維。就是一些極端情況下的處理機制,比如在本文中對超時的處理,對容器互斥的處理等。
轉載請注明: 文章轉載自:愛思資源網 http://www.randomwithlife.com/show-31-1124-1.html