海量之道之弱聯網優化

IT技術精選文摘發表於2018-05-28

前言

【弱聯網優化】作為海量之道2.0系列課題的基礎方法論之一,望文生義,想必定是賣弄行動網路訊號不佳時如何優化的奇技淫巧。恭喜你,感覺靠譜,不過我們還想多聊一點其它話題。

移動網際網路顛覆著我們的生活方式,這個每時每刻伴隨著我們的網路到底有哪些特點,又是如何影響我們接入資訊世界的體驗呢。以下場景如似曾相識,敬請對號入座:

上班路上收到朋友分享的一張美女圖片,縮圖目測衣服用料相當節儉,立馬興奮點開欲詳細鑽研,卻發現怎麼脫也脫不下來,不對,是“拖”不是“脫”,仰望蒼天,欲哭無淚。

進電梯前收到女友一條訊息:“你到底愛不愛我!”,當然馬上回復“必須的必!”,電梯門關閉了,北風那個吹,菊花那個轉,等到春暖梯開,滿屏都是女友的問候“在嗎!”、“這都要想那麼久!”、“跟哪個MM聊天呢!”、“我生氣了!”、“你是好人,再見!”,看著自己的回覆剛剛傳送成功,停在最後一行,整個互動資訊一氣呵成,都是眼淚。

和朋友聚餐,菜端上先拍照分享,再大快朵頤,明明坐在對面,偏偏還得用手機聊天,世界最遠的距離,莫過於我們坐在一起,卻只能用手指切磋。

有因有果,有道有術,不入虎穴焉得虎子,不扯了,進入正題。

1. 行動網路的特點

我們看到行動網路和移動網際網路時代使用者的行為有如下三個典型特點:

1) 移動狀態網路訊號不穩定,高時延、易抖動丟包、通道狹窄;

2) 移動狀態網路接入型別和接入點變化頻繁;

3) 移動狀態使用者使用高頻化、碎片化、非WIFI流量敏感;

為什麼?參考【圖一 無線網路鏈路示意】,我們嘗試從物理上追根溯源:

【圖一 無線網路鏈路示意】

第一、直觀印象是通訊鏈路長而複雜,從(移動)終端裝置到應用伺服器之間,相較有線網際網路,要多經過基站、核心網、WAP閘道器(好訊息是WAP閘道器正在被依法取締)等環節,這就像送快遞,中間環節越多就越慢,每個中轉站的服務質量和服務效率不一,每次傳遞都要重新交接入庫和分派排程,一不小心還能把包裹給弄丟了;

第二、這是個資源受限網路,移動裝置接入基站空中通道數量非常有限,通道排程更是相當複雜,如何複雜就不展開了,莫文蔚那首歌詞用在這裡正合適:“我講又講不清,你聽又聽不懂......”,最最重要的是分配的業務通道單元如果1秒鐘不傳資料就會立馬被釋放回收,六親不認童叟無欺;

第三、這個鏈條前端(無線端)是高時延(除某些WIFI場景外)、低頻寬(除某些WIFI場景外)、易抖動的網路,無線各種制式網路頻寬上限都比較低而傳輸時延比較大(參見【表一 運營商移動訊號制式頻寬標準】),並且,沒事就能丟個包裹玩玩,最最重要的是,距離基站的遠近,把玩手機的角度、地下室的深度等等都能影響無線訊號的質量,讓包裹在空中飛一會,再飛一會......。這些因素也造成了移動網際網路網路質量穩定性差、接入變化頻繁,與有線網際網路對比更是天上人間的差別,從【圖二 有線網際網路和移動網際網路網路質量差異】中可以有更直觀的感受;

【圖二 有線網際網路和移動網際網路網路質量差異】

【表一 運營商移動訊號制式頻寬標準】資料來自網際網路各種百科,定性不定量,僅供參考;

移動運營商

通訊標準

訊號制式

備註

中國移動

2.5G

GPRS

屬2G網路,基於GSM,理論峰值速率172 Kbps(實際最高115 Kbps),圖示“G”,時延300 ~ 500ms;

中國移動

2.75G

EDGE

屬2G網路,基於GSM,理論下行峰值速率236 Kbps、上行峰值速率118 Kbps,圖示“E”,時延200 ~ 400ms;

中國移動

3G

TD-SCDMA

屬3G網路,理論下行峰值速率1.6 Mbps、上行峰值速率1.6 Mbps,圖示“T”,時延100ms;

中國移動

3.5G

HSPA

屬3G網路,基於TD-SCDMA,包括HSDPA和HSUPA,理論下行峰值速率2.8 Mbps、上行峰值速率2.2 Mbps,圖示“H”,時延100ms;

中國移動

4G

TD-LTE

屬4G網路,理論下行峰值100 Mbps,上行峰值 50Mbps,時延10 ~ 50ms;

中國電信

2G

CDMA 1X

屬2G網路,基於CDMA,理論下行峰值速率153Kbps、上行峰值速率153 Kbps,圖示“1X”,時延500 ~ 600ms;

中國電信

3G

EDVO

屬3G網路,基於CDMA 2000,理論峰值下行3.1 Mbps、上行1.8 Mbps,圖示“3G”,時延100ms;

中國電信

4G

TD-LTE FDD-LTE

均屬4G網路,TD-LTE理論下行峰值速率100 Mbps,上行峰值 速率50Mbps,FDD-LTE理論下行峰值速率150 Mbps,上行峰值速率40 Mbps,時延10 ~ 50ms;

中國聯通

2.5G

GPRS

屬2G網路,基於GSM,理論峰值114 Kbps,圖示“G”,時延300 ~ 500ms;

中國聯通

2.75G

EDGE

屬2G網路,基於GSM,理論下行峰值速率236 Kbps、上行峰值速率118 Kbps,圖示“E”,時延200 ~ 400ms;

中國聯通

3G

HSPA

屬3G網路,基於WCDMA,包括HSDPA和HSUPA,理論下行峰值速率14.4 Mbps、上行峰值速率5.76 Mbps,圖示“H”,時延100ms;

中國聯通

4G

TD-LTE FDD-LTE

均屬4G網路,TD-LTE理論下行峰值速率100 Mbps,上行峰值 速率50Mbps,FDD-LTE理論下行峰值速率150 Mbps,上行峰值速率40 Mbps,時延10 ~ 50ms;

N/A

WIFI

WLAN IEEE 802.11a/b/g

論峰值速率54 Mbps,時延10 ~ 50ms;

N/A

WIFI

WLAN IEEE 802.11n

理論峰值速率<600 Mbps,時延10 ~ 50ms;

【表一 運營商移動訊號制式頻寬標準】

第四、這是個區域性封閉網路,空中通道接入後要做鑑權、計費等預處理,WAP網路甚至還要做資料過濾後再轉發,在業務資料有效流動前太多中間代理人求參與,效率可想而知。產品研發為什麼又慢又亂,廣大程式猿心裡明鏡似的;最最重要的是,不同運營商之間跨網傳輸既貴且慢又有諸多限制,聰明的運營商便也用上了快取技術,催生了所謂網路“劫持”的現象。

如果我們再結合使用者在移動狀態下2G/3G/4G/WIFI的基站/AP之間,或者不同網路制式之間頻繁的切換,情況就更加複雜了。

2. 行動網路為什麼“慢”

我們在行動網路的特點介紹中,很容易的得到了三個關鍵字:“高時延”、“易抖動”、“通道窄”,這些物理上的約束確實限制了我們移動衝浪時的速度體驗,那麼,還有別的因素嗎。

當然有,汗牛充棟、罄竹難書:

1) DNS解析,這個在有線網際網路上司空見慣的服務,在移動網際網路上變成了一種負擔,一個往復最少1s,還別提遇到移動運營商DNS故障時的尷尬;

2) 鏈路建立成本暨TCP三次握手,在一個高時延易抖動的網路環境,並且大部分業務資料互動限於一個HTTP的往返,建鏈成本尤其顯著;

3) TCP協議層慢啟動、擁塞控制、超時重傳等機制在行動網路下引數設定的不適宜;

4) 不好的產品需求規定或粗放的技術方案實現,使得不受控的大資料包、頻繁的資料網路互動等,在行動網路側TCP鏈路上傳輸引起的負荷;

5) 不好的協議格式和資料結構設計,使得協議封裝和解析計算耗時、耗記憶體、耗頻寬,甚至協議格式臃腫冗餘,使得網路傳輸效能低下;

6) 不好的快取設計,使得資料的載入和渲染計算耗時、耗記憶體、耗頻寬;

現在終於知道時間都去哪了,太浪費太奢侈,還讓不讓人愉快的玩手機了。天下武功,唯快不破,我們一起踏上“快”的探索之路吧。

3. 移動聯網快的四個方法

在移動網際網路時代,對我們的產品和技術追求提出了更高的挑戰,如何從容和優雅的面對,需要先從精神上做好充分的準備,用一套統一的思考和行動準則武裝到牙齒:

1) 不要我等,一秒響應;

2) 可用勝於完美;

3) 水到渠成,潤物無聲;

聽起來很抽象,也不著急解釋(羅老師說:除了親人和法院,其它人誤會,都TND懶得解釋),耐心看完整篇文章再來回味,定有醍醐灌頂,昏昏欲睡之功效。

從來就沒有什麼救世主,只有程式猿征服一切技術問題的夢想在空中飄蕩。屢敗屢戰,把過往實踐中的經驗教訓總結出來,共同研討。針對行動網路的特點,我們提出了四個方法來追求極致的“爽快”:快鏈路、輕往復、強監控、多非同步。


下面逐一展開研討。

3.1. 快鏈路

我們需要有一條(相對)快速、(相對)順暢、(相對)穩定的網路通道承載業務資料的傳輸,這條路的最好是傳輸快、不擁堵、頻寬大、收費少。生活中做個類比,我們計劃驅車從深圳到廣州,如果想當然走廣深高速十之八九要杯具,首先這個高速略顯破敗更像省道,路況不佳不敢提速;其次這條路上的車時常如過江之鯽,如果身材不好操控不便,根本就快不起來;最後雙向六車道雖然勉強可以接受,但收費居然比廣深沿江高速雙向八車道還貴;正確的選路方案目前看是走沿江高速,雖然可能要多跑一段里程,但是通行更暢快。實際上,真實情況要更復雜,就如同【圖二 有線網際網路和移動網際網路網路質量差異】所示,漫漫征途中常常會在高速、國道、省道、田間小道上切換。

如何才能做到快鏈路,且聽下面分解。

3.1.1. TCP/IP協議棧引數調優

純技術活,直接上建議得了,每個子項爭取能大致有個背景交待,如果沒說清楚,可以找Google。

① 控制傳輸包大小

控制傳輸包的大小在1400位元組以下。暫時不講為什麼這樣建議,先舉個例子來類比一下,比如一輛大卡車滿載肥豬正在高速上趕路,豬籠高高層疊好不壯觀,這時前方突然出現一個隧道限高標識,司機發現卡車超限了,這下咋整。方案一,停車調頭重新找路,而且十之八九找不到,最後只能哪來回哪;方案二,把其中一群豬卸下來放本地找人代養,到達目的地卸完貨回來再取,你別說,這個機制在TCP/IP協議棧中也有,學名“IP分片”,後面會專門介紹。這個故事側面證實美國電腦科學家也曾經蹲在高速路邊觀察生豬超載運輸的過程,並飽受啟發。且慢,每次遇到問題,想到一些方案後我們都應該再捫心自問:“還有沒有更好的辦法呢?”。當然有,參照最近流行的說法,找個颱風眼,把豬都趕過去,飛一會就到了,此情此景想想也是醉了。

迴歸正題,概括的說,我們設定1400這個閾值,目的是減少往復,提高效能。因為TCP/IP網路中也有類似高速限高的規定,如果在超限時想要繼續順暢傳輸,要麼做IP分片要麼把應用資料拆分為多個資料包文(意指因為應用層客戶端或伺服器向對端傳送的請求或響應資料太大時,TCP/IP協議棧控制機制自動將其拆分為若干獨立資料包文傳送的情況,後面為簡化討論,都以IP分片這個分支為代表,相關過程分析和結論歸納對二者均適用)。而一旦一個資料包文發生了IP分片,便會在資料鏈路層引入多次的傳輸和確認,加上報文的拆分和拼接開銷,令得整個資料包的傳送時延大大增加,並且,IP分片機制中,任何一個分片出現丟失時還會帶來整個IP資料包文從最初的發起端重傳的消耗。有點枯燥了,我們從一些基礎概念開始逐步深入理解:

a. 【乙太網】

這個術語一般是指數字裝置公司(Digital Equipment Corp.)、英特爾公司(I n t e l Corp .)和X e r o x公司在1 9 8 2年聯合公佈的一個標準,它是當今TCP/IP採用的主要網路技術。乙太網採用一種稱作C S M A / C D的媒體接入方法,其意思是帶衝突檢測的載波偵聽多路接入(Carrier Sense, Multiple Access with Collision Detection)。隨著乙太網技術的不斷演進,傳輸速率已由最初的10 Mb/s發展到如今100Mb/s、1000Mb/s、10000Mb/s等。

