天天看點

從輸入URL到頁面加載完成的過程中都發生了什麼從資料傳輸的角度:

   首先,對于http肯定是有用戶端和伺服器的,在這個語境中,用戶端和伺服器本質上也都是一個軟體,實作了http協定相關标準的軟體。用戶端一般由都是由浏覽器充當,也就是說,在浏覽器中實作了http用戶端的相關功能。而伺服器的實作就多種多樣啦,我們可以用java寫servlet,c#寫ASP.net,還有php,ruby,Python,nodejs等。實際上我想,http服務在作業系統底層應該有實作,而這些語言隻不過是利用作業系統的http服務封裝成自己的接口供開發人員編寫web伺服器程式。而我們熟悉的IIS,Tomcat,Apache,Web logic,都是能夠作為某些web伺服器容器的大型伺服器平台,它們都會包括很多更為強大的功能。一般來說,我們這裡所說的伺服器指的是自己用特定語言寫的web應用伺服器程式。nodejs不需要web容器,本身就有對http的直接應用子產品,是以用nodejs建立一個web伺服器是很友善的。

整體通信

   有了用戶端和伺服器,就可以開始通信了,整體上分為3個步驟:

  1. 因為http是建構在TCP之上,那麼自然是要經過3次握手建立連接配接。
  2. 建立連接配接後,伺服器會根據url請求中的資訊進行處理,作出響應,一般來說是找到一個html檔案傳回給用戶端。
  3. 用戶端即浏覽器得到html,進行渲染。

下面詳細說下這3個步驟

建立連接配接

    這個跟網絡關聯多一些,我網絡學的馬馬虎虎,隻能大體說一下。對于http的用戶端,它的輸入就是一個url,而對于建立連接配接,它需要的隻是url的host(主機)部分,而主機位址一般是網站的域名,是以第一步肯定是是域名解析,也就是要通過DNS伺服器進行域名解析得到網站的ip位址,然後向這個ip位址發送一個連接配接建立的請求,如果伺服器接收到請求會傳回一個确認,用戶端得到确認再次發送确認,連接配接建立成功。當然在這個過程中還會涉及到很多細節。

伺服器處理

    建立好連接配接後,用戶端就會發送http請求,請求資訊包含一個頭部和一個請求體,

    一般的web技術都會把請求進行封裝然後交給我們的伺服器進行處理,比如servlet會把請求封裝成httpservletrequest對象,把響應封裝成httpsevletresponse對象。nodejs的http子產品,當你建立伺服器的時候會寫一個回調函數,回調的參數用來接受http請求對象和響應對象,然後在回調函數中對請求進行處理。

    在請求對象中我們可以得到path(路徑),queryString(查詢字元串),body(post請求中送出的資料)等。對請求的處理就可以很複雜,也可以很簡單。我們可以根據path找到用戶端想要的檔案,讀取這個檔案,然後通過響應對象把内容傳回給用戶端,這個過程,不同的技術提供的api可能不同,尤其是用慣了MVC架構的人,可能隻是指定一個檔案,或者在配置檔案中設定一下就好了。但是最終的實作肯定是符合http響應标準的,也就是要有一個響應頭和一個響應體。我一般接觸到的設定響應頭就是設定content-type來決定MIME類型,設定Cache-Control,last-modify等緩存内容。一般來說傳回給用戶端的内容是一個html字元串,然後content-type設為text/html。當然也可能用戶端請求的是一個image檔案,那麼就是讀取image檔案後,content-type可能設為image/png,image/jpg等,然後把内容傳回給用戶端。這樣一次對請求的處理就結束了。

    當然這個過程太單一,而且處理過程也可能很複雜,又有資料的操作,又有頁面的建構,又有路徑的查找比對,又有檔案的讀取等等,于是就出現了MVC架構以及後來演變出的各種MV*架構。隻是概述一下MVC主要做了什麼,在我看來最重要的就是解耦和子產品化。我認為MVC實作最重要的有兩點:

  1. 路由比對,http請求的path中就不需要指定到具體的視圖位置,而是按照我們制定的規則進行比對,這樣就有了很大的靈活性,可程式設計性。
  2. 模闆技術,一般來說我們最後傳回給用戶端的是一個html字元串,而有時候這個字元串往往不是靜态單一的,有的時候需要和資料進行結合,需要拼接。這就帶來了很大的麻煩,模闆技術為解決這個問題帶來很大的便利性,同時又能夠把視圖和資料進行解耦。

