1. 程式人生 > 其它 >cdn技術詳解上

cdn技術詳解上

轉自https://mp.weixin.qq.com/s/i_QCuAzi5nnuzSWJZW7WEw

大家好,,我叫白金(真名),很高興今天來給大家做個分享。在 CU 混了大概 10 年,ID 是 platinum,原在藍汛(ChinaCache)工作,現在在光載無限,目前擔任架構部總監的職位。

今天來給大家分享下關於 CDN 的東西,以及我自己的一些發現、一些個人的拙見。總共分為 3 個部分:原理、詳解、各種坑。

首先說一下 CDN 的基本原理部分,主要分 4 塊來描述:CDN 的由來、排程是怎麼做的、快取是什麼、關於安全。

最初剛有網際網路的時候,頻寬用量不多、使用者少,並不存在什麼問題,後來隨著發展,逐漸出現了使用量大、訪問緩慢的情況。最初 95 年的時候,有兩個博士試圖通過利用數學的辦法來解決動態路由問題,且效果還不錯,這便是 Akamai 的前身,也是全球第一個CDN 公司。98 年中國成立了國內第一家 CDN 公司,藍汛,ChinaCache,很榮幸我曾在這個公司任職,群裡也有好多前藍汛的同事們,也有很多現還在藍汛的同事。

什麼是CDN?

這是一個做過 CDN 之後的拓撲圖,裡面有幾個概念需要明確一下:

Origin Server: 源站,也就是做 CDN 之前的客戶真正的伺服器;

User: 訪問者,也就是要訪問網站的網民;

Edge Server: CDN 的伺服器,不單隻“邊緣伺服器”,這個之後細說;

s/\(單\)只/\1指/;

Last Mile: 最後一公里,也就是網民到他所訪問到的 CDN 伺服器之間的路徑。

我們平時所使用的DNS伺服器,一般稱之為LDNS,在解析一個域名的時候,一般有兩個情況,一種是域名在DNS上有記錄,另一種情況是沒有記錄,兩種情況的處理流程不一樣。

當你訪問163這個域名時,如果LDNS上有快取記錄,那它會直接將IP地址直接給你。如果沒有快取記錄,它將會一步步向後面的伺服器做請求,然後將所有資料進行彙總交給最終的客戶。

當你訪問163這個地址時,實際上如果本身沒有內容的話,它要去後面拿資料,這個過程術語叫遞迴,它首先會向全球13個根域伺服器請求,問com域名在哪,然後根域伺服器作出回答,一步步往下,這個過程較複雜,如果大家感興趣可去查相關資料,在這就不一一贅述。

DNS排程

肯定很多人好奇是如何進行排程和進行定位的?

其實也是通過LDNS的具體地址來進行的,比如,看圖,假設你是一個廣東電信客戶,那你所使用的DNS伺服器去做遞迴的時會訪問到某一個CDN廠商的GRB,全球的一個排程系統,他就能看到來自於哪個LDNS。假設如果使用者和LDNS使用同一個區域的伺服器,他就會間接認為使用者也是廣東電信的。

再舉個例子,比如說北京聯通的使用者,它使用DNS地址,一般自動給它分配的是北京聯通的伺服器,這個伺服器去做遞迴的時候,排程伺服器就會看到這個請求是來自北京聯通的LDNS伺服器,就會給它分配一個北京聯通的伺服器地址,然後讓來自北京聯通的使用者直接訪問北京聯通的伺服器地址,這樣來實現精準的區域性排程。

從這個排程理論上看,我們可以發現一個問題,就是假設使用者所使用的LDNS地址和你是同一個區域,那麼這個時候我們的排程才有可能是正確的。但是舉個例子來說,如果你是北京聯通的使用者,可是使用的是廣東電信的LDNS的話,就會讓GRB系統誤以為你是廣東電信的客戶,這樣就會錯誤的排程過去。

之前有一次我在小區裡上網,由於我的路由器有問題,我設了202.106.0.20的北京聯通的DNS伺服器地址,後來出差去深圳,訪問比較大的網站發現比較慢,經過分析,才發現原來我設的DNS地址是北京聯通的,而我在廣東和深圳使用的網路都是電信接入的,但是分配給我的是北京聯通的地址,那我用電信的線路訪問北京聯通的地址,勢必就會很慢。

因為剛才講到的DNS排程機制存在一定問題,所以在某些場合下我們會使用第二種排程機制,叫HTTP的排程。

瞭解http協議的人知道,在http協議中有一個叫302跳轉的功能,它的實現並不是說你訪問一個URL,然後馬上吐給你想要的資料,而是吐給你一個302返回信令,這個信令頭部會告訴你,有一個location目標,這個location就是告訴你下一步將要怎麼做,而具體排程是通過location來實現的。

即便我所使用的DNS和我不在一個區域,但當我訪問http server的時,這個server是由CDN公司提供的。客戶訪問server的時,雖說通過DNS方式無法拿到客戶的真正IP地址,但是如果你訪問的是http server,他一定能直接看到客戶的真實IP,利用這種方法可以進行排程的糾偏,可以直接返回給你一個302,然後location裡面攜帶一個真正離你最近的CDN server。

這種排程方式,優勢是準確,但是也存在弊端,它需要有一次TCP的三次握手建連,他不像DNS那樣直接請求一個數據包過去給一個反饋就OK了,他需要一次TCP的三次握手建連。