我們現在使用的TCP/IP網路協議,基本上都在乙太網上傳輸,資料被封裝在一個個乙太網包中傳遞,這些乙太網包就是那一輛輛運豬的大卡車。乙太網包的封裝格式可以參考【圖三 乙太網的封裝格式(RFC 894)】,很容易看出乙太網包能傳輸的有效“資料”大小在46 ~ 1500位元組之間。如果我們把乙太網看做是運豬的高速公路,能承載有效資料的最大值看作是高速路上隧道的限高,那麼這個限高在TCP/IP協議中學名是MTU(Maximum Transmission Unit,最大傳輸單元)。MTU屬於鏈路層制定的邏輯(並非物理)特性限制,所謂無規矩不成方圓。

【圖三 乙太網的封裝格式(RFC 894)】

b. 【TCP/IP資料包】

TCP/IP資料包被封裝在乙太網包的“資料”中,通過【圖四 TCP資料在IP資料包中的封裝】可以看到,一個IP資料包包括IP包頭、TCP包頭和TCP資料三個部分,其中兩個包頭分別用於IP層和TCP層的報文傳輸控制,可以理解為運豬的大卡車和豬籠。TCP資料則是有效載荷,可以理解為那群肥豬。

【圖四 TCP資料在IP資料包中的封裝】

我們再來詳細看看IP資料包,如【圖五 IP資料包格式及首部中的各欄位】所示,一個標準IP資料包中,IP包頭大小為20位元組,如果加上可選項,則IP包頭最大可以達到60位元組。

【圖五 IP資料包格式及首部中的各欄位】

TCP資料包如【圖六 TCP資料包格式及首部中的各欄位】所示,一個標準TCP包頭大小為20位元組,如果加上可選項,則最大也可以達到60位元組。

【圖六 TCP資料包格式及首部中的各欄位】

c. 【TCP MSS】

TCP MSS(TCP Maximum Segment Size,TCP最大報文段長度,後面均簡稱MSS)表示TCP/IP協議棧一次可以傳往另一端的最大TCP資料長度,注意這個長度是指TCP報文中的有效“資料”(即應用層發出的業務資料)部分,它不包括TCP報文包頭部分,我們可以把它理解為卡車能裝運生豬的最大數量或重量。它是TCP選項中最經常出現,也是最早出現的選項,佔4位元組空間。

MSS是在建立TCP連結的三次握手過程中協商的,每一方都會在SYN或SYN/ACK資料包文中通告其期望接收資料包文的MSS(MSS也只能出現在SYN或SYN/ACK資料包中),說是協商,其實也沒太多回旋的餘地,原因一會講。如果協商過程中一方不接受另一方的MSS值,則TCP/IP協議棧會選擇使用預設值:536位元組。

有了以上的基礎知識,我們就能比較清晰的描述出乙太網、MTU、TCP/IP資料包文和MSS之間的關係了,如【圖七 TCP/IP資料包、MTU/MSS在乙太網格式中的關係】所示,MTU和MSS關係用公式表達就是:

MTU = IP包頭 + TCP包頭 + MSS;

對照到我們肥豬裝運的例子,自然得出公式如下:

限高 = 卡車高度 + 籠子高度 + 生豬數量或重量;

【圖七 TCP/IP資料包、MTU/MSS在乙太網格式中的關係】

注:FCS(Frame Check Sequence)是指幀校驗值;

實際上MSS值太小或太大都不合適。

太小比如設為1位元組,那麼為了傳輸1個位元組的資料,得搭上IP包頭的20位元組和TCP包頭的20位元組,如果再加上鍊路層、物理層的其它開銷,顯然效率低下不夠環保,這就如同卡車跑一趟只拉一頭肥豬一樣,相當坑。

MSS是不是越大越好呢,這也符合我們的正常思維邏輯,就好比養豬場和買家都希望卡車一趟能多運幾頭肥豬,可以加快資源週轉效率。但實際情況是MSS如果設得太大,封裝的資料過多,不但傳輸時延會增加,還很可能因為超過MTU的限制,使得在IP層傳輸過程中發生分片(又是它,忍著,馬上就會展開了),接受方在處理IP分片包所消耗的資源和處理時間都會增大,前面也提到過,如果IP分片在傳輸中出現分片丟失,哪怕只是丟失一個分片,都會引起整個IP資料包的重傳,這是因為IP層本身沒有設計超時重傳機制,有興趣可以研讀《TCP/IP詳解 卷一:協議》瞭解詳細細節。由此可以想見網路開銷會因此大大增加。

TCP/IP協議設計者是不希望分片出現的,現在有點明白前面說MSS協商迴旋餘地不大的含義了吧。另外,MSS同滑動視窗和擁塞控制也有關聯,後續談到相關話題時我們再細聊。

d. 【IP分片】

快樂運豬路遇限高緊急應對方案二閃亮登場,IP資料包文傳輸過程中,任何傳輸路徑上節點的IP層在接收到一份要傳送的IP資料包文時,首先會通過路由選路判斷應從本地哪個網路介面把IP資料包轉發出去,隨後查詢獲取該網路介面的MTU,如果IP資料包文長度超過了這個MTU,且該資料包文沒有設定DF(Don't Frament,不要分片,非預設值)標誌位,就得做IP分片,即把接收到的IP資料包文拆分成多個更小(不超過該介面MTU)的IP資料包文繼續傳輸,並且,分片的資料可能在路上會被再次分片,分片到達最終目的地後會按順序重新組裝還原,【圖五 IP資料包格式及首部中的各欄位】中3位標誌和13位片偏移就是用來幹這個的。

為了避免IP分片,TCP/IP協議設計者在TCP層實現了MSS協商機制,設想如果最終確定的MSS小於路由路徑中最小的那個MTU,那麼就能避免IP分片的發生。

在TCP連結三次握手過程中,網路通訊的兩個端點在SYN和SYN/ACK資料包文中分別把自己出口MSS發給對端,以便對方瞭解自己的“限高”水平,最終控制發出的應用資料包文大小,達到避免IP分片的目的。

如果運氣好,路由路徑上的路由裝置會積極參與三次握手過程中MSS協商機制,一旦發現自己出口的MSS比資料包文中的那個小,就會主動修改資料包文中的MSS,這樣整個路由鏈路端到端這條“高速路”的整體“限高”水平就準確清晰了。

通過【圖八 TCP MSS協商過程】,可以瞭解上述TCP MSS的協商過程。注意,這個“完美”方案需要運氣好才行。因為中間路由裝置五花八門,不能支援或者不願支援MSS協商的情況時有發生。想讓大夥都積極支援協商的美好願望,就如同滿懷對全世界各國政府官員實施財產公示的期許,結果是一樣一樣的。

【圖八 TCP MSS協商過程】

快樂運豬路遇限高緊急應對方案一有沒有發揮空間呢?很好的問題,聰明的TCP/IP協議設計者當然不甘心,於是利用前面提到的DF標誌位設計了一個叫做路徑MTU發現的機制就用到了方案一的原理,如果IP資料包文的3位標誌欄位中的DF位置為1,則IP層遇到需要IP分片時,就會選擇直接丟棄報文,並返回一個相應的ICMP出錯報文,看到了吧,此路不通,請帶領群豬原路返回。這個方案運作成本頗高。不繼續深入描述了,有興趣可以研讀《TCP/IP詳解 卷一:協議》。

至此,我們可以得出如下結論,TCP/IP資料包文大小超過物理網路層的限制時,會引發IP分片,從而增加時空開銷。

因此,設定合理的MSS至關重要,對於乙太網MSS值建議是1400位元組。什麼,你的數學是體育老師教的嗎?前面說乙太網最大的傳輸資料大小是1500位元組,IP資料包文包頭是20位元組,TCP報文包頭是20位元組,算出來MSS怎麼也得是1460位元組呀。如果回答是因為很多路由裝置比如CISCO路由器把MSS設定為1400位元組,大夥肯定不幹,回憶一下IP和TCP的資料包包頭都各有40位元組的可選項,MTU中還需要為這些可選項留出空間,也就壓縮了MSS的空間。要是再追問為啥這個值不是1380位元組,那就有點過分了。

那麼問題來了,控制“限高”哪種方案才最強。我們嘗試探討一下。

首先,可以在我們自己IDC內將各種路由交換裝置的MSS設定小於或等於1400位元組,並積極參與TCP三次握手時的MSS協商過程,期望達到自動控制伺服器收發資料包文大小不超過路徑最小MTU從而避免IP分片。這個方案的問題是如果路由路徑上其它裝置不積極參與協商活動,而它的MTU(或MSS設定值)又比較low,那就白乾了。這就好比國家制定了一個高速沿途隧道限高公示通告標準,但是某些地方政府就是不告訴你,沒轍。


其次,可以在業務服務中控制應用資料請求/響應的大小在1400位元組以下(注:也無法根本避免前述方案中間路由MTU/MSS low的問題),在應用層資料寫入時就避免往返資料包大小超過協商確定的MSS。但是,歸根到底,在出發前就把資料拆分為多個資料包文,同IP分片機制本質是相同的,互動響應開銷增加是必然的。考慮到人在江湖,安全第一,本方案從源頭上控制,顯得更實際一些。


當然,最靠譜的還是做簡法,控制傳輸資料的慾望,用曼妙的身姿騰挪有致,相關的內容放到輕往復章節探討。


對應到前面的快樂運豬案例,就是要麼在生豬裝車之前我們們按照這條路上的最低限高來裝車(問題是怎麼能知道整個路上的最低限高是多少),要麼按照國家標準規定允許的最小限高來裝車,到這裡,肥豬們終於可以愉快的上路了,風和日麗,通行無阻,嗯,真的嗎?


② 放大TCP擁塞視窗


把TCP擁塞視窗(cwnd)初始值設為10,這也是目前Linux Kernel中TCP/IP協議棧的預設值。放大TCP擁塞視窗是一項有理有據的重要優化措施,對行動網路尤其重要,我們同樣從一些基本理論開始逐步深入理解它。


TCP是個傳輸控制協議,體現控制的兩個關鍵機制分別是基於滑動視窗的端到端之間的流量控制和基於RTT/RTO測算的端到網路之間的擁塞控制。


流量控制目標是為了避免資料傳送太快對端應用層處理不過來造成SOCKET快取溢位,就像一次發了N車肥豬,買家那邊來不及處理,然後臨時囤貨的豬圈又已客滿,只好拒收/拋棄,相關概念和細節我們不展開了,有興趣可以研讀《TCP/IP詳解 卷一:協議》。


擁塞控制目標是在擁塞發生時能及時發現並通過減少資料包文進入網路的速率和數量,達到防止網路擁塞的目的,這種機制可以確保網路大部分時間是可用的。擁塞控制的前提在於能發現有網路擁塞的跡象,TCP/IP協議棧的演算法是通過分組丟失來判斷網路上某處可能有擁塞情況發生,評判的具體指標為分組傳送超時和收到對端對某個分組的重複ACK。在有線網路時代,丟包發生確實能比較確定的表明網路中某個交換裝置故障或因為網路埠流量過大,路由裝置轉發處理不及時造成本地快取溢位而丟棄資料包文,但在行動網路中,丟包的情況就變得非常複雜,其它因素影響和干擾造成丟包的概率遠遠大於中間路由交換裝置的故障或過載。比如短時間的訊號干擾、進入一個訊號遮蔽的區域、從空閒基站切換到繁忙基站或者行動網路型別切換等等。網路中增加了這麼多不確定的影響因素,這在TCP擁塞控制演算法最初設計時,是無法預見的,同時,我們也確信未來會有更完善的解決方案。這是題外話,如有興趣可以找些資料深入研究。


擁塞控制是TCP/IP協議棧最經典的和最複雜的設計之一,網際網路自我犧牲的利他精神表露無遺,設計者認為,在擁塞發生時,我們應該減少資料包文進入網路的速率和數量,主動讓出道路,令網路能儘快調整恢復至正常水平。


擁塞控制機制包括四個部分:


a.         慢啟動;


b.         擁塞避免;


c.         擁塞發生時的快速重傳;


d.         快速恢復;


話題太大,我們聚焦到與本主題相關的【慢啟動】上。


慢啟動這項措施的緣起是,當新連結上的資料包文進入一個擁塞狀況不可預知的網路時,貿然過快的資料傳送可能會加重網路負擔,就像養豬場每天都會向很多買家發車送肥豬,但是出發前並不瞭解各條高速路上的擁堵情況,如果按照訂單一口氣全部發出去,會遇到兩種情況,一是高速很順暢,很快到達(此時流量控制可能要干預了);二是高速本身就有些擁堵,大批卡車上路加劇了擁堵,並且肥豬們堵在路上,缺衣少食餓瘦了買家不幹,風餐露宿凍死了賣家吃虧,重新發貨還耽誤時間,並且,用於重新發貨的貨車加入高速則進一步加重了擁堵的情況。作為一個充滿社(wei)會(li)良(shi)知(tu)精神的養豬場,我們肯定不願意貿然增加高(zi)速(ji)的負擔。


下面進入簡單的理論知識介紹部分,如覺枯燥,敬請諒解。


TCP是一個可靠傳輸協議,基礎是傳送-應答(ACK)式確認機制,就好比肥豬運到目的地買家簽收以後,要給卡車司機一個回執帶回去交差,豬場老闆一看回執,大喜過望,馬上繼續裝車發運,如此往復。如【圖九 TCP連結建立、傳輸和關閉示意】,可以瞭解這種傳送-應答式工作的基本流程,如果再結合流量控制、擁塞控制和超時重傳等機制,會有很多變種case,整個協議棧因而顯得比較複雜。


但,萬變不離其宗,老子說“是以聖人抱一為天下式”,真經典。


【圖九 TCP連結建立、傳輸和關閉示意】


慢啟動顧名思義,就是把(網路鏈路資料包文傳輸)啟動的速度放慢一些。方法其實也挺簡單,TCP傳送方維護了兩個引數用於控制這個過程,它們分別是擁塞視窗(cwnd,Congestion Window)和慢啟動門限(ssthresh,Slow Start Threashold),具體演算法如下:


1)        TCP連結建立好以後,cwnd初始化1,單位是連結建立過程中協商好的對端MSS,1代表一次可以傳送1 * MSS個位元組。ssthresh初始化為65535,單位是位元組;


