量變引發質變。html
因爲咱們如今開發的雲平臺項目是一個跨雲調度的重型計算平臺,因此會用到不一樣的雲服務廠商的計算實例服務器,好比阿里雲的ECS、亞馬遜的EC2或者谷歌雲的compute engine等,同時也會在這些計算實例之間進行數據傳輸。 這些服務器之間的傳輸速度一般是不一樣的,即便是同一個雲服務廠商內的不一樣區域服務器之間傳輸數據,帶寬也會有所不一樣。 因此須要對這些服務器之間的帶寬速度進行測量,以供調度進程分配任務和傳輸數據。 總結起來這個功能實現起來有3個要求:前端
針對這3個問題,我實行了如下解決方案。git
一種簡單的設想就是啓動測速所需的客戶端和服務端,讓這些程序之間互相爭搶進行測速。 github
可是這種方式在進程的管理上很容易出現問題,由於每一個進程既要操做本身的狀態還須要操做其它進程的狀態,同時一會兒起4個進程也比較浪費,由於事實上每次測速只會有一個進程工做。 因此更好的方式是用主從結構,由一個主進程來啓停負責測速的客戶/服務端子進程。後端
這樣不但能有效地避免資源的浪費和爭搶,同時主進程中也能夠集成不少邏輯功能,好比對外提供 REST API,記錄日誌、備份測試結果等。服務器
固然爲了方便部署和程序控制,全部的進程都是部署在 Docker 容器中。網絡
因爲主進程須要訪問宿主機的 Docker 服務,因此須要開啓 Docker 的 remote API 服務,對容器提供REST API進行操做。前端工程師
網絡協議是一層一層封裝起來的,而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服務則會報錯,告知容器名稱衝突,從而建立失敗。 固然這種方式也有必定的風險,好比上一個進程測速過程當中出現問題沒有按時退出,那麼則沒法進行新的測速,因此須要須要設定一個超時時間,超過一段時間後主動中止當前測速子程序。 同時若是主程序意外退出,致使中止失敗的話,也要進行處理:在每次啓動主程序的時候進行檢查,及時銷燬未中止的子程序。
多節點算是很是棘手的問題。試想若是在一段時間內同時在多個雲服務器上啓動多個測速程序,如何保證他們有序的進行測速呢? 要解決這個問題,先思考一個簡單些的問題: 在一段時間內,如何決定哪些雲服務器啓動服務端子程序哪些雲服務器啓動客戶端子程序呢? 若是按照「主-從」模式的話須要創建一箇中心節點來進行控制,可是這樣的缺點不少,最重要的一個缺點是若是某個節點與中心節點沒法通訊那麼就沒法得到與其它節點通訊的機會,及時它和其它節點之間網絡暢通。 同時中心節點和其它節點之間也存在多節點通訊的問題。 總而言之這種方式下通訊的成本過高,服務端與客戶端傳輸數據須要的中間環節太多,很容易出現問題。 因此簡單的方式是讓雲服務器之間互相發起測速請求並響應。 這樣的話,主程序的邏輯要分爲兩個模塊,一個模塊用來響應請求、、分配端口、啓動服務端容器。 另外一個用來輪詢帶測速隊列併發起請求、啓動客戶端容器創建鏈接。
工做流程大體以下:
這種處理方式還有一種極端狀況,就是兩個雲服務器之間互相請求進行測試,若是雙方請求到達時間一致,那麼就會同時給對方分配端口,而後同時受到對方分配的端口以後發現服務端已啓動因而放棄鏈接。 因而出現了相似進程「死鎖」的狀態。 對於這種狀況的處理方式是使用時間戳來記錄請求發起的時刻,雙方經過時間戳的前後來決定是否啓動客戶端或服務端。 即便更極端的狀況出現——雙方時間戳相同。那麼經過超時回收或者發消息釋放端口來創建下一次鏈接。
弱網絡指的是網絡不穩定或者帶寬較小的狀況。 這種狀況的處理方式原則上就是重測,可是關於重測有幾個須要注意的地方:
不少時候實現一個功能並不困難,可是要把功能實現好倒是一件不簡單的事。 雖然理論上實現起來只是簡單的調用測速工具就能夠獲得結果,但在實際場景下可用性會變得很低。 好比沒有對弱網絡的重測機制,那麼偶然的網絡抖動就會影響到測速結果。 若是沒有考慮到多節點爭搶鏈接的問題,那麼實際運行在多個雲服務器上可能會形成程序錯誤或測速結果不許確的問題。 要怎麼樣把功能實現好呢? 至少有兩個考慮方向:
原文連接:tech.gtxlab.com/test-speed.…
做者信息:朱德龍,人和將來高級前端工程師。