第二個是你如何訪問到http的伺服器?如果你之前是通過DNS排程過去的,實際上前邊的那個DNS也是省不了,在國內是沒有辦法做anycast的,也就是沒有辦法來直接訪問一個眾所周知的大的IP來進行,所以,一般情況下都是通過DNS來進行第一次排程,然後用http來進行第二次糾偏。這種情況下大家可以想象,如果你下載一個大檔案,比如說電影,但你訪問的是一個頁面小元素,比如說這個圖片只有幾k,那麼,實際上你排程的時間就已佔用了很大的成分。實際上,這種302排程是一種磨刀不誤砍柴工的方案,如果你後面有很多工作要做,比如要下載一個電影時間會很長,那你排程準確,即使花一點時間排程也是值得的。但是如果你後續訪問一下就完了,那麼你這樣排程就沒有太大意義。

除了DNS排程和http的302排程以外,其實還有一種排程方式,叫http DNS排程,它的原理是通過一個正常的http請求,發一個get的請求,然後再請求裡面以引數的形式攜帶一個我要解析的域名,然後伺服器那邊去通過資料庫查詢,查詢之後又通過http的正常響應,把這個你要請求的IP通過http協議給你,這種協議有一個特點就是必須雙端都支援,因為這種模式是非標準的。沒有任何一個RFC文件說,你的客戶端或者你的作業系統天生就支援這種機制。這有點類似是一種API的這種方式,那如果要實現的話就必須雙端都支援。

一般,第三種排程的應用場景是在手機的APP端,在APP軟體裡面,你要訪問某些東西很有可能被運營商劫持等問題,這個劫持問題後面還有很大的篇幅去講。那為了避免這種劫持,可能會用到這種http DNS的排程方式。既然APP的程式都是你自己寫的,所以說實現這麼簡單一個API的藉口是很容易的。

CDN的接入

可能會有人問,你講了這麼多DNS和具體CDN的排程有什麼關係呢?

因為在講你獲得一個具體的DNS域名地址的時,他給你的就是一個IP地址。那在沒有CDN之前,他給你的IP地址就是在原來沒做CDN時的原始伺服器地址。但如果你做過CDN的話,你會發現最終拿到的這個IP地址是CDN的節點,而並不是真正的原始伺服器。

我們通常說的拿到一個IP地址,這實際上是DNS的A記錄。DNS裡面有很多不同的記錄,比如像A記錄負責給你一個IP地址;比如像CNAME記錄給你的是一個域名的別名。當然還有很多其他記錄,比如TXT的記錄、MX記錄等等。這個跟CDN無關,這裡就不細說了,有興趣去查一下DNS相關的文件。

上圖就是一個很明顯的CDN介入後的效果圖。linux裡有一個命令叫dig,它可直接把要訪問域名的具體的解析情況列出來。那麼,通過這個圖可看出,當你要訪問www.163.com時,他最終雖給出的是一個IP地址,但實際上,它經過了兩次CNAME記錄。第一次CNAEM記錄就是我們之前說得CDN的GRB,他拿到了這個資料,就可以間接知道你的這個LOCODNS是從哪裡來的,然後間接給你進行一個定位。以這個圖為例,他實際上第一跳是跳到網速地址,第二跳是分配了網速的一個平臺,這個平臺又分開其他的IP給最終的客戶。

Cache 系統—快取系統

除DNS排程以外,在CDN裡還有一個非常大的重頭戲就是Cache系統,也就是快取系統。它用於把那些可以快取住的東西,快取到CDN的邊緣節點,這樣當第二個人去訪問同一節點,同一具體電影或MP3時就不用再經過CDN鏈路回到真正的源站去拿資料,而是由邊緣節點直接給資料。

在Cache系統裡囊括了很多的技術,比如,用空間換時間的這種高效的資料結構和演算法,多級快取以熱度來區分,前端是SSD後面是機械硬碟等等。很多的細節就不說了,如感興趣的可之後交流。

對於Cache系統來說,有兩種不同的工作狀態。第一種工作狀態就是所謂的命中(hit),第二種就是沒有命中(miss)。如果命中了,直接通過檢索找到磁碟或記憶體上的資料,把這個資料直接吐給客戶,而不是從後面去拿資料。這樣的話就起到一個很完美的加速效果。

第二種是在miss時,其實,miss的時候跟hit唯一的區別就是,當我發現我的本機上沒有這個資源,我會去我的upstream(上游)去拿資料。拿完這個資料,除了第一時間給客戶,同時還會在硬碟上快取一份。如果這個硬碟空間滿了,會通過一系列置換方法,把最老的資料、最冷的資料替換出去。

提到了upstream,不是原始伺服器,原因是因為當客戶訪問到CDN節點的時,他發現上面沒有資料,並不是直接從原始伺服器上去拿,而是經過他的另一個CDN節點,然後通過middlemell的方式去進行一些資料傳輸。然後upstream這一層,從原始伺服器拿資料,通過一系列的加速手段,快速的把資料投遞給我們的邊緣節點,再把這個資料給最終客戶。在過程當中upstream和downstream這兩層都會把資料快取一份。通過這種樹形結構,比如說多個邊緣節點,然後彙總到一個或者幾個副層結點,這樣的話可以逐漸的實現流量的收斂。