2)        每當收到一個ACK,cwnd ++,cwnd呈線性上升,傳送方此時輸出資料量不能超過cwnd和接收方通告的TCP視窗(這個概念我們在後面的章節中會介紹)大小;


3)        每當經過一個RTT(Round Trip Time,網路往返時間),cwnd = cwnd * 2,cwnd呈指數讓升,同樣傳送方此時輸出資料量不能超過cwnd和接收方通告的TCP視窗大小;


4)        ssthresh(slow start threshold)是一個上限,當cwnd >= ssthresh時,就進入“擁塞避免”演算法;



廣告時間,插播簡單介紹一下RTT,它是Round Trip Time(網路往返時間)的簡寫,簡單的理解就是一個資料包文從傳送出去到接收到對端ACK確認的時間(這樣描述其實不夠嚴謹,因為我們沒有展開資料包文傳送和對端ACK確認的各種複雜case)。RTT是TCP超時重傳機制的基礎,也是擁塞控制的關鍵引數,準確的估算出RTT具有偉大的現實意義,同時也是一項相當艱鉅複雜的任務。電腦科學先輩們在持續完善RTT的計算方法,從最初RFC793中描述的經典演算法,到Karn / Partridge演算法,最後發展到今天在使用的Jacobson / Karels演算法,如有興趣可自行以深入研究。


通過【圖十 慢啟動過程示意】,可以更直觀的理解慢啟動的過程,經過兩個RTT,cwnd已經由初始值1演化為4:即在接收方通告視窗大小允許的情況下,可以連續傳送4個資料包文,然後繼續指數增長,這麼看來,慢啟動一點都不慢。


【圖十 慢啟動過程示意】


注:示意圖中三個RTT大括弧逐漸變大不是因為RTT數值變大,而是要      示意包含的資料包文變多;


豬場老闆來解讀一下這個演算法,我們對一個買家同時維護兩個賬單數字,一是起運數量設為n,單位是卡車,二是最大同時發貨數量設為m,以肥豬頭數為單位,描述如下:


1)        同買家訂單協商確定後,n初始化1,把符合通往買家的高速路上限高要求的一輛卡車最大裝載肥豬頭數設為h,1代表一次可以傳送1 * h頭肥豬。m初始化為65535,單位是頭;


2)        每當收到一個買家回執,n ++,n呈線性上升,豬場老闆此時發貨數量不能超過n和買家通告的臨時囤貨的豬圈大小;


3)        每當經過一個送貨往返,n = n * 2,n呈指數讓升,同樣豬場老闆此時發貨數量不能超過n和買家通告的臨時囤貨的豬圈大小;


4)        m是一個上限,當n >= m時,為了避免可能帶來的高速擁堵,就要進入“擁塞避免”演算法;


這裡,需要提到Google的一篇論文《An Argument for Increasing TCP’s Initial Congestion Window》暨RFC6928。Linux Kernel從3.0開始採用了這篇論文的建議---把cwnd 初始化為10個MSS,而在此之前,Linux Kernel採用了RFC3390的規定,cwnd是根據MSS的值來動態變化的。Google的這篇論文值得研究一下,理論分析和實踐檢驗都有。


簡單來說,cwnd初始化為10,就是為了允許在慢啟動通過往復RTT“慢慢”提升擁塞視窗前,可以在第一個網路傳輸回合中就傳送或接收14.2KB(1460 10 vs 5.7KB 1460 4)的資料。這對於HTTP和SSL來講是非常重要的,因為它給了更多的空間在網路互動初始階段的資料包文中填充應用協議資料。


對於移動APP,大部分網路互動都是HTTP併發短連結小資料量傳輸的形式,如果伺服器端有10KB +的資料返回,採用過去的慢啟動機制時,效率會低一些,大概需要2~3個RTT才能完成資料傳輸,反應到使用者體驗層面就是慢,而把擁塞視窗cwnd初始值提升到10後,在大多數情況下都能在1個RTT的週期內完成應用資料的傳輸,這在行動網路這樣的高時延、不穩定、易丟包的場景下,顯得尤其意義重大。


一次就發10卡車肥豬,讓慢啟動歇一會,別問為什麼,有錢,任性。


③ 調大SOCKET讀寫緩衝區


把SOCKET的讀緩衝區(亦可稱為傳送緩衝區)和寫緩衝區(亦可稱為接收緩衝區)大小設定為64KB。在Linux平臺上,可以通過 setsockopt 函式設定SO_RCVBUF和SO_SNDBUF選項來分別調整SOCKET讀緩衝區和寫緩衝區的大小。


這兩個緩衝區跟我們的TCP/IP協議棧到底有怎麼樣的關聯呢。我們回憶一下【圖六 TCP資料包格式及首部中的各欄位】,裡面有個16位視窗大小,還有我們前面提到的流量控制機制和滑動視窗的概念,大幕徐徐拉開,主角紛紛粉墨登場。在正式詳細介紹之前,按照傳統,我們還是先站在豬場老闆的角度看一下,讀緩衝區就好比買家用來囤貨的臨時豬圈,如果貨到了買家使用部門來不及處理,就先在這裡臨時囤著,寫緩衝區就好比養豬場根據訂單裝好車準備發貨,如果買家說我現在可以收貨便可速度發出,有點明白了吧。下面詳細展開探討:


a.         【TCP視窗】


整個TCP/IP協議體系是經典的分層設計,TCP層與應用層之間銜接的部分,就是作業系統核心為每個TCP鏈路維護的兩個緩衝區,一個是讀緩衝一個是寫緩衝。從資料結構角度講,這兩個緩衝區是環形緩衝區。


讀緩衝肩負的使命是把接收到並已ACK(確認)過的TCP報文中的資料快取下來,由應用層通過系統介面讀取消費。就好比買家內部會分原料採購部門和產品加工部門,採購部門收到肥豬後先送到臨時豬圈好吃好喝供著,加工部門需要的時候就會拎著屠刀過來提豬。


寫緩衝肩負的重任是快取應用層通過系統介面寫入的要傳送的資料,然後由TCP/IP協議棧根據cwnd、ssthresh、MSS和對端通告的TCP視窗等引數,擇機把資料分報文段發往對端讀緩衝。想要在擁塞控制等相關引數都允許的條件下連續傳送資料包文,尚需對端通告的TCP視窗大小能夠容納它們。就好比豬場老闆根據買家訂單發貨,先調配若干輛卡車,根據高速的限高要求裝上肥豬,然後再考慮高速的順暢情況來分批發貨,貨可以陸續上路,但還有一個重要前提是發貨前買家通告的臨時豬圈空間是足夠容納這些肥豬的。


TCP視窗是用於在接收端和傳送端之間動態反映接收端讀緩衝大小的變化,它的初始值就是讀緩衝區設定的值,單位是位元組,這個數字在TCP包頭的16位視窗大小欄位中傳遞,最大65535位元組,如果嫌不夠大,在TCP選項中還有一個視窗擴大的選項可供選擇。


為什麼叫視窗,一窗一風景,英文世界很現實,境界也就到Window級了,這與中華文明一沙一世界,一花一天堂的差距甚大。再直觀一些的類比就是你拿著一個放大鏡,在1:10000的軍用地圖上順著一條路苦苦尋找東莞某鎮,放大鏡的範圍就是我們說的視窗。


概括而言,TCP視窗的作用是量化接收端的處理能力,調控傳送端的傳輸節奏,通過視窗的伸縮,可以自如的調節傳送端的資料傳送速率,從而達到對接收端流量控制的目的。


師傅三藏曾經對悟空說:你想要啊?你想要說清楚不就行了嗎?你想要的話我會給你的,你想要我當然不會不給你啦!不可能你說要我不給你,你說不要我卻偏要給你,大家講道理嘛!現在我數三下,你要說清楚你要不要......,嗯,說清楚最重要。


b.         滑動視窗


客戶端和伺服器在TCP連結建立的三次握手過程中,會根據各自接收緩衝區大小通告對方TCP視窗大小,接收方根據自己接收緩衝區大小初始自己的“接收視窗”,傳送方根據對端通告的TCP視窗值初始化一個對應的“傳送視窗”,接收視窗在此端的接收緩衝區上滑動,傳送視窗在彼端的傳送緩衝區上滑動。因為客戶端和伺服器是全雙工,同時可收可發,故我們有兩對這樣的視窗在同時工作。


既然是滑動視窗,就意味著可以滑動、伸縮,【圖十一 TCP視窗邊沿移動】展示了這些情況,注意TCP/IP協議棧規定TCP視窗左邊沿只能向右滑動,且TCP的ACK確認模式也在機制上禁止了TCP視窗左邊沿向左移動。與視窗滑動相關術語有三個:


1)        TCP視窗左邊沿向右邊沿靠近稱為視窗合攏,發生在資料被髮送和確認 時。如果左右邊沿重合時,則形成一個零視窗,此時傳送方不能再傳送任何資料;


2)        TCP視窗右邊沿向右移動稱為視窗張開,也有點類似視窗向右側橫向滑動。這種現象發生在接收方應用層已經讀取了已確認過的資料並釋放了TCP接收緩衝區時;


3)        TCP視窗右邊沿向左移動稱為視窗收縮,RFC強烈建議避免使用這種方式;


【圖十一 TCP視窗邊沿移動】


我們再來看看滑動視窗與SOCKET緩衝區如何結合使用。假設一個客戶端設定了16個單位的讀緩衝區,編號是0 ~ 15,伺服器也相應的設定了16個單位的寫緩衝區,編號是0 ~ 15。在TCP連結建立的時候,客戶端會把自己的讀緩衝大小16通告給伺服器,此時在客戶端和伺服器就維護了一對收發視窗。在【圖十二 伺服器TCP傳送視窗示意】展示了服務端傳送緩衝區和其上的滑動視窗,其中大的黑色邊框就是著名的滑動視窗。


【圖十二 伺服器TCP傳送視窗示意】


傳送緩衝和傳送視窗一共區隔出四個部分:


1)        已傳送並收到ACK確認的資料(即已成功到達客戶端),單元格邊框以粉色標識;


2)        已傳送還未收到ACK確認的資料(即傳送但尚未能確認已被客戶端成功收到),單元格邊框以藍色標識;


3)        處於傳送視窗中還未發出的資料(即對端接收視窗通告還可容納的部分),單元格邊框以綠色標識;


4)        處於傳送視窗以外還未發出的資料(即對端接收視窗通告無法容納的部分),單元格邊框以黃色標識;




為了更好的理解滑動視窗的變化過程,可以觀察【圖十三 TCP滑動視窗變遷示例】,它向我們展示了一個伺服器向客戶端傳送資料時讀寫視窗的變化過程:


【圖十三 TCP滑動視窗變遷示例】


1)        客戶端通告了一個360位元組的TCP視窗並在自己的讀緩衝區初始化該視窗,伺服器在它的寫緩衝區初始化了這個視窗;


2)        伺服器傳送120位元組到客戶端,伺服器傳送視窗此時包括了兩部分,120位元組為等待ACK確認的資料、240位元組為等待傳送的資料,視窗大小為360位元組不變;


3)        客戶端收到120位元組資料,放入接收緩衝區,此時應用層馬上讀取了頭40位元組,接收視窗因此調整為280(360 - 120 + 40)位元組,接收視窗先合攏,然後張開。客戶端回覆ACK確認收到120位元組資料,並且通告接收視窗調整為280位元組;


4)        伺服器收到客戶端的ACK確認,傳送視窗也先發生合攏,隨後根據客戶端通告的新接收視窗大小,重新調整傳送視窗,此時傳送視窗又張開至280位元組;


5)        伺服器傳送240位元組到客戶端,伺服器傳送視窗此時包括了兩部分,240位元組為等待ACK確認和40位元組等待傳送的資料,視窗大小為280位元組不變;


6)        客戶端收到240位元組資料,放入接收緩衝區,此時應用層又讀取了頭80位元組,接收視窗因此調整為120(280 - 240 + 80),接收視窗先合攏,然後張開。客戶端回覆ACK確認收到240位元組資料,並且通告接收視窗調整為120位元組;


7)        伺服器收到客戶端的ACK確認,傳送視窗也先發生合攏,隨後根據客戶端通告的新接收視窗大小,重新調整傳送視窗,此時傳送視窗又張開至120位元組;


8)        伺服器傳送120位元組到客戶端,伺服器傳送視窗此時僅包括一部分,即120位元組等待ACK確認的資料;