用戶端渲染

    用戶端接收到伺服器傳來的響應對象,從中得到html字元串和MIME,根據MIME知道了要用頁面渲染引擎來處理内容即html字元串,于是進入頁面渲染階段,這又是一個很龐雜的體系。

    從浏覽器的角度講,它包含幾大元件,網絡功能(比如http的實作)算是其中之一,渲染引擎也是其中之一,還有其它的一些比如自己UI界面,javascript解釋器,用戶端資料存儲等等。在這裡我們主要關注渲染引擎和javascript解釋器,對于web開發者來說,這才是浏覽器的核心。

    我們能夠在浏覽器中看到一個頁面,那麼這個頁面是怎麼出現的呢?實際上就是調用底層繪圖API給畫出來的。不同的渲染引擎,它的實作也不同,主流的引擎包括IE的Trident,chrome和safary的webkit,firefox的Gecko,chrome又出了一個Blink,放棄webkit。于是乎才有了讓人頭疼的各種相容性問題。

    整體上頁面渲染的過程大緻是這樣的:

    渲染引擎得到html字元串作為輸入,然後對html進行轉換,轉化成能夠被DOM處理的形式,接着轉換成一個dom樹,在解析html的過程,解析到<link>,<script>,<img>等一些請求标簽時,會發送請求把對應的内容擷取到。這時又會同步進行css的解析,建構出css樣式規則應用到dom樹上,然後進行一定的布局處理,比如标記節點塊在浏覽器中的坐标等形成最終的渲染樹,最後根據這棵渲染樹在浏覽器視窗中進行繪制。

    最終我們就看到了頁面的樣子。

    當然在頁面渲染過程中還會同步進行javascript的解析,而且這兩者是在同一個線程中的,是以一旦javascript死循環,頁面的渲染也就進行不下去了。

    以上是從一個web開發者的角度思考的整個過程。如果從别的角度更細化的去想,還包括許多内容:

    比如整個網絡通信中協定的封裝:

    在本機中,把要傳輸的内容即請求對象在應用層上加上App首部,傳遞到傳輸層加上TCP首部,到網絡層加上IP首部,資料鍊路層加上以太網的首部和尾部,然後轉換成bit流進入網絡環境中。到達主機後在一層層解封裝,最後把内容交給伺服器程式。

    再比如這個過程中的認證,加密,安全,編碼等問題都會有一定的處理,不過這些内容我就不是很了解。

從資料傳輸的角度:

第一步:在浏覽器中輸入位址

第二步:浏覽器查找域名的Ip位址---DNS查找

浏覽器緩存--由浏覽器自己決定,而不是作業系統決定

系統緩存--系統調用,檢視系統緩存的記錄

路由器緩存

ISP DNS緩存--如果還找不到就遞歸,知道頂級域名伺服器

第三步:浏覽器給Web伺服器發送HTTP請求

GET http://facebook.com/ HTTP/1.1

  Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]

  User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]

  Accept-Encoding: gzip, deflate

Connection: Keep-Alive

Host: facebook.com

Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]

GET 這個請求定義了要讀取的URL: “http://facebook.com/”。 浏覽器自身定義 (User-Agent 頭), 和它希望接受什麼類型的相應 (Accept andAccept-Encoding 頭). Connection頭要求伺服器為了後邊的請求不要關閉TCP連接配接。