提到Cache的具體技術,我相信這裡的很多朋友都是同行業的,有人會說其實這沒有什麼難的,你只要有網路、有運維人員就可以了。其實我並不這樣認為,因為你如果想把它做好的話其實很難,比如,我列出的很多技術你有沒有在考慮?

舉幾個例子來說,你有沒有做網絡卡的的多佇列和CPU的親和性繫結?你有沒有做磁碟的排程演算法改進?另外,你儲存的時候還是用還是?等等都是有講究的。包括核心的調優包括架構和CPU的繫結,CPU的多級快取的使用,然後你的處理你使用,還是用標準的的這種機制。再比如說編譯的程式時使用的去編譯還是用英特爾的,然後你再做很多的呼叫。比如說一個很簡單的字串拷貝,那你是用,你還是用匯編去寫,你還是用什麼方式等等很多細節。

關於高效能這一塊,還有很多的研究,如大家感興趣的話,可以之後跟我進行進一步的溝通。我想表達的一個觀點就是說,看上去做CDN很簡單,入門確實也簡單,但是要真正想做好很難。

安全問題

在沒有做CDN之前你的網站很有可能會遭受到各種各樣的攻擊。那麼攻擊一般分成兩種,第一種叫蠻力型攻擊,量大的讓你的頻寬無法抗住最後導致拒絕服務,另外一種是技巧性攻擊。

作為CDN來講,就已經將你的原始伺服器的IP進行了隱藏。這樣當一個攻擊者去訪問你的域名的時,實際上訪問的並不是你真正的伺服器。當他訪問的是CDN的節點,就沒有辦法把CDN的節點打倒,換句話說,即使有能力把CDN的比如10g的節點或者是40g的大節點全部打倒,但由於CDN天然的分散式的部署方式,他也很難在同一時間之內迅速的把全國所有CDN的邊緣節點全都打癱。

另外,還有一種攻擊是針對你的DNS地址的。如果你的GRB癱了的話,會導致整個排程系統失靈。如果調動系統失靈,即使你的CDN的Cache server還是能夠正常接受請求,但由於流量排程不了。因此,你需要在DNS層做很多防護機制,比如說用高效能的DNS或用分散式的部署方式等等。

技巧型攻擊不需要很大的流量,就可以把你的原針打倒或是讓你的網頁出現錯誤的情況。比如說,像注入、掛馬甚至說更嚴重的會直接拖走你的資料庫等等。那麼作為CDN來說,有很多廠商實際上已經開始具備這樣的技巧性的防護能力了,比如說WAF(Web Application Fierwall),就是應用層防火牆,他可以直接去解析你的請求內容,分析內容是否有惡意性,如有惡意性的話去進行過濾,報警等一系列措施來保證你的原始伺服器的安全。

詳解篇

第二部分主要是針對網路層的優化、架構的優化、Cache的選型還有效能分析等等幾個方面,對整個CDN的基礎原理作很深入地剖析。

原始的CDN其實是Content Delivery Network這三個詞的縮寫,也就是內容分發網路。但我認為應該是can do something on Network。CDN的理念是加速,所以,我們就盡一切可能去做各種優化,從一層到七層的優化來實現最終的優化效果。

為什麼說一層是優化,實際上也是硬體,你的伺服器選型就是一種優化。你是用ssd,還是用saker硬碟,你是該用pce卡,還是應該用ssd。你的CPU應該用至強還是應該用阿童木的等等,都是需要去斟酌。

至於二層,鏈路層的優化指的就是資源方面。比如機房如何去選擇。

三層路由層是指你在middlemell這塊真正選路的具體的細節,後面會有一個圖來具體講一下。

四層是指傳輸層的優化,我們一般的業務全都是TCP,所以說這裡面就可以明確的說這裡是指TCP的優化。還有一個就是七層也是可以優化的。比如說你強行對內容進行壓縮,甚至你改變壓縮級別去壓縮。

作為CDN來說,基本上我羅列了一下可能會用到的一些技術,大概10個。比如說就近分佈、策略性的快取、傳輸的優化、鏈路層的優化、包括內容的預取、合併回源。然後持久連線池、主動壓縮,還有當你原始伺服器掛了的話你怎麼樣能夠保證讓客戶看到資料等很多的細節。

路徑的優化,實際上,我們可以把它抽象成是一個求最短路徑最優解的思路去解決真實的問題。當你從a點到b點需要傳輸資料的時,往往會經過一個c點,比直接從a到b更快。在互聯網裡有個三角原理,和地理位置的原理有一定區別的。雖說有一定的相關性,但還是有區別的,有可能從a經過c到b會比a直接到b更快。

在資料傳輸的時,需要去考慮很多綜合因素,目前為止,包括阿克麥也很難做到完全系統自動化去做鏈路選擇和切換。在排程的時,很多公司都有專門的團隊管流量排程的。很多的系統可能只起到支撐和參考的作用,而真正需要決策的還是人。因為你需要考慮的元素太多了,比如說要考慮你的頻寬成本、頻寬節點冗餘量、伺服器承載能力,要考慮你的客戶敏感度哪些該切哪些不該切等很多細節。

傳輸層的優化剛才講到了是TCP優化,在現今的互聯網裡,TCP優化是可以帶來最直接客戶體驗感的一種實現方式。如果想講TCP優化到底是怎麼回事,我們就得先從頭講一下TCP具體的原理是怎樣的。