9)        客戶端收到120位元組資料,放入接收緩衝區,接收視窗因此調整為0(120 - 120),接收視窗合攏為0。客戶端回覆ACK確認收到120位元組資料,並且通告接收視窗調整為0位元組;


10)     伺服器收到客戶端的ACK確認,傳送視窗也發生合攏,隨後根據客戶端通告的新接收視窗大小,重新調整傳送視窗,此時因為接收視窗為0,傳送視窗保持合攏狀態;


提升TCP吞吐量,最佳狀態是在流量控制機制的調控下,使得傳送端總是能傳送足夠的資料包文填滿傳送端和接收端之間的邏輯管道和緩衝區。其中邏輯管道的容量有專門的學名叫BDP(Bandwidth Delay Product,頻寬時延乘積,BDP = 鏈路頻寬 * RTT),在一個高頻寬低時延的網路中,TCP包頭中的16位視窗大小可能就不夠用了,需要用到TCP視窗縮放選項,在RFC1323中定義,有興趣可以研究一下。


豬場老闆解讀:滑動視窗是從養豬場到買家臨時豬圈的出入閘門,豬場養殖場這道出閘門叫傳送視窗,買家臨時豬圈那道入閘門叫接收視窗,為了不讓買家的臨時豬圈爆滿溢位無法簽收新來的肥豬們,進而導致豬場白送一趟貨,豬場老闆必須要等買家通告自己空閒槽位數量後才可進行生豬發貨操作,這個槽位數量就是視窗大小,槽位減少或增加,受到豬場發貨速率和買家屠宰部門提貨速率的共同影響,表現出類似視窗合攏或張開的滑動狀態。我們期待的最佳狀態就是高速路上跑滿歡快的車隊,臨時豬圈住滿幸福的肥豬。


三藏對小牛精說:所以說做妖就像做人,要有仁慈的心,有了仁慈的心,就不再是妖,是人妖。哎,他明白了,你明白了沒有?


④ 調大RTO(Retransmission TimeOut)初始值


將RTO(Retransmission TimeOut)初始值設為3s。


TCP為每一個報文段都設定了一個定時器,稱為重傳定時器(RTO),當RTO超時且該報文段還沒有收到接收端的ACK確認,此時TCP就會對該報文段進行重傳。當TCP鏈路發生超時時,意味著很可能某個報文段在網路路由路徑的某處丟失了,也因此判斷此時網路出現擁塞的可能性變得很大,TCP會積極反應,馬上啟動擁塞控制機制。


RTO初始值設為3s,這也是目前Linux Kernel版本中TCP/IP協議棧的預設值,在鏈路傳輸過程中,TCP協議棧會根據RTT動態重新計算RTO,以適應當前網路的狀況。有很多的網路調優方案建議把這個值儘量調小,但是,我們開篇介紹行動網路的特點之一是高時延,這也意味著在一個RTT比較大的網路上傳輸資料時,如果RTO初始值過小,很可能發生不必要的重傳,並且還會因為這個事件引起TCP協議棧的過激反應,大炮一響,擁塞控制閃亮登場。


豬場老闆的態度是什麼樣的呢:曾經有一份按時發貨的合同擺在我的面前,我沒有去注意,等到重新發了貨才追悔莫及,塵世間最痛苦的事莫過於此,如果上天能給我一個再來一次的機會,我希望對甲方說耐心點,如果非要給這個耐心加一個期限的話,我希望是一萬年。


⑤ 禁用TCP快速回收


TCP快速回收是一種連結資源快速回收和重用的機制,當TCP連結進入到TIME_WAIT狀態時,通常需要等待2MSL的時長,但是一旦啟用TCP快速回收,則只需等待一個重傳時間(RTO)後就能夠快速的釋放這個連結,以被重新使用。Linux Kernel的TCP/IP協議棧提供了一組控制引數用於配置TCP埠的快速回收重用,當把它們的值設定為1時表示啟用該選項:


1)        net.ipv4.tcp_tw_reuse = 1


2)        net.ipv4.tcp_tw_recycle = 1


3)        net.ipv4.tcp_timestamps = 1(tcp_tw_recycle啟用時必須同時啟用本項,反之則不然,timestamps用於RTT計算,在TCP報文頭部的可選項中傳輸,包括兩個引數,分別為傳送方傳送TCP報文時的時間戳和接收方收到TCP報文響應時的時間戳。Linux系統和移動裝置上的Android、iOS都預設開啟了此選項,建議不要隨意關閉)


以上引數中tw是TIME_WAIT的縮寫,TIME_WAIT與TCP層的連結關閉狀態機相關。下面我們看看TIME_WAIT是誰,從哪裡來,往哪裡去。

前面我們在介紹基礎理論知識的時候,【圖九 TCP連結建立、傳輸和關閉示意】中最後四個資料包文就是TCP連結關閉的過程,俗稱四次揮手,分手總是難以割捨的,所以連結建立只需三次握手,分手得要四次回首。

TCP設計目標是可靠傳輸,哪怕在分手時也得確保成功。為此,在TCP連結關閉階段設計了繁雜的狀態機,在【圖十四 TCP狀態變遷圖】的左下角虛線框中的四個狀態FIN_WAIT1、FIN_WAIT2、CLOSING、TIME_WAIT,代表著主動關閉TCP連結這一方的可能狀態,前三個狀態最終都會進入到等待響應最後一個FIN的ACK的這個階段,即TIME_WAIT狀態,並且在此停留2MSL(2倍Maximum Segment Lifetime,2倍報文段最大生存時間,RFC793規定MSL為2分鐘,Linux Kernel中TCP/IP協議棧採用的是30秒,這個值的選擇是有講究的,它是一個物理上的約束,表示一個IP資料包文在地球上最長的存活時間,意思就是即便收不到這個ACK,也會給時間讓它最終在地球的某個角落裡消失)時長。這樣處理的原因是在四次揮手過程中,主動關閉方需要確保自己最後傳送響應對端FIN的ACK能被對端收到,如果對端出現超時重傳了FIN,則意味著自己上次發的ACK丟失了,那麼自己還有機會再次傳送ACK確認,乘以2就是為了給重傳的ACK充裕的到達時間。

真是太纏綿了,感天動地。在創造TCP/IP的年代,窄頻寬、高時延、不穩定的網路狀態,這樣的設計相當必要,要分手也得大家都確認才行,愛情片裡太多這樣的誤會了,不學習網路知識生活中是要吃大虧的。

【圖十四 TCP狀態變遷圖】

迴歸正題,前面的基礎知識告訴我們,只有TCP連結的主動關閉方會進入TIME_WAIT狀態,這會給連結主動關閉方所在的TCP/IP協議棧帶來什麼樣的影響呢。歸納一下主要有兩個方面:

1) TCP/IP協議棧隨機埠資源耗盡

鋪墊一個基礎知識:TCP對每個連結用一個四元組(TUPLE)來唯一標識,分別是源IP、目標IP、源埠、目標埠。通常在使用一個特定的目標服務時,目標IP(即伺服器IP)和目標埠(即伺服器知名/私有埠)是固定的,源IP通常也是固定的,因此連結主動發起方TUPLE的最大數量就由源埠的最大數量決定,TCP/IP v4規定埠號是無符號短整型,那麼這個最大值就是65535。

假設一個伺服器即作為TCP連結的主動開啟方(通常是作為客戶端角色,它使用本地隨機分配的臨時埠)又是TCP連結的主動關閉方,則大量主動關閉的連結會進入到TIME_WAIT狀態,如果大夥在這個狀態都折騰60秒(Linux MSL預設為30秒,2MSL為60秒),這臺機器相關的TUPLE資源會被快速佔用、堆積並很快因為(源)埠的65535限制而耗盡,以後該TCP/IP協議棧上執行的其程式作為TCP連結的主動開啟方再想連結同一個目標伺服器時,就只能等待2MSL釋放,從應用角度來看就是連結建立失敗,使用者要承受精神和肉體雙重摺磨,無法接受。

2) TCP/IP協議棧TUPLE相關資料結構大量消耗記憶體

假設一個伺服器作為TCP連結的被動開啟方(通常是作為伺服器角色)和主動關閉方,則大量主動關閉的連結會進入到TIME_WAIT狀態,如果大夥在這個狀態都折騰60秒,本地機器TCP/IP協議棧維護的TUPLE資料項會快速堆積並佔用大量核心記憶體資源,最關鍵的是因為此時TCP四元組碰撞概率極低(因為源IP、源埠大多都是不同的),導致TUPLE的積壓幾乎不受限制而野蠻生長,這對於一個高負載又要求高效能的伺服器而言,感情上是相當痛苦的,肉體上勉強能接受。

基於以上分析,為了提高伺服器網路效能,一些伺服器選擇配置啟用TCP快速回收(真的需要配置嗎,配置真的有效果嗎,後面逐步會談到)來優化效能。然而,新的問題出現了,三藏說:看,現在是妹妹要救姐姐,等一會那個姐姐一定會救妹妹的......恩恩怨怨何時了啊。

【問題1】如果客戶端通過同一個NAT連結應用伺服器時,客戶端TCP連結可能被RESET拒絕或者無響應、響應緩慢。我們來具體分析一下成因,NAT作為代理層面向伺服器時,客戶端側的源IP會被收斂成NAT的地址,通常有三種情況:

1) NAT為公網代理,比如公司內大夥用手機通過WIFI上網就屬於這種模式,邏輯結構類似【圖十五 客戶端通過NAT上網示意】。另有一點背景交待:我們上網衝浪時發起的連結絕大多數都是短連結;

【圖十五 客戶端通過NAT上網示意】

2) NAT為後端伺服器叢集做四層或七層Load Balance(以下簡稱LB),比如HAProxy或LVS的四層NAT模式、Nginx的七層LB模式,典型場景是客戶端HTTP請求經過LB轉發到後端的伺服器叢集。LB與伺服器叢集之間大多也是採用短連結,邏輯結構類似【圖十六 伺服器通過NAT做LB】;

【圖十六 伺服器通過NAT做LB】

3) 上述第1和第2中情況的組合,具體可以參考【圖十七 典型客戶端連線伺服器鏈路示意】,後面會有專門的討論,此處不再贅述;

Linux Kernel的TCP/IP協議棧在開啟TCP連結TIME_WAIT狀態快速回收時,只需等待一個重傳時間(RTO可能很短,甚至都來不及在netstat -ant中看到TIME_WAIT狀態)後就釋放而無需等待通常的2MSL超時。被釋放的TCP連結的TUPLE資訊同時也就就清除了。那麼,問題來了,如果短時間內有新的TCP連結複用了這個TUPLE,就有可能會因為收到之前已釋放的連結上,因延遲而剛剛到達的FIN,從而導致新連結被意外關閉。實際上,還會有鏈路被串接的問題。

為了規避這些問題,TCP/IP協議棧在快速回收釋放TUPLE後,又利用IP層PEER(TCP/IP協議棧中維護的連結對端資料結構)資訊中的對端IP、PEER最後一次TCP資料包文時間戳等資訊(注:對端埠資訊此時已經在TCP層被清除掉了),對TCP連結通過快速回收和重用TUPLE到新連結上做了一系列約束,在RFC1323中有相應的描述。簡單講就是在同時滿足以下條件時,不能重用從TIME_WAIT狀態快速回收的TUPLE,此時的表現是不響應或對SYN請求響應RESET:

1)來自同一臺PEER機器的TCP連結資料包文中攜帶時間戳欄位;

2)之前同一臺PEER機器(僅僅指IP,埠資訊因連結被TCP快速釋放而缺失) 的某個TCP報文曾在60秒之內到過本伺服器;

3)新連結的時間戳小於PEER機器上次TCP到來時的時間戳;

條件已經相當苛刻,碰撞概率應該很低了。但由於只有PEER的IP而缺少PEER的埠資訊作為判斷TCP連結另一端唯一性的約束,不能重用的概率便放大了65535倍。假設PEER是一臺單獨的機器,問題不大,因為一臺機器上的時間戳是單調增長的,一旦出現時光倒流,則可以確定是舊的資料包文延遲了,直接丟掉即可。但是,如果很多客戶端通過同一臺NAT裝置接入進來,那麼問題就嚴重了,因為工作在四層的NAT不會修改客戶端傳送的TCP報文內的時間戳,而客戶端們各自的時間戳又無法保持一致,伺服器只認時間戳最大的那個,其它通通丟掉或者對SYN請求直接響應RESET,太冤了。

我們的業務服務中,典型模式是客戶端使用HTTP短連結通過接入伺服器使用業務服務,且這些接入伺服器基本都是以LB方式在執行,接入伺服器與業務伺服器之間則大多為直接連結或通過代理排程,無論是有線網際網路的B/S架構,還是移動網際網路的C/S架構都是如此。客戶端使用者也大多數都是通過NAT上網的。參考【圖十七 典型客戶端連線伺服器鏈路示意】可以有更直觀的瞭解。

【圖十七 典型客戶端連線伺服器鏈路示意】

基於前述知識,我們以【圖十七 典型客戶端連線伺服器鏈路示意】為基礎來觀察,可以分三種情況討論快速回收配置引數的合理使用:

1) 連結主動開啟方和主動關閉方均為客戶端