請求中也包含浏覽器存儲的該域名的cookies。可能你已經知道,在不同頁面請求當中,cookies是與跟蹤一個網站狀态相比對的鍵值。這樣cookies會     存儲登入使用者名,伺服器配置設定的密碼和一些使用者設定等。Cookies會以文本文檔形式存儲在客戶機裡,每次請求時發送給伺服器。

除了擷取請求,還有一種是發送請求,它常在送出表單用到。發送請求通過URL傳遞其參數(e.g.: http://robozzle.com/puzzle.aspx?id=85)。發送     請求在請求正文頭之後發送其參數。

像“http://facebook.com/”中的斜杠是至關重要的。這種情況下,浏覽器能安全的添加斜杠。而像“http: //example.com/folderOrFile”這樣的      位址,因為浏覽器不清楚folderOrFile到底是檔案夾還是檔案,是以不能自動添加 斜杠。這時,浏覽器就不加斜杠直接通路位址,伺服器會響應一個重      定向,結果造成一次不必要的握手。

第四步:facebook的永久重定向相應

伺服器給浏覽器響應一個301永久重定向響應,這樣浏覽器就會通路“http://www.facebook.com/” 而非“http://facebook.com/”。

為什麼伺服器一定要重定向而不是直接發會使用者想看的網頁内容呢?這個問題有好多有意思的答案。

其中一個原因跟搜尋引擎排名有 關。你看,如果一個頁面有兩個位址,就像http://www.igoro.com/ 和http://igoro.com/,搜尋引擎會認為它們是       兩個網站,結果造成每一個的搜尋連結都減少進而降低排名。而搜尋引擎知道301永久重定向是 什麼意思,這樣就會把通路帶www的和不帶www的位址歸       到同一個網站排名下

還有一個是用不同的位址會造成緩存友好性變差。當一個頁面有好幾個名字時,它可能會在緩存裡出現好幾次。

第五步:浏覽器跟蹤永久重定向位址

現在,浏覽器知道了“http://www.facebook.com/”才是要通路的正确位址,是以它會發送另一個擷取請求

第六步:伺服器“處理請求”

伺服器接收到擷取請求,然後處理并傳回一個響應。

這表面上看起來是一個順向的任務,但其實這中間發生了很多有意思的東西- 就像簡單的網站,何況像facebook那樣通路量大的網站呢!

Web 伺服器軟體

      web伺服器軟體(像IIS和阿帕奇)接收到HTTP請求,然後确定執行什麼請求處理來處理它。請求處理就是一個能夠讀懂請求并且能生成HTML來進行響應     的程式(像ASP.NET,PHP,RUBY...)。

      舉個最簡單的例子,需求處理可以以映射網站位址結構的檔案層次存儲。像http://example.com/folder1/page1.aspx這個位址會映射/httpdocs/folder     1/page1.aspx這個檔案。web伺服器軟體可設定成為位址人工的對應請求處理這樣page1.aspx的釋出位址就可以是http://example.com/folder1/page1。

請求處理

      請求處理閱讀請求及它的參數和cookies。它會讀取也可能更新一些資料,并講資料存儲在伺服器上。然後,需求處理會生成一個HTML響應。

    所有動态網站都面臨一個有意思的難點-如何存儲資料。小網站一半都會有一個SQL資料庫來存儲資料,存儲大量資料和/或通路量大的網站不得不找一些辦    法把資料庫配置設定到多台機器上。解決方案 有:sharding (基于主鍵值講資料表分散到多個資料庫中),複制,利用弱語義一緻性的簡化資料庫。

第七步:伺服器發回HTML響應

第八步:浏覽器顯示HTML

在浏覽器沒有完整接受全部HTML文檔時,它就已經開始顯示這個頁面了

第九步:浏覽器發送擷取嵌入在HTML中的對象

在浏覽器顯示HTML時,它會注意到需要擷取其他位址内容的标簽。這時,浏覽器會發送一個擷取請求來重新獲得這些檔案。

下面是幾個我們通路facebook.com時需要重擷取的幾個URL:

圖檔

http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif

http://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif

CSS 式樣表

http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css

http://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css

JavaScript 檔案

http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js

http://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js

這些位址都要經曆一個和HTML讀取類似的過程。是以浏覽器會在DNS中查找這些域名,發送請求,重定向等等...

     但不像動态頁面那樣,靜态檔案會允許浏覽器對其進行緩存。有的檔案可能會不需要與伺服器通訊,而從緩存中直接讀取。伺服器的響應中包含了靜态文     件儲存的期限資訊,是以浏覽器知道要把它們緩存多長時間。還有,每個響應都可能包含像版本号一樣工作的ETag頭(被請求變量的實體值),如果浏覽     器觀察到檔案的版本 ETag資訊已經存在,就馬上停止這個檔案的傳輸。

試着猜猜看“fbcdn.net”在位址中代表什麼?聰明的答案是"Facebook内容分發網絡"。Facebook利用内容分發網絡(CDN)分發像圖檔,CSS表和Java     Script檔案這些靜态檔案。是以,這些檔案會在全球很多CDN的資料中心中留下備份。

舉例來講,當你試着ping static.ak.fbcdn.net的時候,可能會從某個akamai.net伺服器上獲得響應。有意思的是,當你同樣再ping一次的時候,響      應的伺服器可能就不一樣,這說明幕後的負載平衡開始起作用了

第十步:浏覽器發送異步(AJAX)請求

在Web 2.0偉大精神的指引下,頁面顯示完成後用戶端仍與伺服器端保持着聯系。

以 Facebook聊天功能為例,它會持續與伺服器保持聯系來及時更新你那些亮亮灰灰的好友狀态。為了更新這些頭像亮着的好友狀态,在浏覽器中執行     的JavaScript代碼會給伺服器發送異步請求。這個異步請求發送給特定的位址,它是一個按照程式構造的擷取或發送請求。還是在Facebook這個例子中,     用戶端發送給http://www.facebook.com/ajax/chat/buddy_list.php一個釋出請求來擷取你好友裡哪個線上的狀态資訊。

提起這個模式,就必須要講講"AJAX"-- “異步JavaScript 和 XML”,雖然伺服器為什麼用XML格式來進行響應也沒有個一清二白的原因。再舉個例子     吧,對于異步請求,Facebook會傳回一些JavaScript的代碼片段

除了其他,fiddler這個工具能夠讓你看到浏覽器發送的異步請求。事實上,你不僅可以被動的做為這些請求的看客,還能主動出擊修改和重新發送它     們。AJAX請求這麼容易被蒙,可着實讓那些計分的線上遊戲開發者們郁悶的了。

Facebook聊天功能提供了關于AJAX一個有意思的問題案例:把資料從伺服器端推送到用戶端。因為HTTP是一個請求-響應協定,是以聊天伺服器不能把     新消息發給客戶。取而代之的是用戶端不得不隔幾秒就輪詢下伺服器端看自己有沒有新消息。

這些情況發生時長輪詢是個減輕伺服器負載挺有趣的技術。如果當被輪詢時伺服器沒有新消息,它就不理這個用戶端。而當尚未逾時的情況下收到了      該客戶的新消息,伺服器就會找到未完成的請求,把新消息做為響應傳回給用戶端。

                                       《你剛在淘寶上買了一件衣服---詳細解析技術流程》

        你發現快要過年了,于是想給你的女朋友買一件毛衣,你打開了www.taobao.com。這時你的浏覽器首先查詢DNS伺服器,将www.taobao.com轉換成ip位址。不過首先你會發現,你在不同的地區或者不同的網絡(電信、聯通、移動)的情況下,轉換後的IP位址很可能是 不一樣的,這首先涉及到負載均衡的第一步,通過DNS解析域名時将你的通路配置設定到不同的入口,同時盡可能保證你所通路的入口是所有入口中可能較快的一個 (這和後文的CDN不一樣)。

  你通過這個入口成功的通路了www.taobao.com的實際的入口IP位址。這時你産生了一個PV,即Page View,頁面通路。每日每個網站的總PV量是形容一個網站規模的重要名額。淘寶網全網在平日(非促銷期間)的PV大概是16-25億之間。同時作為一個獨立的使用者,你這次通路淘寶網的所有頁面,均算作一個UV(Unique Visitor使用者通路)。最近臭名昭著的12306.cn的日PV量最高峰在10億左右,而UV量卻遠小于淘寶網十餘倍,這其中的原因我相信大家都會知道。

  因為同一時刻通路www.taobao.com的人數過于巨大,是以即便是生成淘寶首頁頁面的伺服器,也不可能僅有一台。僅用于生成www.taobao.com首頁的伺服器就可能有成百上千台,那麼你的一次通路時生成頁面給你看的任務便會被配置設定給其中一台伺服器完成。這個過程要保證公正、公平、平均(暨這成百上千台伺服器每台負擔的使用者數要差不多),這一很複雜的過程是由幾個系統配合完成,其中最關鍵的便是LVS(Linux Virtual Server),世界上最流行的負載均衡系統之一,正是由目前在淘寶網供職的章文嵩博士開發的。

  經過一系列複雜的邏輯運算和資料處理,用于這次給你看的淘寶網首頁的HTML内容便生成成功了。對web前端稍微有點常識的童鞋都應該知道,下一步浏覽器會去加載頁面中用到的css、js、圖檔、腳本和資源檔案。但是可能相對較少的同學才會知道,你的浏覽器在同一個域名下并發加載的資源數量是有限制的,例如IE6-7是兩個,IE8是6個,Chrome各版本不大一樣,一般是4-6個。我剛剛看了一下,我通路淘寶網首頁需要加載126個資源,那麼如此小的并發連接配接數自然會加載很久。是以前端開發人員往往會将上述這些資源檔案分布在好多個域名下,變相的繞過浏覽器的這個限制,同時也為下文的CDN工作做準備。

  據不可靠消息,在雙十一當天高峰,淘寶的通路流量最巅峰達到871GB/S。這個數字意味着需要178萬個4Mb帶寬的家庭寬帶才能負擔的起,也完全有能力拖垮一個中小城市的全部網際網路帶寬。那麼顯然,這些通路流量不可能集中在一起。并且大家都知道,不同地區不同網絡(電信、聯通等)之間互訪會非常緩慢,但是你卻發現很少發現淘寶網通路緩慢。這便是CDN(Content Delivery Network),即内容分發網絡的作用。淘寶在全國各地建立了數十上百個CDN節點,利用一些手段保證你通路的(這裡主要指js、css、圖檔等)地方是離你最近的CDN節點,這樣便保證了大流量分散在各地通路的加速節點上。

  這便出現了一個問題,那就是假若一個賣家釋出了一個新的寶貝,上傳了幾張新的寶貝圖檔,那麼淘寶網如何保證全國各地的CDN節點中都會同步的存在這幾張圖 片供使用者使用呢?這裡邊就涉及到了大量的内容分發與同步的相關技術。淘寶開發了分布式檔案系統TFS(Taobao File System)來處理這類問題。

  好了,這時你終于加載完了淘寶首頁,那麼你習慣性的在首頁搜尋框中輸入了'毛衣'二字并敲回車,這時你又産生了一個PV,然後,淘寶網的主搜尋系統便開始為你服務了。它首先對你輸入的内容基于一個分詞庫進行分詞操作。衆所周知,英文是以詞為機關的,詞和詞之間是靠空格隔開,而中文是以字為機關,句子中所有的字連起來才能描述一個意思。例如,英文句子I am a student,用中文則為:“我是一個學生”。計算機可以很簡單通過空格知道student是一個單詞,但是不能很容易明白“學”、“生”兩個字合起來才表示一個詞。把中文的漢字序列切分成有意義的詞,就是中文分詞,有些人也稱為切詞。我是一個學生,分詞的結果是:我 是 一個 學生。

  進行分詞之後,還需要根據你輸入的搜尋詞進行你的購物意圖分析。使用者進行搜尋時常常有如下幾類意圖:(1)浏覽型:沒有明确的購物對象和意圖,邊看邊買,使用者比較随意和感性。Query例如:”2010年10大香水排行”,”2010年流行毛衣”, “zippo有多少種類?”;(2)查詢型:有一定的購物意圖,展現在對屬性的要求上。Query例如:”适合老人用的手機”,”500元 手表”;(3)對比型:已經縮小了購物意圖,具體到了某幾個産品。Query例如:”諾基亞E71 E63″,”akg k450 px200″;(4)确定型:已經做了基本決定,重點考察某個對象。Query例如:”諾基亞N97″,”IBM T60″。通過對你的購物意圖的分析,主搜尋會呈現出完全不同的結果來。

  之後的數個步驟後,主搜尋系統便根據上述以及更多複雜的條件列出了搜尋結果,這一切是由一千多台搜尋伺服器完成。然後你開始逐一點選浏覽搜尋出的寶貝。你開始檢視寶貝詳情頁面。經常網購的親們會發現,當你買過了一個寶貝之後,即便是商家多次修改了寶貝詳情頁,你仍然能夠通過‘已買到的寶貝’檢視當時的快照。這是為了防止商家對在商品詳情中承諾過的東西賴賬不認。那麼顯然,對于每年數十上百億比交易的商品詳情快照進行儲存和快速調用不是一個簡單的事情。這 其中又涉及到數套系統的共同協作,其中較為重要的是Tair,淘寶自行研發的分布式KV存儲方案。

  然後無論你是否真正進行了交易,你的這些通路行為便忠實的被系統記錄下來,用于後續的業務邏輯和資料分析。這些記錄中通路日志記錄便是最重要的記錄之一, 但是前邊我們得知,這些通路是分布在各個地區很多不同的伺服器上的,并且由于使用者衆多,這些日志記錄都非常龐大,達到TB級别非常正常。那麼為了快速及時 傳輸同步這些日志資料,淘寶研發了TimeTunnel,用于進行實時的資料傳輸,交給後端系統進行計算報表等操作。

  你的浏覽資料、交易資料以及其它很多很多的資料記錄均會被保留下來。使得淘寶存儲的曆史資料輕而易舉的便達到了十數甚至更多個 PB(1PB=1024TB=1048576GB)。如此巨大的資料量經過淘寶系統1:120的極限壓縮存儲在淘寶的資料倉庫中。并且通過一個叫做雲梯的,由2000多台伺服器組成的超大規模資料系統不斷的進行分析和挖掘。

  從這些資料中淘寶能夠知道小到你是誰,你喜歡什麼,你的孩子幾歲了,你是否在談戀愛,喜歡玩魔獸世界的人喜歡什麼樣的飲料等,大到各行各業的零售情況、各類商品的興衰消亡等等海量的資訊。    

  說了這麼多,其實也隻是叙述了淘寶上正在運作的成千上萬個系統中的寥寥幾個。即便是你僅僅通路一次淘寶的首頁,所涉及到的技術和系統規模都是你完全無法想 象的,是淘寶2000多名頂級的工程師們的心血結晶,其中甚至包括長江學者、國家科學技術最高獎得主等衆多大牛。同樣,百度、騰訊等的業務系統也絕不比淘寶簡單。你需要知道的是,你每天使用的網際網路産品,看似簡單易用,背後卻凝聚着難以想象的智慧與勞動。