上圖,我畫了四個不同的紅圈,cwnd,英文是,就是擁塞控制視窗,用途是控制傳送端的傳送速度。ss是slow start的縮寫,也就是慢啟動,這是任何一個TCP協議在最開始的時候必經的一個階段。

後兩個詞較有意思,ssthresh,是slow start threshold的縮寫,也就是說慢啟動閾值。它是出現在你傳輸到一定速度的時,認為應慢點去傳的時,原來的指數傳輸方式,增長速度方式變成現行的速度增長,來儘量的規避和避免網路的擁塞。

那整個擁塞避免階段其實就是圖中右下角的CA,這是擁塞避免這個線性的過程,不是指數。指數的那個叫慢啟動。

當TCP開始傳輸資料的時,最開始的時候並不是以一個很快的速度發出。你看到的wget或是下載某一個東西的速度猛的就非常快,實際上它是一個由微觀慢慢把速度加起來的過程,只是這個時間很短你可能並沒有察覺。但實際上從微觀上來看最開始是有一個所謂的初始發包數量的這麼一個概念。在早期的2.6.18核心,也就是3645相對應的這個版本之前,初始的發包數量是兩個。

它發出第一輪資料,實際上只發兩個資料包。等待這兩個資料包的完全確認,如這兩個資料包完全收到ACK確認資料之後,會認為第一輪資料你都已經收到了,這時它會把發包的數量調整成4個。如果第二個也收到了,就調成8個16個32個這樣的一個增長過程,就是slow start的過程。那這個過程實際上就是一個最開始在TCP剛開始建立的時候的一個最初始的過程。

那這個過程什麼時候會結束?其實就是在丟包的時候。如果沒有丟包,我沒有理由降速或者調整發送速度。當他遇到丟包的時候他就會把這個值記錄下來,並且通過他的擁塞控制演算法,算出一個合理的閾值。那麼當下一次速度增長到這個閾值的時候,就會知道不能再指數增長了,而是應該線性的增長髮包的數量,來避免再次丟包。

還有個概念就是RTO,實際上是在伺服器發資料而客戶端始終沒有響應的時,它會等待一個超時定時器。這個超時定時器一旦出現,就會回到另一個協議棧裡的一個狀態機。當這個狀態機處於丟包狀態時,它就會把它的CWND降到最開始這麼大,那麼他的速度就會驟降,這是個非常嚴重的問題。且一旦驟降它會重新評估。有可能,你之前,比如說你的視窗長到24,但是你丟包了,然後他給你算出你應該到16就變成線性。如果你再出現嚴重問題,它可能會把閾值降到12,並啟用線性傳輸模式了。

通過介紹這個原理,大家可能會看到,其實TCP是一個很聰明的做法。它在能儘量傳的時候拼命的提高速度,然後在丟包的時候就儘量降低速度,儘量的規避擁堵。

現如今的網路產生了很大不同,因為,比如說WiFi的接入、3G、4G的移動訊號的接入,甚至南電信北聯通的一些資源的不充沛,更甚至是惡意的一些限速,會導致你丟包的原因,有時並不是真正的擁塞。而是你鏈路裡面命中註定會有這麼多的資料會丟掉。

大家想象一下,假如有一個恆定的丟包概率的網路。當我發一百個包的時候,丟掉百分之二十,只收到了八十個,這時如果去降速,就意味著降得速度越低,傳送的資料量就越小,那麼對端收到的就更少。因為丟包概率是恆定的,如果遇到這種情況的話,早期的TCP的擁塞控制演算法就已經不能滿足現有的這種環境的需求了,因此我們要考慮如何去優化。

這是用tcpdump把資料包抓下來後用Verashape軟體開啟並且進行圖形化分析的一個微觀展示圖。

圖裡可以看到另外一個細節,就是能看到有很多不同的這種發包的週期。這個其實就是我剛才講的每次發兩個、四個、八個、十六個這樣的不同的傳送的時刻。但這裡有個問題,他傳送時會一股腦兒地把資料發出去。雖說在巨集觀上來講,你單位時間之內只能發這麼多,但是從微觀上來講,實際上你這麼一次傳送就相當於是burst,這種大的衝擊有可能會導致中間網路鏈路不充沛,然後會造成丟包。

在早期研究GPRS網路或者是25G的網路的時候,尤其會遇到這種情況。他的特徵是RTT很長,同時你的頻寬很小。那大家可以想象一下,如果你的頻寬很小,每一次突發這麼大,從微觀角度來講,這個資料就已經有可能會造成微觀上的丟包了。

另外一種優化的方法就是的平滑發包,充分的利用每一個發包週期之間的時間間隔,然後把資料包打散。這樣的話,既沒有讓對方從巨集觀上感覺發送速度慢,從微觀上我也讓這個資料變得更平滑,而不會導致某一個具體的小時間的一個時刻,由於鏈路不充足而導致丟包。

除了剛才說的以外,還有很多優化的方法。比如說建連優化,當你去發信包三次握手的時,預設情況下,對方如果未反饋,你會以1為一個貝司值然後以2的主數遞增這樣去重試。比如,一秒鐘重試一次,兩秒鐘一次,四秒鐘一次,很多次之後,它會自動的放棄。那如果按照6.18核心,以3為一個貝司值,以3的主數遞增,三、六、十二這樣。所以,這個環節就可能會導致很嚴重的問題,那對伺服器來說你可以做到什麼?比如說你再發完這個CS第二次握手之後,如果他一段時間沒響應,可快速給他再重發一遍。