a. 如伺服器LB工作在七層且在公網提供服務,則它與HTTP伺服器叢集之間一般都是短連結,此時,伺服器LB符合隨機埠資源耗盡的模式。因為它的時間戳是單調遞增的,故無需擔心連結碰撞,符合 TCP快速回收重用的條件,但由於伺服器LB部署在公網對客戶端提供服務,客戶端有可能通過NAT代理訪問外部網路,便無法保證時間戳單調遞增,故建議關閉TCP快速回收選項;

b. 如伺服器LB工作在四層模式,自身不受影響,故關閉TCP快速回收選項;

c. HTTP伺服器叢集與層級靠後的業務伺服器之間大多都是短連結,HTTP伺服器的情況與前述第a點類似,如果它在七層伺服器LB之後部署,且與層級靠後的業務伺服器之間沒有NAT,則可以考慮啟用TCP快速回收選項,除此之外,都建議關閉TCP快速回收選項;

2) 內網伺服器(業務伺服器、邏輯代理伺服器等)之間有相互呼叫時,建議優先採用長連結方案。如果確實需要使用短連結方案時,則層級靠前的伺服器往往即是連結的主動開啟方,又是連結的主動關閉方,符合隨機埠資源耗盡的模式。考慮到單臺伺服器能確保自己時間戳單調遞增,開啟tcp_tw_recycle也能符合TCP快速回收重用的條件,且不用擔心碰撞,因此建議啟用TCP快速回收選項。這裡需要注意兩個特殊情況:

a. 如果層級靠前的伺服器有一端直接在公網為客戶端提供服務,而客戶端有可能通過NAT代理訪問外部網路,則不宜啟用TCP快速回收選項;

b. 如果層級靠前的伺服器與層級靠後的伺服器之間有四層NAT隔離,也需要謹慎考慮。除非伺服器間系統時鐘同步精準,能確保層級靠前的伺服器叢集總體時間戳在毫秒級的精度上能單調遞增,否則建議關 閉TCP快速回收選項;

3) 伺服器叢集被模擬客戶端邏輯攻擊,此時伺服器會主動關閉連結,從而導致大量出現TIME_WAIT狀態,伺服器因此符合TCP/IP協議棧TUPLE相關資料結構記憶體大量消耗的模式但,考慮到客戶端可能處在NAT之後,建議保持關閉TCP快速回收選項。我們應利用提前部署的安全機制在TCP三次握手期間及早拒絕連結來解決此類問題;

服務端系統架構千變萬化,較難窮舉,總結一下上述的討論:

1) 伺服器如果直接在公網服務於客戶端時,因為客戶端有可能通過NAT代理訪問外部網路,故建議關閉TCP快速回收選項;

2) 伺服器各層級在內網互聯時,同時作為連結的主動發起方和連結的主動關閉方,建議開啟TCP快速回收。上述建議例外場景是:如伺服器層級之間有4層NAT,則需要考察層級靠前的伺服器叢集時鐘同步的精度水平是否能到毫秒級,通常建議關閉TCP快速回收選項;

【問題2】CMWAP轉發的包時間戳有亂跳的情況,也會遇到類似問題1的現象。因為現在WAP的使用者越來罕見,就不展開了;

⑥ HTTP協議:開啟SOCKET的TCP_NODELAY選項

TCP/IP協議棧為了提升傳輸效率,避免大量小的資料包文在網路中流竄造成擁塞,設計了一套相互協同的機制,那就是Nagle's Algorithm和TCP Delayed Acknoledgement。

Nagle演算法(Nagle's Algorithm)是以發明人John Nagle的名字來命名。John Nagle在1984年首次用這個演算法來嘗試解決福特汽車公司的網路擁塞問題(RFC 896),該問題的具體描述是:如果我們的應用程式一次產生1個位元組的資料(典型的如telnet、XWindows等應用),而這個1個位元組資料又以網路資料包的形式傳送到遠端伺服器,那麼就很容易使網路中有太多微小分組而導致過載。

因為傳輸1個位元組有效資料的微小分組卻需花費40個位元組的額外開銷(即IP包頭20位元組 + TCP包頭20位元組),這種有效載荷利用率極其低下的情況被統稱為愚蠢視窗症候群(Silly Window Syndrome),前面我們在談MSS時也提到過,如果為一頭豬開個大卡車跑一趟,也夠愚鈍的。對於輕負載廣域網或者區域網來說,尚可接受,但是對於重負載的廣域網而言,就極有可能引起網路擁塞導致癱瘓。

Nagle演算法要求一個TCP連結上最多隻能有一個未被確認的小分組(資料長度小於MSS的資料包),在該分組的確認到達之前不能再傳送其它小分組。此時如果應用層再有新的寫入資料,TCP/IP協議棧會蒐集這些小分組並快取下來,待以下時機發出:

1) 收到接收端對前一個資料包文的ACK確認;

2) 當前資料屬於緊急資料;

3) 蒐集的資料達到或超過MSS;

【圖十八 Nagle演算法未開啟和開啟資料包文互動示意】對比了Nagle演算法未開啟(左側圖示)和開啟(右側圖示)的資料包文互動過程。

【圖十八 Nagle演算法未開啟和開啟資料包文互動示意】

TCP Delayed Acknoledgement 也是為了類似的目的被設計出來的,它的作用就是延遲ACK包的傳送,使得TCP/IP協議棧有機會合並多個ACK或者使ACK可以隨著響應資料一起返回,從而提高網路效能。TCP Delayed Acknoledgement定義了一個超時機制,預設超時時間是40ms,超過這個時間,則不再等待立即傳送延遲的ACK。

如果一個TCP連線的一端啟用了Nagle's Algorithm,而另一端啟用了TCP Delayed Acknoledgement,而傳送的資料包又比較小,則可能會出現這樣的情況:傳送端在等待接收端對上一個資料包文的ACK才傳送新的資料包文,而接收端則正好延遲了這個ACK的傳送,那麼正要被髮送的新資料包文也就同樣被延遲了。

上述情況出現的前提是TCP連線的傳送端連續兩次呼叫寫SOCKET介面,然後立即呼叫讀SOCKET介面時才會出現。那麼為什麼只有 Write-Write-Read 時才會出現問題,我們可以分析一下Nagle's Algorithm的虛擬碼:

if there is new data to send  if the window size >= MSS and available data is >= MSS

    send complete MSS segment now  else

    if there is unconfirmed data still in the pipe

      enqueue data in the buffer until an acknowledge is received    else

      send data immediately

    end if

  end ifend if

程式碼顯示,當待傳送的資料比 MSS 小時,先判斷此時是否還有未ACK確認的資料包文,如果有則把當前寫的資料放入寫緩衝區,等待上個資料包文的ACK到來。否則立即傳送資料。對於Write-Write-Read的呼叫秩序,傳送端第一個Write會被立刻傳送,此時接收端TCP Delayed Acknoledgement機制期待更多的資料到來,於是延遲ACK的傳送。傳送端第二個Write會命中傳送佇列中還有未被ACK確認的資料的邏輯,所以資料被快取起來。這個時候,傳送端在等待接收端的ACK,接收端則延遲了這個ACK,形成互相等待的局面。後面等到接收端延遲ACK超時(比如40ms),接收端就會立即發出這個ACK,這才能觸使傳送端快取的資料包文被立即發出。

現代TCP/IP 協議棧預設幾乎都啟用了這兩個功能。

我們在移動APP的設計實現中,請求大部分都很輕(資料大小不超過MSS),為了避免上述分析的問題,建議開啟SOCKET的TCP_NODELAY選項,同時,我們在程式設計時對寫資料尤其要注意,一個有效指令做到一次完整寫入(後面會講協議合併,是多個指令一次完整寫入的設計思想),這樣伺服器會馬上有響應資料返回,順便也就捎上ACK了。

3.1.2.   接入排程


① 就快接入


在客戶端接入伺服器排程策略的演化過程中,我們最早採用了“就近接入”的策略,在距離客戶端更近的地方部署伺服器或使用CDN,期望通過減少RTT來提高網路互動響應效能。這個策略在國內的落地執行還需要加一個字首:“分省分運營商”,這就給廣大負責IDC建設的同學帶來了巨大的精神和肉體折磨。


在持續運營的過程中,根據觀察到的資料,發現並非物理距離最近的就是最快的。回憶一下前面談到的吞吐量指標BDP,它與鏈路頻寬和RTT成正比關係,而RTT是受物理距離、網路擁塞程度、IDC吞吐量、跨網時延等諸多因素綜合影響的,單純的就近顯然不夠精細了。


“就快接入”在“就近接入”策略的基礎上改善提升,它利用客戶端測速和報告機制,通過後臺大資料分析,形成與客戶端接入IP按就快原則匹配接入伺服器的經驗排程策略庫,令客戶端總能優先選擇到最快的伺服器接入點。


對於接入伺服器,我們按照訪問目標資料屬性緯度的不同,可以分為至少兩個集合,它們分別是:


1)業務邏輯伺服器集合;


2)富媒體伺服器集合,富媒體包括頭像、圖片和視訊等尺寸比較大的資料;


這兩類伺服器集合通常由獨立的接入排程FSM管理。


客戶端在訪問不同的資料型別時使用不同的伺服器集合,這樣的劃分體現了輕重分離、信令和資料分離的架構理念。


每個伺服器集合又可按接入排程的優先秩序劃分為三個子列表:


1)        【動態伺服器列表】


伺服器按策略(比如就快接入)並結合裝置負載情和容量情況、網路容量情況綜合計算下發的一系列伺服器IP地址,某些產品還會在動態伺服器列表靠後的部分加上動態伺服器域名(該域名與靜態伺服器域名列表內容不同,是一種動態擴充套件方式),對於下載類業務,動態伺服器列表最後會包含動態回源伺服器IP地址等。客戶端應當持久化儲存動態伺服器列表,並在APP啟動時載入到記憶體快取中,其快取索引的KEY通常是網路型別,對於WIFI網路,KEY的內容中再加上一個SSID,以便區分不同的WIFI熱點。客戶端在持久化和記憶體中基於不同的KEY快取3 ~ 5組(建議值,可根據業務特點靈活選擇和配置)動態伺服器列表資料,並按照LRU方式做更新淘汰;


2)        【靜態伺服器域名列表】


預埋在客戶端持久化儲存中,在首次啟動APP或動態伺服器列表訪問全部失敗時使用;


3)        【靜態伺服器IP列表】


預埋在客戶端持久化儲存中,其主要價值在於,當使用客戶端遇到動態伺服器列表和靜態伺服器域名列表訪問都出現異常時,有最低限度的可用性保障。靜態伺服器IP列表貴精不貴多,能分別服務國內和海外使用者即可。對於下載類業務,靜態伺服器IP列表最後還有包含靜態回源伺服器IP地址;


每個伺服器列表都包含一批列表項,一般為2 ~ 3個。每個伺服器列表中的列表項按照優先順序從前到後排列,故也需維護一個自己獨立的排程機制,我們稱之為伺服器列表排程FSM。


基於以上的分類基礎,客戶端和伺服器接入排程機制的具體的做法通常為:


1)        客戶端實現接入排程FSM模型和伺服器列表排程FSM模型,這兩個FSM是巢狀關係,可以理解為外迴圈和內迴圈的關係,就好比地球圍著太陽公轉的時候也沒耽誤自轉;


2)        客戶端儲存預埋業務邏輯和富媒體兩個伺服器集合,每個伺服器集合都包含靜態伺服器域名列表和靜態伺服器IP列表;


3)        伺服器實現就快接入排程演算法,依託非同步計算持續更新的經驗排程策略庫,進行動態匹配計算;


4)        客戶端和伺服器共同實現一套動態伺服器列表下發和更新機制;


5)        實踐中有些伺服器還要求客戶端支援302跳轉的能力,這個邏輯機制上可以有,策略上不提倡;


我們先考察接入排程FSM,如【圖十九 接入排程FSM示意】,它的狀態變遷驅動力來自:


1)        當前狀態下相應的伺服器列表無有效資料(資料項為空或全部試完一輪);


2)        伺服器下發了新的動態伺服器列表;


接入排程FSM狀態變遷的原則是:


1)        客戶端首次使用時,接入排程FSM狀態入口在靜態伺服器域名列表;


2)        客戶端在冷啟動(除首次使用)、熱啟動時,接入排程FSM狀態入口在動態伺服器列表。動態伺服器列表通常在冷啟動時從本地持久化快取載入,在記憶體快取中會被伺服器下發的資料更新,一旦更新,客戶端應擇機持久化到本地儲存中;


3)        接入排程FSM狀態變遷時,以進入伺服器下發的動態伺服器列表狀態為最高優先順序,即三個伺服器列表發生狀態變遷時,都先向伺服器動態列表跳轉;


4)        第3點之特例:當剛從動態伺服器列表變遷到靜態伺服器域名列表且未收到伺服器下發新的動態伺服器列表時,靜態伺服器域名列表變遷的下一站是靜態伺服器IP列表。這裡要特別談一下前面那個時間限定詞“剛”,這個前提設定的原因是行動網路易抖動,1分鐘前動態伺服器伺服器列表不可用不代表5分鐘後依然不可用,因此,我們把這個“剛”設定為:一直在前臺執行的5分鐘以內的時間;


5)        特別的,如果是因為伺服器下發新的動態伺服器列表導致狀態變遷,那麼接入排程FSM狀態要置位還原,重新按第2條原則執行;





【圖十九 接入排程FSM示意】


