史上最全的CDN內容分發網絡實戰技巧(網絡優化)

今天來給你們分享下關於 CDN 的東西,以及我本身的一些發現、一些我的的拙見。總共分爲 3 個部分:原理、詳解、各類坑。前端

 

 

首先說一下 CDN 的基本原理部分,主要分 4 塊來描述:CDN 的由來、調度是怎麼作的、緩存是什麼、關於安全。node

 

 

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

 

 

什麼是 CDN?c++

 

這是一個作過 CDN 以後的拓撲圖,裏面有幾個概念須要明確一下:算法

Origin Server: 源站,也就是作 CDN 以前的客戶真正的服務器;數據庫

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

Edge Server: CDN 的服務器,不單隻「邊緣服務器」,這個以後細說;安全

s/\(單\)只/\1指/;服務器

Last Mile: 最後一千米,也就是網民到他所訪問到的 CDN 服務器之間的路徑。cookie

 

咱們平時所使用的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的主數遞增,3、6、十二這樣。因此,這個環節就可能會致使很嚴重的問題,那對服務器來講你能夠作到什麼?好比說你再發完這個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,那就是二、四、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同樣的標準。這個時候,在發送的第一個數據以後就把這個鏈接關掉,此時客戶端並不知道服務端是不支持的,他還嘗試發起第二次請求結果就致使被塞。

閱讀原文

相關文章
相關標籤/搜索