這種資料包的優化實際上並不會佔用什麼網路,而且有可能會勾引出第三次握手,快速的解決由於你的伺服器在出項上導致第二次握手丟包。

另外,還有很多的客戶可能較關心具體的細節,比如,你的首包時間是多少?首包時間是當你發完http的get請求之後,你所拿到的第一個資料。那這第一個資料往往是你的響應頭。這個響應頭有可能是和你的內容一起傳送過來的,也有可能是先發送一個響應頭然後再發內容,這取決於你自己的server的時限。在TCP裡面有一個Nagel演算法,Nagel演算法會把這個資料拼湊成一個大塊兒後發出。如果你要是在engikers裡配TCP nodelay,把這個配完後就可以。有什麼發什麼可以去提升這個首包的效果。

平滑發包剛才也講過了,丟包預判就是你通過統計學的一些方法,把端到端的,比如,c到你的傳輸具體情況做一個記錄。然後,如果你要是發現丟包率是比較符合規律的話,可以在沒有丟包的時候你預判有可能丟包,那你就時不時的去把某些資料包重發一遍,發兩遍,即使他能收到。這樣的話也是可以達到加速的抗丟包效果的。

後面還有很多細節,這裡就不再贅述。右邊是一個linux下的TCP協議棧狀態機的切換躍遷圖。這個圖裡面的open狀態指的是在沒有任何丟包的正常狀態,Recovery狀態是開始有重傳。Disorder這個狀態是看到有資料包亂序。CWR是一種TCP頭部攜帶的顯性的擁塞控制,這個一般很少用到.但是蘋果作業系統確實支援的。左邊那個Loss狀態就是之前我一直講的一旦遇到RTO以後,就會驟降這種情況。所以,在TPC優化的時還需考慮你怎麼樣去優化你的協議的狀態躍遷,讓他儘量不跑到Loss這個狀態。

很多做TCP優化的,只是改變TCP擁塞控制演算法,直接編譯出一個核心模組重新載入,然後改一下的擁塞控制模組,再重新載入一下就OK了。實際上,這種做法只能改變你計算CWND的數量,但並不能改變他的狀態。如果你想做TCP優化,你必須要動TCP協議棧本身。

在linux協議棧裡面有一個控制引數叫tcp slow start after idle。意思是,你在資料傳輸的時,如果等了一段時間超出一定的時間閾值之後他會把CWND給你降到初始值。

那麼,這種情況是有問題的,假如你是一個http的業務,而且都是這種小檔案。恰好你又用了keep life這種狀態,每一個請求結束後並不馬上斷掉連結,而是期待下一次的資料請求。在第一個資料塊object,比如下載第一個圖片,他去請求時,這個CWND會逐漸通過慢系統長到一定的高度。由於你兩次get請求之間可能間隔了一段時間,這個時間一旦超過閾值,就會在傳送端自己把這個CWND降到初始值。一旦降到初始值,這個時候你第一個object在下載以後,CWND好不容易漲上去的就白長了。你在下載第二個的時候實際上還是從慢速開始。

在linux系統裡預設這個值開啟,它實際上是會給你降的。如果你想做優化的話,最簡單的做法就是把它置成0。如果置成0,即使連線中間隔的時間很長,你在請求第二個object的時,他的初始的傳送速度就繼續按照剛才的大小繼續傳送。這樣,就可以達到一個很好的加速效果。

通過第一部分的講解,大家也知道CDN有一個非常重要的就是快取系統,用來做資料的快取。當你下載一個電影或mp3的時,資料會留在你的快取系統裡面。如果有第二個人還訪問同一個檔案的時,還恰好通過你去訪問,那你就可以直接把這個內容給客戶,而無需把這個內容遞交到真正的原始伺服器,然後再從原始伺服器去拿。

但在真正的業務場景裡,除了可快取的內容以外,還有一種是完全不可快取的。比如說你的登陸、再比如說你瀏覽論壇頁面的時候有一些動態的一些元素,因為每個人看到的東西是不一樣的,不同的URL、不同的cookie可能看到的東西玩是完全不同,那這個時候這個資料就沒有辦法快取。有人會說這種情況下是不是CDN就沒有價值了,如果是純動態頁面的話,其實不是的。通過架構優化我可以給你展示一下,通過CDN你能怎麼樣去加速,能加到一個什麼樣的速度?

這是個在沒有做CDN之前,客戶訪問源站的時候的連線示意圖。這裡面提到一個概念RTT,之前也說到了它的真正術語是往返時延,實際上就是我們平時說的ping值。但是ping值只是RTT的一部分,並不是說RTT就是ping值。實際上,TCP也有往返時延的,比如,你發起一個信包,然後對方回覆,這段時間間隔也是RTT。有一種ping叫TCPping,利用的就是這個特點。如圖,假設客戶到原站的RTT是80毫秒,假設你要下載的檔案是30kb,算一下他大概需要多久時間可以下載完成。