我們以動態伺服器列表為例來考察伺服器列表排程FSM,先說明一下,同其他兩個列表不同的是,動態伺服器列表中的列表項數量完全由伺服器下發時控制。如【圖二十 動態伺服器列表排程FSM】所示,伺服器列表排程FSM的狀態變遷驅動力來自:


1)        連結建立失敗或超時;


2)        連結建立成功但收發資料錯誤(包含網路型別切換、無網路等)或超時;


3)        伺服器下發新的動態伺服器列表;


伺服器列表排程FSM狀態變遷的原則為:


1)        APP冷啟動時,伺服器列表排程FSM狀態全部重新置位,按第2條原則  執行;


2)        客戶端由前到後順序嘗試伺服器列表中的資料項,不可逆向執行、不可亂序執行;


3)        客戶端嘗試一遍本伺服器列表所有資料項,如果全部失敗,則退出這個伺服器列表排程FSM,進入到接入排程FSM;


4)        連結建立失敗(建議要再做1 ~ 2次重試,重試間隔3 ~ 5s,這兩個引數   雲端應該可配可控,相關詳細討論可參考3.1.3鏈路管理)或超時、連結     建立成功收發資料錯誤或超時、伺服器下發新的動態伺服器列表時,服務    器列表排程FSM狀態要變遷;


5)        特別的,如果是因為伺服器下發新的動態伺服器列表導致狀態變遷,那麼伺服器列表排程FSM狀態要置位還原,重新按第2條原則執行;





【圖二十 動態伺服器列表排程FSM】


客戶端接入排程首要目標是確保可用性,其次是選擇最快的鏈路。客戶端無論同哪個集合中哪個伺服器列表的接入伺服器建立連結,伺服器都應按照就快策略的標準評判此時客戶端選擇的伺服器接入點是否符合要求,有沒有更快的接入點,如果有,就隨著業務資料響應一併下發至客戶端,客戶端同步更新動態伺服器列表的資料,驅動排程FSM和伺服器列表排程FSM發生狀態變遷,使得下次再發起伺服器訪問時能使用更優的接入服務,接入鏈路切換時機這裡有三個方案可供探討(後續鏈路管理也會有相關的討論):


1)        直接關閉當前鏈路,立即嘗試使用新的動態伺服器列表建立連結;


2)        直接關閉當前鏈路,當有網路訪問時嘗試使用新的動態伺服器列表建立連結;


3)        保持當前鏈路,立即嘗試使用新的動態伺服器列表建立連結,一旦成功,馬上切換新的業務請求到新鏈路上,然後在舊鏈路空閒時將其關閉;


實踐中可以根據APP的特點來選擇鏈路切換方案。


那麼,客戶端報告什麼樣的資料可以作為伺服器排程策略計算的依據呢?


1)        網路型別,比如WIFI、2/3/4G等,WIFI時多提供一份SSID資訊;


2)        接入IP歸屬,比如電信、聯通、移動、海外及其所屬省市等,注:歸屬由伺服器判斷;


3)        目標域名,用於服務端校驗訪問目標和自己提供的服務是否匹配;


4)        訪問目標服務時的測速資料(IO次數、每次IO位元組和耗時、RTT估算值等)和服務質量資料(如接入排程FSM狀態、伺服器地址、連結成功或失敗、連結成功所需時長、連結失敗錯誤碼、重試次數等);


說了半天,這一切的基礎是我們要部署足夠多和廣的伺服器接入點,也可以使用CDN,依託在一個分省市分運營商甚至覆蓋全球的IP庫和通過大量客戶端測速報告的業務質量統計資料計算出來的、接入IP按就快原則匹配接入伺服器的經驗排程策略庫之上。


總結一下與就快接入相關的內容:


1)        伺服器分省分運營商分國內外的部署及使用CDN,廣度和深度並舉;


2)        客戶端測速報告及服務質量監控報告,測速這個話題,稍微多探討一下,在有                     線網路,實時測速並調整排程策略資料是非常普通的方案,但放在行動網路條件下,就有重新思考的必要。行動網路易抖動和移動應用大部分短連結輕量互動的特點,使得我們很難在一個短的時間內做出網路速度的有效判斷,即便有初步的判斷,也可能因為沒有馬上使用的時機而導致過期失效。因此,我們更傾向於把這些質量資料包告到後臺,通過大量的資料歸併分析,形成接入速度排程策略的判斷依據;


3)        客戶端接入IP庫與接入伺服器就快排程匹配庫需要持續更新;


4)        伺服器排程中儘量減少302跳轉,做到一擊即中;


② 去DNS的IP直連:


DNS不但需要1個RTT的時間消耗,而且行動網路下的DNS還存在很多其它問題:


1)        部分DNS承載全網使用者40%以上的查詢請求,負載重,一旦故障,影響巨大,這樣的案例在PC網際網路也有很多,Google一下即可感受觸目驚心的效果;


2)        山寨、水貨、刷ROM等移動裝置的LOCAL DNS設定錯誤;


3)        終端DNS解析濫用,導致解析成功率低;


4)        某些運營商DNS有域名劫持問題,實際上有線ISP也存在類似問題。域名劫持對安全危害極大,產品設計時要注意服務端返回資料的安全校驗(如果協議已經建立在安全通道上時則不用考慮,安全通道可以基於HTTPS或者私有安全體系)。對於劫持的判斷需要客戶端報告實際拉取服務資料的目標地址IP等資訊;


5)        DNS汙染、老化、脆弱;


綜上就是在前述就快接入小節中,接入排程FSM會優先使用動態伺服器列表的原因。


③ 網路可達性探測


在連線建立過程中如果出現連線失敗的現象,而終端系統提供的網路狀態介面反饋網路可用時,我們需要做網路可達性探測(即向預埋的URL或者IP地址發起連線嘗試),以區別網路異常和接入服務異常的情況,為定位問題,優化後臺接入排程做資料支援。


探測資料可以非同步報告到伺服器,至少應該包含以下欄位:


1)        探測事件ID,要求全域性唯一不重複;


2)        探測發生時間;


3)        探測發生時網路型別和其它網路資訊(比如WIFI時的SSID等);


4)        本地排程的接入伺服器集合型別;


5)        本地排程的接入伺服器IP(如使用域名接入,可忽略);


6)        探測的目標URL或IP地址


7)        本次探測的耗時;


3.1.3.   鏈路管理


鏈路就是運肥豬的高速路,就快接入是選路,鏈路管理就是如何高效的使用這條路。下面是一些實踐總結:


① 鏈路複用


我們在開篇討論無線網路為什麼慢的時候,提到了連結建立時三次握手的成本,在無線網路高時延、頻抖動、窄頻寬的環境下,使用者使用趨於碎片化、高頻度,且請求響應又一次性往返居多、較頻繁發起等特徵,建鏈成本顯得尤其顯著。


因此,我們建議在鏈路建立後可以保持一段時間,比如HTTP短連結可以通過HTTP Keep-Alive,私有協議可以通過心跳等方式來保持鏈路。具體要點建議如下:


1)        鏈路複用時,如果服務端按就快策略機制下發了新的接入動態伺服器列表,則應該按照接入排程FSM的狀態變遷,在本次互動資料完成後,重建與新的接入伺服器的IP鏈路,有三個切換方案和時機可選擇:


a.         關閉原有連結,暫停網路通訊,同時開始建立與新接入伺服器的TCP鏈路,成功後恢復與伺服器的網路互動;


b.         關閉原有連結,暫停網路通訊,待有網路互動需求時開始建立與新接入伺服器的IP鏈路;


c.         原有連結繼續工作,並同時開始建立與新接入伺服器的TCP鏈路,成功後新的請求切換到新建鏈路上,這個方式或可稱為預建連結,原連結在空閒時關閉;


2)        鏈路複用時區分輕重資料通道,對於業務邏輯等相關的信令類輕資料通道建議複用,對於富媒體拉取等重資料通道就不必了;


3)        鏈路複用時,如與協議合併(後面會討論)結合使用,效果更佳;


② 區分網路型別的超時管理


在不同的網路型別時,我們的鏈路超時管理要做精細化的區別對待。鏈路管理中共有三類超時,分別是連線超時、IO超時和任務超時。我們有一些經驗建議,提出來共同探討:


1)        連線超時:2G/3G/4G下5 ~ 10秒,WIFI下5秒(給TCP三次握手留下1次超時重傳的機會,可以研究一下《TCP/IP詳解 卷一:協議》中TC P的超時與重傳部分);


2)        IO超時:2G/3G/4G下15 ~ 20秒(無線網路不穩定,給抖動留下必要的恢復和超時重傳時間),WIFI下15秒(1個MSL);


3)        任務超時:根據業務特徵不同而差異化處理,總的原則是前端面向使用者互動界                     面的任務超時要短一些(儘量控制在30秒內並有及時的反饋),後臺任務可以長一些,輕資料可以短一些,重資料可以長一些;


4)        超時總是伴隨著重試,我們要謹慎小心的重試,後面會討論;


超時時間宜短不宜長,在一個合理的時間內令當前鏈路因超時失效,從而驅動排程FSM狀態的快速變遷,效率要比痴痴的等待高得多,同時,在使用者側也能得到一個較好的正反饋。


各類超時引數最好能做到雲端可配可控。


③ 優質網路下的併發鏈路


當我們在4G、WIFI(要區分是WIFI路由器還是手機熱點)等網路條件較優時,對於請求佇列積壓任務較多或者有重資料(富媒體等下載類資料)請求時,可以考慮併發多個鏈路並行執行。


對於單一重資料任務的多連結併發協同而言,需要伺服器支援斷點續傳,客戶端支援任務協同排程;


④ 輕重鏈路分離


輕重鏈路分離,也可以說是信令和資料分離,目的是隔離網路通訊的過程,避免重資料通訊延遲而阻塞了輕資料的互動。在使用者角度看來就是資訊在非同步載入,控制指令響應反饋及時。


移動端大部分都是HTTP短連結模式工作,輕重資料的目標URL本身就不同,比較天然的可以達到分離的要求,但是還是要特別做出強調,是因為實踐中有些輕資料協議設計裡面還會攜帶類似頭像、驗證碼等的實體資料。


⑤ 長連結


長連結對於提升應用網路互動的及時性大有裨益,一方面使用者使用時,節省了三次握手的時間等待,響應快捷;另一方面伺服器具備了實時推送能力,不但可以及時提示使用者重要資訊,而且能通過推拉結合的非同步方案,更好的提升使用者體驗。


長連結的維護包括連結管理、連結超時管理、任務佇列管理等部分,設計實施複雜度相對高一些,尤其是在行動網路環境下。為了保持鏈路還需要做心跳機制(從另外一個角度看,這也是針對簡單資訊一個不錯的PULL/PUSH時機,,但需注意資料傳輸要夠輕,比如控制在0.5KB以內),而心跳機制是引入長連結方案複雜度的一個重要方面,行動網路鏈路環境複雜,國內閘道器五花八門,鏈路超時配置各有千秋,心跳時長選擇學問比較大,不但要區分網路型別,還得區分不同運營商甚至不同省市,歷史上曾經實踐了2分鐘的心跳間隔,最近比較多的產品實踐選擇4.5分鐘的心跳間隔。而且長連結除了給行動網路尤其是空中通道帶來負擔外,移動裝置自身的電量和流量也會有較大的消耗,同時還帶來後端頻寬和伺服器投入增加。所以,除了一些粘性和活躍度很高、對資訊到達實時性要求很高的通訊類APP外,建議謹慎使用長連結,或可以考慮採用下面的方式:


1)        退化長連結:即使用者在前臺使用時,保持一個長連結鏈路,活躍時通過使用者使                     用驅動網路IO保持鏈路可用;靜默時通過設定HTTP Keep-Alive方式,亦或通過私有協議心跳方式來保持鏈路。一旦應用切換後臺,且在5~10分鐘內沒有網路互動任務則自行關閉鏈路,這樣在使用者互動體驗和資源消耗方面取得一個平衡點;


2)        定時拉取/詢問:對於一些有PUSH需求的APP,我們可以採用一個雲端可配置間隔時長的定時拉取/詢問方案。有三個重點,一是定時的間隔雲端可以配置,下發更新到客戶端後下次生效;二是拉取/詢問時,如果下發的指令有要求進一步PULL時,可以複用已建立的鏈路,即前述退化長連結的模式;三是定時拉取/詢問時機在客戶端要做時間上的均勻離散處理,避免大的併發查詢帶來頻寬和負載的巨大毛刺;


3)        如果可能,優先使用OS內建的PUSH通道,比如iOS的APNS、Andriod的                 GCM(Google這個以工程師文化著稱的公司,在做OS級基礎設施建設時,卻表現出了很差的前瞻性和系統思考的能力,GCM的前身C2DM都沒怎麼普及使用就被替換了,這也意味著Android各種版本PUSH能力不 一致的問題。但無論怎麼說,OS級的基礎設施無論在效能、穩定性還是在效率上都會優於APP層自己實現的方案),實施推拉結合的方案。特別要提到的一點是,中國特色無所不在,國內運營商曾經封過APNS的PUSH埠2195,也會干擾GCM的埠5528,更別提這些底層服務的長連結會被運營商干擾。對於Android平臺,還存在系統服務被各種定製修改的問題。別擔心,辦法總比問題多,保 持清醒;