圖裡共3種顏色,紅色代表TCP的三次握手是一個建連的過程。第一次,第二次第三次然後建連完成。大家仔細看綠色的,綠色是發get請求的時候的一個數據包。藍色的大量的資料傳輸表示伺服器開始以不同週期開始吐資料。那麼這裡邊我是假設他初始CWND是2,那就是2、4、8這樣去長。

在TCP裡邊還有一個MSS的概念,TCP協議能一個數據包儲存的最大真正的七層內容是有多長。在普通的網路當中,MTU如果是1500位元組的話,IP頭是20位元組,TCP頭是20位元組,在一般情況下MSS是1460,也就是說一個數據包可以傳遞1460位元組的內容。那我們算一下,30kb是30*1024這麼多位元組,那麼它需要幾輪才能傳輸完成呢?

第一輪發兩個資料包,第二輪發四個資料包,第三輪發八個資料包,第四輪的時候其實剩餘的資料量已經不足16個數據包了,但是仍然要傳送一輪。

後面是四個來回我剛才講了,再加上前面的TCP三次握手也佔一個往返時延。也就是說一共有五個來回週期。那假設這個往返時延是80毫秒,可以算一下,5*80,這是400毫秒。也就是在這麼一個網路的情況下,頻寬足夠充足,然後在沒有抖動也沒有丟包的情況下,要傳一個30kb的資料在80毫秒延遲的情況下,他最快的理論速度也需400毫秒才能完成,這是在CDN之前。

上圖是在做CDN之後的效果圖。我們可以看到,在客戶和園站之間,部署兩個CDN的節點,一個叫下層一個叫上層。下層離使用者很近,這一塊兒的距離就lastmell。上層離源站近,這一塊兒這個距離我們firstmell。然後上下層之間我們叫middlemell。為確保能夠充分的體現即使是動態的資料我們也能起到很完美的加速效果,我在上下層之間我仍保留80毫秒。同時在Lastmell和firstmell分別引入20毫秒,那麼總共延時就變成了120毫秒。也就是說現在網路環境總延時是原來的1.5倍。

首先來看一下firstmell,因為firstmell和加速之前的拓普相比,唯一不同的是往返時延變小,由80毫秒變成了20毫秒。計算一下20*(1+4)=100毫秒。

再來看lastmell,由於lastmell傳送端是我們自己的伺服器,所以完全可以利用TCP優化這種方式來讓資料包傳送更快。比如,最簡單的加速方式就是去修改你的CWND,原來是2現在修改到10,目前在2.6.32核心以上,他預設的這個CWND應該都是10,早期是2。谷歌在很早之前就已提出一個觀點,早期ARFC的標準裡說初始值是2,,已不合時宜,因為現在的網路頻寬都很大。所以我們應該把它提到10,後來就所有的東西都是以10為初始值。

假設初始值就是10,可以算一下需要多少個週期能把資料發完?共有30K的資料,第一輪發10個,每一個數據包記住這裡邊說的10個指的是10個數據包。每個資料包可存放的內容是1460個位元組,那實際上第一輪發10個就已經變成14.6k。那第二輪20個數據包用不滿就已經發完了,套用公式20*3,實際上只需要60毫秒就可完成。

最有意思的是middlemell這塊。因為所有的東西全都是我們自己的,伺服器也是我們自己的,然後網路也是相對來說可控的。所以可以在middlemell這塊兒做很多有意思的事情。比如TCP,由於伺服器是在機房,他跟原針不一樣,原針有可能頻寬很小,而在lastmell也不可能吐資料太快,如果太快你的最終客戶端的網路又個瓶頸。但我們兩個節點之間傳輸的時實際上是可以速度很快的,也可以直接把資料一次性的30個包傳到下層,從上層傳到下層。

除這個以外還有一個很重要的觀點,我可以通過上下層的一種長連線的機制keeplive的一個機制,忽略TCP的3次握手。即使換不同使用者去訪問同一個下層,但因我上下層之間已經建立了一個所謂的通道,那麼也可以讓這個資料通過我這個通道直接把get請求傳送到上層,上層把這個交給原針。這樣減少一個往返。套用公式可以看一下80*(0+1),總共只需要80毫秒。

把三個部分一起加起來,可以算一下60+80+100=240。也就是說,這種環境下,在總的延時是原來1.5倍的情況下,完美的做到比原來提升40%的優化效果。在不能快取的純動態的情況下,我在中間的middlemell沒有任何RTT減少的情況下,我的CDN架構給你帶來的價值。

還有兩個細節我沒有說,第一個細節是,真正的我們找上下層的鏈路的時有可能會小於80毫秒。因為利用我們之前說的那個路由的最短路徑的演算法,有可能會找一個經過c點到達上層,總共的RTT可能會小於80毫秒或更小,實際上還能進一步的縮短時間。另外,這裡講的是上層拿到所有的資料之後才會給下層,下層拿到所有資料之後才會給使用者,實際上他不會在所有資料收到之後才傳輸,他是隨收隨傳的,所以這三個過程在時間的橫軸上是有疊加的,就導致時間進一步縮短。

之前我有講,在CDN裡你玩的是什麼?你玩的實際上就是網路,尤其是對CDN公司來說。坦白來講,伺服器有三大部分組成,第一部分是你的作業系統,第二部分是你的Cache快取系統,第三部分就是你的網路。而對於OS來說,一般你的作業系統選型完畢優化之後你一般不會再動它了,除非遇到了重大的安全隱患或者是有重大的升級。而對你Cache系統來說也是,一般都求穩,在沒有重大的bug的時,不會去輕易的改變。但最複雜的就是網路,你必須要掌握對網路的控制度,這樣的話你才能駕馭它。

如果你的網路研究的很透徹,通過你的分析會發現很多問題。給各位講個案例,我們在訪問某一個資源的時,大概可能五年前或更早,我剛加入藍訊的時去分析,看到我們不如競爭對手。我通過資料包的分析發現,不是我們資源不好,而是我們的TCP優化沒有做,而對方做了TCP優化。

以這個例子來講,可通過第一個信包和第二個包之間的時間差可看到他的RTT是23毫秒。通過這個RTT我們可看到它在第19個包和第21個包之間有一個時間跳變,這個跳變就意味著它屬於第二輪發包機制。那第一輪可以數一下一共是10個包,也就是說初始initcwnd值是10。假設有一個50kb的資料包需要傳送,算一下需要多長時間。50*1024算出位元組再除以 1460換成包數,再加1,加1的原因是考慮到一定會有餘數,餘數的話就佔一個包所以加1。等於36個包,要把36個包發出去才能完成這個傳送。

假如要發36個包,初始發包數量是10。可算下36拆解等於10+20+6。他需要3個往返才能把這個資料發完。套公式算下它的傳送時間需要多久?RTT我們之前算了是23,23*4。為什麼是4呢,因為你還要加一個TCP三次握手的一個時間,一共需要92毫秒才能完成。

上圖是競爭對手的情況,我們可以通過第一次和第二次握手,看到他的往返時延是35毫秒。和之前的23毫秒相比,可知這個資源的ping值比原來增加了52%。通過剛才的分析方法我們也可以找到第35和37號包的跳變點。那麼35號包之前是第一個傳送輪迴。整個的發包數量是20,它的初始發包數量實際上並不是標準的10,而是20。那麼,我們可以再算一下,如果你有50kb必須要發出,你最終需要也是36個包,但是你初始是20就需兩輪,分別是20+16。

通過套公式,可知需要150毫秒完成。那150毫秒跟之前的92比只慢14%。在資源落後52%的情況下,最終效果才慢了14%,中間的這個差距實際上就是你的技術帶來的價值。

這是個視訊點播對比資料圖,高低代表傳送速率,橫軸是時間。通過對比,明顯可看到廠商a的傳送速度高於廠商b。做完TCP後和做TCP優化前,差距還是很大。整個過程可看到第一個廠商的速度起來以後非常平穩,直到結束,而第二個廠商他最開始速度很快逐漸發現要丟包減速,速度就就漲不起來了。這就是提速優化價值。

上圖是當時分析為什麼伺服器傳送慢的一個結果,通過這個分析直接就給大家總結一下吧!結果可以看到這是由於TCP演算法自身的問題而導致的,他把時間白白浪費了,他們有時間其實是可以繼續發出去的但並沒有繼續發。

另外,還有一個有意思的,每個廠商無論你是做CDN,做電商、做IT企業,只要你有對外提供的server,而且server的負載比較高都會遇到的一個syncookie的坑。給大家講一下。在TCP的標準裡有兩個選項一個叫WScale一個是SACK。他並不是在RFC793裡邊出現的,他是在RFC1323裡補充而出現的。

現在講一下這個WScale什麼東西。預設的情況下在標準的TCP協議,在早期的時候是沒有WScale概念的。他在TCP的頭部有一個16byte的空間來表示你能傳送的最大位元組數,一個週期能傳送的最大位元組數。那根據TCP的吞吐量的計算公式,吞吐量一定是小於等於最大發送視窗除以RTT的,這個RTT是以秒為單位。

之所以說小於等於是因為一般的情況下他是有可能有亂序或者抖動的。假如你的TCP協議傳輸時,RTT是100毫秒,假設網路之間沒有丟包也沒有亂序也沒有抖動,且頻寬是無限大的。套公式可知,64k除以100毫秒,也就是0.1,吞吐量最大是640k。即使你的頻寬無限大,沒有丟包,沒有抖動,最大640k,就是這麼嚴格。

大家可能覺得這個有點兒不可思議,為什麼我們傳輸速度是遠大於這個呢?因為在新的標準裡引用WScale這個概念。在TCP三次握手的時候,客戶端如果要支援這個選項的話,服務端被通知支援這個選項。如果服務端也支援,會記錄下來說客戶端支援,同時迴應也支援。在客戶端拿到第二次握手時,服務端就也把自己置成支援狀態了。在資料傳輸的時,它是以2為底數,然後以WScale的這個n值為指數的一個滑動視窗遞增值。

利用這個WScale是可把傳送視窗的數量漲到很大的,比如說64k、128k、256k甚至更大。如果要這樣再套公式,他的傳輸效果就會變得非常好了。

關於引數SACK,選擇性應答,全稱是Selective ACK。在你資料傳輸的時,沒有這個選項他會怎麼樣呢?比如,要傳10個數據包,只有第6個數據包丟掉了,那麼在服務端收到ACK的時候他會知道,只收到了5,然後就沒有其他資訊了。這個時候他需要怎麼做呢?需要把6到10重新發一遍。那會導致兩個問題,第一,你的資料從開始到傳完,速度就會變慢。第二個就是佔用額外頻寬把7到10進行一個沒必要的重傳。