⑥ 小心重試


自動重試是導致後臺雪崩的重要因素之一。在行動網路不穩定的條件下,大量及時的重試不但不能達到預期,反而無謂的消耗移動裝置的電量甚至流量。因此,我們在重試前要有一些差異化的考慮:


1)        當前移動裝置的網路狀況如何,如果沒有網路,則不必重試;


2)        重試設定必要的時間間隔,因為移動接入網路抖動到恢復可能需要一點時間,馬上重試並非最佳策略,反而可能無謂的消耗電量。實踐中,可以在一次連線或IO失敗(立即失敗或超時)時,過3 ~ 5秒後再試;


3)        重試應設定必要的總時限,因為三個伺服器列表比較長,每個伺服器地址都要重試和等待若干次,最終可能導致接入排程FSM和伺服器列表排程FSM流轉耗時過長,此時使用者側體驗表現為長時間等待無響應。總時限引數可以參考前述區分網路型別的超時管理中的任務超時值。一旦某次重試成功,重試總時限計時器要歸零;


4)        伺服器下發特定錯誤碼(比如伺服器故障、過載或高負載)時,提示客戶端停止重試並告知安撫使用者,我們在強監控這個主題下有詳細的討論;


每個目標伺服器地址的重試次數、重試總時限和重試時間間隔最好能做到雲端可配可控。


特別需要提出的一點是,移動APP採用HTTP短連結模式實現CS互動時,廣泛的使用了系統原生元件或者開源元件,這些友好的模組把超時和重試都封裝起來,其預設值是否適合自己的業務特點,需要多多關注。使用前,最好能知其然更知其所以然。


⑦ 及時反饋


透明和尊重,會帶來信任和默契,家庭如此、團隊如此、使用者亦如此。欲蓋彌彰和裝傻充愣也許短暫取巧,拉長時間軸來看,肯定要付出慘重的代價。及時和真誠的告知狀況,贏得諒解和信任,小付出,大回報,試過都知道。


當發現因為網路不存在或者其它屬於移動端裝置鏈路的異常時,應該及時和顯著的提示使用者,讓使用者注意到當前有諸如網路不存在、FREE WIFI接入認證頁面需確認等等問題,使使用者可以及時處理或理解問題狀態。


當發現是伺服器問題時,應及時、顯著和真誠的告知使用者,爭取使用者的諒解。


網路異常提示或伺服器故障通告等資訊的呈現要做到一目瞭然,無二義和二次互動。


我們在強監控這個主題下有詳細的方法討論。

3.1.4. IO管理

基於一個快速和高效管理的鏈路之上,做好IO排程和控制,也是提升效能和改善使用者體驗的重要環節。要探討的內容包括:

① 非同步IO

非同步化IO的目的就是避免資源的集中競爭,導致關鍵任務響應緩慢。我們在後面差異服務個大的分類中會重點探討。這裡特別先提出來,是建議在程式架構頂層設計時,要在整體機制上支援非同步化,設計必要的非同步匯流排來聯絡各個層級模組,匯流排可能會涉及包括佇列管理(優先順序、超時、CRUD等)、事件驅動、任務排程等。

非同步IO除了網路方面外,對移動裝置,我們還特別要考慮一下磁碟IO的非同步。因為頻繁、大吞吐量的磁碟IO會造成APP的UI卡頓,從使用者體驗上看就是互動響應遲鈍或者滑動幀率下降。一般來說,磁碟IO非同步會選用空間換時間的方案,即快取資料批量定時寫入磁碟。

② 併發控制

有了非同步IO,併發控制就顯得尤為重要。把非同步機制當作銀彈任意使用,就如同我們給移動APP設計了一個叫“發現”的地方一樣,很可能各種膨脹的需求、不知道如何歸類的需求就紛至沓來,期待有朝一日被“發現”。

非同步IO提供了一個很好的發射後不用管的機制,這就會造成使用者的膨脹,無論是否必要、無論輕重緩急,把請求一股腦的丟給非同步佇列,自己瀟灑的轉身就走。這樣不但會帶來效率和互動響應效能的下降,也會造成資源的無謂消耗。

在後面多非同步這個大分類的討論中會涉及到輕重緩急的話題,在前述非同步IO的磁碟IO的時空效率轉換話題中,還應該包括IO併發的控制,我們即不能因為併發過多的鏈路造成網路頻寬的獨佔消耗影響其它APP的使用,也不可因快速、大量的非同步資料造成緩寫機制形同虛設或是佔用過大的記憶體資源。

③ 推拉結合

PUSH機制應該是蘋果公司在移動裝置上取得輝煌成就的最重要兩個機制之一,另外一個是移動支付體系。我們這裡的討論不包括iOS和APPLE移動裝置的擬人化互動體驗,只側重根基性的機制能力。APNS解決了資訊找人的問題,在過去,只有運營商的簡訊有這個能力,推送和拉取使得我們具備了實時獲取重要資訊的能力。

為何要推拉結合。因為系統級的推送體系也必須維持一個自己的鏈路,而這個鏈路上要承載五花八門的APP推送資料,如果太重,一方面會在設計上陷入個性化需求的繁瑣細節中,另外一方面也會造成這條鏈路的擁堵和效能延遲。因此,通過PUSH通知APP,再由APP通過自己的鏈路去PULL資料,即有效的利用了PUSH機制,又能使得APP能按需使用網路,不但簡化了鏈路管理,而且節省了電量和流量。

④ 斷點續傳

一方面,在討論鏈路管理時,我們建議了優質網路下的併發鏈路來完成同一個重資料拉取任務。這就會涉及到任務的拆分和並行執行,基礎是後臺能支援斷點續傳。

另外一方面,從客戶端的角度而言,行動網路的不穩定特點,可能會造成某個重資料拉取任務突然失敗,無論是自動重試還是使用者驅動的重試,如果能從上次失效的上下文繼續任務,會有省時間、省電量和省流量的效果,想想也會覺得十分美好。

3.2. 輕往復

“技”止此爾。強調網路互動的“少”,更應強調網路互動的“簡”。 我們在一條高時延易抖動的通道上取得效率優勢的關鍵因素就是減少在其上的往復互動,最好是老死不相往來(過激),並且這些往復中交換的資料要儘量的簡潔、輕巧,輕車簡從。這個概念是不是有點像多幹多錯,少幹少錯,不幹沒錯。

把我們實踐過的主要手段提出來探討:

① 協議二進位制化

二進位制比較緊湊,但是可讀性差,也因此形成可維護性和可擴充套件性差、調測不便的不良印象。這也造成了大量可見字符集協議的出現。計算機是0和1的世界,她們是程式猿的水和電,任何一個整不明白,就沒法愉快的生活了。

② 高效協議

高效的協議可以從兩個層面去理解,一是應用層標準協議框架,二是基於其上封裝的業務層協議框架,有時候也可以根據需要直接在TCP之上把這兩個層面合併,形成純粹的業務層私有協議框架。不過,為了簡化網路模組的通訊機制和一些通用性、相容性考慮,目前大多數情況下,我們都會選擇基於HTTP這個應用層標準協議框架之上承載業務層協議框架。下面我們針對上述兩個層面展開探討。

首先是應用層的標準協議優化,比如HTTP/1.1的Pipeline、WebSocket(在HTML5中增加)、SPDY(由Google提出)、HTTP/2等,其中特別需要關注的是處在試驗階段的SPDY和草案階段的HTTP/2。

SPDY是Google為了規避HTTP/1.1暨以前版本的侷限性開展的試驗性研究,主要包括以下四點:

1) 鏈路複用能力,HTTP協議最早設計時,選擇了一問一答一連線的簡單模式,這樣對於有很多併發請求資源或連續互動的場景,鏈路建立的數量和時間成本就都增加了;

2) 非同步併發請求的能力,HTTP協議最早的設計中,在拉取多個資源時,會對應併發多個HTTP鏈路(HTTP/1.1的Pipeline類似)時,服務端無法區分客戶端請求的優先順序,會按照先入先出(FIFO)的模式對外提供服務,這樣可能會阻塞客戶端一些重要優先資源的載入,而在鏈路複用的通道上,則提供了非同步併發多個資源獲取請求指令的能力,並且可以指定資源載入的優先順序,比如CSS這樣的關鍵資源可以比站點ICON之類次要資源優先載入,從而提升速度體驗;

3) HTTP包頭欄位壓縮(注:特指欄位的合併刪減,並非壓縮演算法之意)精簡,HTTP協議中HEAD中欄位多,冗餘大,每次請求響應都會帶上,在不少業務場景中,傳遞的有效資料尺寸遠遠小於HEAD的尺寸,頻寬和時間成本都比較大,而且很浪費;

4) 伺服器端具備PUSH能力,伺服器可以主動向客戶端發起通訊向客戶端推送資料;

HTTP/2由標準化組織來制定,是基於SPDY的試驗成果開展的HTTP協議升級標準化工作,有興趣瞭解詳細情況可以參考HTTP/2的DRAFT文件。

其次是業務層的協議框架優化,它可以從三個方面考察,一是協議處理效能和穩定性好,包括諸如協議緊湊佔用空間小,編碼和解碼時記憶體佔用少CPU消耗小計算快等等,並且bad casae非常少;二是可擴充套件性好,向下相容自不必說,向上相容也並非不能;三是可維護性強,在協議定義、介面定義上,做到可讀性強,把二進位制協議以可讀字元的形式展示,再通過預處理轉化為原始碼級檔案參與工程編譯。可能會有同學強調協議調測時的可閱讀、可理解,既然讀懂01世界應該是程式設計師的基本修養,這一項可能就沒那麼重要了。

高效的業務層協議框架從分散式系統早期代表Corba的年代就有很多不錯的實踐專案,目前最流行的開源元件應屬ProtoBuf,可以學習借鑑。

正所謂殊途同歸、心有靈犀、不謀而合,英雄所見略同......,說來說去,高效協議的優化思路也都在鏈路複用、推拉結合、協議精簡、包壓縮等等奇技淫巧的範疇之內。

③ 協議精簡

協議精簡的目的就是減少無謂的資料傳輸,提升網路效能。俗話說“千里不捎針”,古人誠不我欺也。我們實踐總結以下三點供參考:

1) 能不傳的就不傳。把需要的和希望有的資料都列出來,按照對待產品需求的態 度,先砍掉一半,再精簡一半,估計就差不多了。另外,高效協議提供了比較好的擴充套件性,預留欄位越少越好,移動網際網路演化非常快,經常會發現前瞻的預留總是趕不上實際的需求;

2) 抽象公共資料。把各協議共性的屬性資料抽象出來,封裝在公共資料結構中, 即所謂包頭一次就傳一份,這個想法不新鮮,TCP/IP的設計者們早就身體力行了。除了帶來資料冗餘的降低外,還降低了維護和擴充套件的複雜度,一石二鳥,且抽且行;

3) 多用整數少用字元,數字比文字單純,即簡潔又清晰,還不需要擔心英文不好被後繼者BS;

4) 採用增量技術,通知變化的資料,讓接收方處理差異,這是個很好的設計思想,實踐中需要注意資料一致性的校驗和保障機制,後面會有專門的細節討論;

④ 協議合併

協議合併的目標是通過將多條互動指令歸併在一個網路請求中,減少鏈路建立和資料往復,提升網路效能。把實戰總結的六點提出來供參考:

1) 協議合併結合協議精簡,效率翻番;

2) 協議合併的基礎是業務模型的分析,在分類的基礎上去做聚合。首先得區分出來緩急,把實時和非同步的協議分類出來分別去合併;其次得區分出來輕重,協議請求或協議響應的資料規模(指壓縮後),儘量確保在一個資料包文中可完成推拉;

3) 協議合併在包的封裝上至少有兩種選擇,一是明文協議合併後統一打包(即壓縮和解密);二是明文協議分別打包,最後彙總;前者效率高一些,在實戰中用的也較普遍;後者為流式處理提供可能;

4) 協議合併對伺服器的非同步處理架構和處理效能提出了更高的要求,特別需要權衡網路互動效率和使用者對後臺處理返回響應期待之間的取捨;

5) 協議間有邏輯順序關係時,要認真考慮設計是否合理或能否合併;

6) 重資料協議不要合併;

⑤ 增量技術

增量技術準確分類應該算是協議精簡的一個部分,它與業務特點結合的非常緊密,值得單獨討論一下。增量技術在CS資料流互動比較大的時候有充分發揮的空間,因為這個技術會帶來客戶端和伺服器計算、儲存的架構複雜度,增加資源消耗,並且帶來許多保障資料一致性的挑戰,當然,我們可以設計的更輕巧,容許一些不一致。

我們用一個案例來看看增量技術的運用。

在應用分發市場產品中,都有一個重要功能,叫更新提醒。它的實現原理很簡單,以Android裝置為例,客戶端把使用者移動裝置上安裝的APP包名、APP名稱、APP簽名、APP版本號等資訊傳送到伺服器,伺服器根據這些資訊在APP庫中查詢相應APP是否有更新並推送到客戶端。這個過程非常簡單,但如果使用者手機上裝了50個APP,網路上互動的資料流就非常客觀了,即浪費流量和電量,又造成使用者體驗的緩慢,顯得很笨重。

這個時候,增量技術就可以派上用場了,比如下面的方案:

1) 每個自然日24小時內,客戶端選擇一個時間(優先選擇駐留在後臺的時候)上報一次全量資料;

2) 在該自然日24小時的其它時間,客戶端可以定時或在使用者使用時傳送增量資料,包括解除安裝、安裝、更新升級等帶來的變化;

3) 作為弱一致性的保障手段,客戶端在收到更新提示資訊後,根據提醒的APP列表對移動裝置上實際安裝和版本情況做一次核對;

4) 上述擇機或定時的時間都可以由雲端通過下發配置做到精細化控制;

⑥ 包壓縮

前面精打細算完畢,終於輪到壓縮演算法上場了。選擇什麼演算法,中間有哪些實戰的總結,下面提出來一起探討:

1) 壓縮演算法的選擇,我們比較熟悉的壓縮演算法deflate、gzip、bzip2、LZO、Snappy、FastLZ等等,選擇時需要綜合考慮壓縮率、記憶體和CPU的資源消耗、壓縮速率、解壓速率等多個緯度的指標,對於行動網路和移動裝置而言,建議考慮使用gzip。另外需要注意的是,輕資料與重資料的壓縮演算法取捨有較大差異,不可一概而論;

2) 壓縮和加密的先後秩序,一般而言,加密後的二進位制資料流壓縮率會低一些,建議先壓縮再加密;

3) 注意一些協議元件、網路元件或資料本身是否已經做過壓縮處理,要避免重複工作,不要造成效能和效率的下降。比如一些圖片格式、視訊或APK檔案都有自己的壓縮演算法。說到這,問題又來了,如果應用層標準協議框架做了壓縮,那麼基於其上封裝的業務層協議框架還需要壓縮嗎,壓縮技術到底哪家強?這個問題真不好回答,考慮到HTTP/2這樣的應用層標準協議框架定稿和普及尚需時日,建議在業務層協議框架中做壓縮機制。或者追求完美,根據後端應用層標準協議框架響應是否支援壓縮及在支援時的壓縮演算法如何等資訊,動態安排,總的原則就是一個字:只選對的,不選貴的;

3.3. 強監控

可監方可控,我們在端雲之間,要形成良好的關鍵運營資料的採集、彙總和分析機制,更需要設計雲端可控的配置和指令下發機制。本篇重點討論與主題網路方面相關關鍵指標的“監”和“控”。

以就快接入為例來探討一下強監控能力的構建和使用。

1) 接入質量監控,客戶端彙總接入排程FSM執行過程元資訊以及業務請求響應結果的元資訊,並由此根據網路型別不同、運營商不同、網路接入國家和省市不同分析接入成功率、業務請求成功率(還可細化按業務型別分類統計)、前述二者失敗的原因歸類、接入302重定向次數分佈暨原因、接入和業務請求測速等;

2) 建設雲端可控的日誌染色機制,便於快速有針對性的定點排查問題;

3) 終端硬體、網路狀態的相關引數採集彙總;

4) 建設雲端可控的接入排程(比如接入IP列表等)和網路引數(比如連線超時、IO超時、任務超時、併發連結數、重試間隔、重試次數等)配置下發能力;

5) 伺服器根據彙總資料,通過資料分析,結合伺服器自身的監控機制,可以做到:

a. 支援細粒度的接入排程和網路引數的優化雲控;

b. 支援伺服器的部署策略優化;

c. 發現移動運營商存在的一些差異化問題比如URL劫持、網路裝置超時配置不當等問題便於推動解決;

d. 發現分省市伺服器服務質量的異常情況,可以動態雲端排程使用者訪問或者降級服務,嚴重時可以及時提示客戶端發出異常安撫通告,避免加劇伺服器的負載導致雪崩。安民告示的快速呈現能力,考驗了一個團隊對可“控”理解的深度,我們在實踐中,提供了三級措施來保障:第一級是伺服器端通過協議或跳轉URL下發的動態通告,這在非IDC公網故障且業務接入伺服器正常可用時適用;第二級是預埋靜態URL(可以是域名或IP形式,優先IP)拉取動態通告,適用其它故障,靜態URL部署的IP地址最好同本業務系統隔離,避免因為業務服務所在IDC公網故障不可用時無法訪問;第三級是客戶端本地預埋的靜態通告文案,內容會比較模糊和陳舊,僅作不時之需;

e. 支援非同步任務的雲端可配可控,比如下載類APP的下載時間、下載標的和下載條件約束(磁碟空間、移動裝置電量、網路型別等)的差異化配置,通過錯峰排程,達到削峰平谷並提升使用者體驗的效果;

特別需要注意的是,客戶端資料包告一定要有資料篩選控制和資訊過濾機制,涉及使用者隱私的敏感資訊和使用記錄必須杜絕取樣上報。在我們的日誌染色機制中要特別注意,為了排查問題極可能把關鍵、敏感資訊記錄報告到後端,引入安全風險。

3.4. 多非同步


經過前面不懈的努力,初步打造了一個比較好的技術根基,好馬配好鞍,好車配風帆,怎麼就把領先優勢拱手送與特斯拉了。


使用者欲壑難平,資源供不應求,靠“術”並無法優雅的解決。跳出來從產品角度去觀察,還有些什麼能夠觸動我們思考的深度呢。根據不同的需求和使用場景,用有損服務的價值觀去權衡取捨,用完美的精神追求不完美,此乃道的層面。


所謂大道至簡,完美之道,不在無可新增,而在無可刪減。通過多非同步和各類快取機制,提供區分網路、區分業務場景下的差異化服務,是我們孜孜以求的大“道”。


下面通過一些實踐案例的總結,來探索簡潔優雅的弱聯網體驗改善之道(開始肆無忌憚的吹噓了)。


① 【網路互動可否延後】


微部落格戶端某個版本啟動時,從閃屏載入到timeline介面需要6秒+。這樣的體驗是無法接受的,與使用者2秒以內的等待容忍度是背道而馳的。從技術角度去分析,很容易發現問題,諸如我們在啟動時有10+個併發的網路請求(因為是HTTP短連結,意味著10+個併發的網路連結)、閃屏載入、主UI建立、本地配置載入、本地持久化資料載入至Cache等等程式行為,優化的目標很自然就集中在網路請求和本地配置、持久化資料載入上。


梳理併發網路請求,可以從以下三個方面考察:


1)        哪些請求是要求實時拉取的,比如timeline & 提及 & 私信的數字、身份校驗;


2)        哪些請求是可以非同步拉取的,比如timeline、使用者Profile、雲端配置、雙向收聽列表、閃屏配置、timeline分組列表、相簿tag列表等;


3)        哪些請求是可以精簡或合併的,比如timeline & 提及 & 私信的數字與身份校驗合併;


此時,取捨就非常簡單和清晰了,啟動時1~2個網路請求足夠應對。所做的僅僅是把一些請求延後發起,這是一種非同步機制。


在移動APP裡面還有大量類似的場景,比如使用者更新了APP的某個設定項或者自己Profile的某個欄位,是停在介面上轉菊花等網路互動返回後再提示結果,亦或是把介面互動馬上還給使用者,延後非同步向伺服器提交使用者請求,這裡面的價值取向不同,“快”感也便不同。


② 【網路內容可否預先載入】


微部落格戶端在timeline重新整理時,使用者向上快速滑屏,到達一個邏輯分頁(比如30條微博訊息)時,有兩個取捨,一是提前預載入下個分頁內容並自動拼接,給使用者無縫滑動的體驗;二是等到使用者滑動到達分頁臨界點時現場轉菊花,卡不卡看當時的網路狀況。實踐中選擇了方案一。使用者在滑動瀏覽第一個邏輯分頁時,APP就利用這個時間窗主動預先拉取下一個邏輯分頁的內容,使得使用者能享受一個順暢的“刷”的體驗。


所做的僅僅是把一個請求提前發起了,這也是一種非同步機制。思考的要點是:


1)        預先載入的內容是使用者預期的嗎,預先載入和自動下載之間,失之毫厘謬以千里;


2)        預先載入的內容對使用者移動裝置的資源(比如流量、電量等)和後端伺服器的資源(比如頻寬、儲存、CPU等)消耗要做好估算和判斷,體貼和惡意之間,也就一步之遙;


3)        預先載入區分輕重資料,輕資料可以不區分網路狀況,重資料考慮僅限優質網路下執行,最好這些策略雲端可以控制;


4)        預先通過網路拉取載入或儲存的過程中,不要打攪使用者的正常使用;


在移動APP中,預載入有大量的實踐,比較典型的就是升級提醒,大家都採用了先下載好升級包,再提示使用者有新版本的策略,讓你順暢到底。


③ 【使用者體驗可否降級】


微部落格戶端在香港公共WIFI下重新整理timeline總是失敗,通過後臺使用者接入請求和響應日誌分析,判斷是香港IDC到香港公共WIFI的匯介面頻寬窄、時延大,此時該如何應對。


從前面探討的TCP/IP網路知識,可以知道,在一個窄頻寬高時延網路中,吞吐量BDP必然很小,也就是說單位大小的資料傳輸所需的時間會很長。如果按照通常一次下發一個邏輯分頁timeline資料的策略,那麼從伺服器到客戶端傳輸,整個資料需要拆分成多個TCP資料包文,在緩慢的傳輸過程中,可能一個資料包文還未傳輸完成,客戶端的鏈路就已經超時了。


如果在弱網路(需要在應用層有測速機制,類似TCP/IP的RTT機制,測速時機可以是拉取微博訊息數字時)下,把邏輯分頁的微博訊息數由30調整為5會如何,如果方案成立,使用者刷微博的體驗是不是會下降,因為滑動一屏就要做一次網路互動,即便是配合預載入,也可能因為網路太慢,操控太快而又見菊花。外團在香港實測了這個版本,感嘆,終於可以刷了。


在飢渴難耐和美酒佳餚之間,似乎還有很多不同層級的體驗。聊勝於無,這個詞很精準的表述了服務分層,降級取捨的重要性。思考的要點是:


1)        產品的核心體驗是什麼,即使用者最在乎的是什麼,在做巨集觀分層設計時要充分保障核心體驗;


2)        每個產品互動介面中,什麼資料是無法容忍短時間不一致的,即什麼是使用者不能容忍的錯誤,在做微觀分層設計時要充分考慮正確性;


3)        在巨集觀和微觀分層的基礎上,開始設想在什麼條件下,可以有什麼樣的降級取捨,來保障可用,保障爽快的體驗;


4)        分層不宜太多太細,大部分產品和場景,3層足矣;


在移動弱網路條件下,處處可見降級取捨的案例。比如網路條件不佳時,降低拉取縮圖的規格,甚至乾脆不自動拉取縮圖等等,分層由心,降級有意。


④ 【端和雲孰輕孰重】


移動APP時代,絕對的輕端重雲或者輕雲重端都是不可取的,只有端雲有機的配合,才能在一個受限的網路通道上做出更好的使用者體驗。正所謂東家之子,胖瘦有致。


比如移動網遊APP,如取向選擇輕端重雲,那麼玩家的戰鬥計算就會大量的通過網路遞交給伺服器處理並返回,卡頓家常便飯,操控感盡失。


比如微部落格戶端,如果取向選擇重端輕雲,微博timeline所有的訊息都拉取後設資料(比如微博正文包括文字、各類URL、話題、標籤、@、訊息的父子關係、訊息中使用者profile、關係鏈等等),由客戶端實時計算拼裝,不但客戶端使用者需要消耗大量流量計算量,而且給後端伺服器帶來巨大的頻寬成本和計算壓力,如果過程中網路狀況不佳,還會非常卡頓。


通過實踐總結,端和雲孰輕孰重,取捨的關鍵是在資料計算規模可控和資料安全有保障的前提下:


1)        減少網路往復,要快;


2)        減少網路流量,要輕;


端雲有機結合,可以很好的演繹機制與策略分離的設計思想,從而使系統具備足夠的柔韌性。


不得不再次特別提到的一點是,快取技術是非同步化的基礎,它滲透在效能和體驗提升的方方面面,從持久化的DB、檔案,到短週期的記憶體資料結構,從業務邏輯資料,到TCP/IP協議棧,它無所不在。快取涉及到資料結構組織和演算法效能(耗時、命中率、記憶體使用率等)、持久化和啟動載入、更新、淘汰、清理方案等,有機會我們可以展開做專題的介紹。牢記一個字,快取是讓使用者爽到極致的利器,但千萬別留下垃圾。


提倡多非同步,實際上是要求團隊認真審視產品的核心能力是什麼,深入思考和發現什麼是使用者最關心的核心體驗,把有限的資源聚焦在它們身上。通過考察使用者使用產品時的心理模型,體驗和還原使用者使用場景,用追求完美的精神探索不完美之道。


網際網路服務核心價值觀之一“不要我等”,在移動網際網路時代仍應奉為圭臬,如何面對新的挑戰,需要更多的學習、思考、實踐和總結,這篇文章即是對過去實踐的總結,亦作為面對未來挑戰的思考基點。


老子曰過:上士聞道,勤而行之;中士聞道,若存若亡;下士聞道,大笑之。不笑不足以為道。求求你了,笑一個。


知易行難,故知行合一似(jiu)為扯蛋,那麼我們就且扯且珍惜吧。

公眾號推薦:

原文連結:https://cloud.tencent.com/developer/article/1005365

相關文章