同樣的在TCP三次握手的時候寫標記,並且兩邊都確認同時開啟,如果要是都支援的話,在客戶端反饋資料的時,他就會告訴服務端,收到連續的序號完整的到5,但是我還收到了7到10。服務端就可通過這兩個資訊進行拼接找到中間的空隙,就會知道只有6號丟掉了,他就只需傳6就可以。為什麼要強調這兩個,可能是為後面那個syncookie的坑做鋪墊。

接觸過linux的人都知道在裡面有一個叫syncookie的機制。是一種幫助你去防護一定量攻擊的一種方法。那麼它的原理是什麼呢?在每一次資料正常建立的時它優先消耗一個叫連線佇列的一個概念。等連線佇列滿了,如果你syncookie未開啟,有新的請求過來,他就會把那個新丟掉,如果你有大量的這種假的建連資料包已充斥滿了整個建連佇列的,那麼他就會導致拒絕服務。

那syncookie開啟的情況下會怎麼樣呢?他會在協議棧之前自己偽造一個應答機制,並不是真正的協議棧去代應答第二次握手。同時他的第二次握手會攜帶一個算好的一個cookie值作為第三次握手的校驗。如果他收到了第三次握手的校驗值的會被認為是一個合法的建連,那麼,他會把這個通過注入的方式,直接告訴你這個連結可以直接用了。那在前期syncookie當滿的時候開始啟動這個狀態,他是不佔用佇列的,所以說他是一個非常好的防攻擊的一個手段,但是他的防攻擊的量不會很大,微量是可以的。

但坑也恰恰就在這。由於syncookie他不是標準的TCP協議棧,所以說他的支援,並不是非常的完備。等一段syncookie發出,他代應答的第二次握手並不攜帶WScale和SACK這個選項。就會讓客戶端誤認為是不支援的,所以,後續的溝通就變得非常的低效。我們之前做過一個實驗,在有一定量丟包而且大延時的情況下,你的速度可能只有300多k。後來查了很多資料發現確實是這個樣子,而且我們做了很多的模擬時間。比如,都為syncookie出發的時,他速度確實就很快。

後來我們做了一個改動,在syncookie上,如果要是代應答的時,我們攜帶SACK的這個資料給客戶,那後來建連的時都可以把這個功能用起來。用起來時我們在線上是真正的無環境試驗可以提升大概25%到35%的服務質量。

另外,講一下關於Cache的選型。很多的公司都在自研,也有很多公司直接用的開源的軟體,至於怎麼去選可能就要看你自己的場景了。比如,裸盤技術不依賴檔案系統,但他的缺點是在於他對業務的支撐比較差,且他是c++語言寫的COVER住的人還是不多的,那世界上有很多的公司是跟那個SD相結合,利用的方式去拆解業務,後續來做Cache。

還有很多公司是走的自研路線,比如像網速藍汛,阿里快忙這些公司都是曾經做過自研的,後續是什麼發展目前不太清楚。

坦白來講,並不推薦自研。能用錢解決的問題都不是問題。如果去自研,第一,你要耗費大量的人力和時間。第二,你需要去思考,你能不能cover住這些原本人家已經做好的東西。你要自研的話,前提是你在重複造車的過程當中一定有一個車輪,那個車輪能不能保證你的這個龍骨能夠跟人家一樣,甚至比人家更好。這裡給大家分享一個我之前遇到過的自研軟體的坑。

這個資料包它體現的是什麼?體現的是你看最後的那兩個資料包,倒數第二個是一個get請求。它傳送完這個get請求之後馬上收到了一個RST,這個連線被斷掉了。當時造成了很多的投訴,那我們也是去考慮這個問題到底是怎麼造成的?

通過抓包在伺服器上抓包微觀去分析,我們發現實際上這個問題是怎麼造成的呢?看上面這個截圖,在客戶端發起請求的時候他有一個欄位就可能是keep live。意思是說他期望去和伺服器進行這種長連線。而伺服器是怎麼給的呢?伺服器給出去的時,並沒有明確的告訴客戶端是否支援。導致這個問題的原因是由於我們的研發人員並沒有真正地領會RTT協議的精髓,他沒有完全cover住這個RTT協議導致最基本的這種車輪,這個輪骨做的是有問題的導致很嚴重的坑。

在HTTP1.0協議裡邊,如果你要是發一個可能是keeplive你希望和server進行keeplive這種溝通的話。Server端必須要告訴說我支援這樣才能支援,有點像TCP的三次握手,那個WScale和那個SACK類似這樣,但是在1.1協議裡邊兒恰恰不是這個樣子的。如果你的身邊沒有任何反饋的話,客戶端會預設為,我發了這個東西,你有沒有告訴我說你不支援,那能不能支援?那這個時候問題就出現了。

這個就導致了第一個請求結束之後,服務端實際上是不支援,但沒有給出明確的顯性的資訊來告訴客戶端。研發人員把1.0和1.1協議弄混了,或是他還認為1.1協議是跟1.0一樣的標準。這個時候,在傳送的第一個資料之後就把這個連線關掉,此時客戶端並不知道服務端是不支援的,他還嘗試發起第二次請求結果就導致被塞。

轉自:https://mp.weixin.qq.com/s/i_QCuAzi5nnuzSWJZW7WEw