從輸入 url 開始能作哪些優化

長文,梳理優化措施的做用位置。css

此文主要講的事情是如何讓用戶快點看到首屏頁面,其主要影響因素是延遲和解析渲染耗時。有關安所有分其實也是優化的一部分。咱們着重說下網絡部分。html

大體過程:DNS 域名解析、創建 TCP 鏈接、下載資源、解析頁面。文章描述的優化會盡可能限制在當時的分析的過程下。前端

1.DNS域名解析

通常來說,咱們輸入的 url 是域名,而爲了識別一個實體,TCP/IP 使用 IP 地址來惟一肯定一臺主機到因特網的鏈接,DNS會幫助咱們完成域名到IP地址映射的工做。以www.aaa.com爲例,解析過程大體以下:java

過程

  • 瀏覽器ajax

    • 瀏覽器查詢瀏覽器緩存,沒有。
  • 本機層segmentfault

    • 瀏覽器客戶端向系統詢問服務器IP地址,調用本機內的DNS解析程序,檢查本身本地的hosts文件是否有這個域名映射關係,沒有。
    • 查找本機的DNS解析器緩存,沒有。
  • 路由器緩存瀏覽器

    • 可能還存在路由器緩存這一層
  • 本地DNS服務器緩存

    • 本機的DNS解析程序向本地的DNS服務器發起請求,通常爲TCP/IP參數中設置的首選DNS服務器,是知道IP地址的,通常會UDP協議。
    • 本地DNS服務器查詢是否在本地區域文件中,沒有。
    • 本地DNS服務器查詢DNS緩存中是否存在,沒有。
    • 本地DNS服務器會根據是否設置轉發器判斷是向上一級DNS服務器(其解析規則同理)仍是直接向根DNS服務器(知道根DNS服務器的IP地址)發送請求。
  • 與DNS服務器安全

    • 收到請求後,根DNS服務器並不直接解析地址,可是知道每一個頂級域中的一臺服務器的地址(如com域名服務器)。若是爲迭代查詢方式,此頂級域DNS服務器的ip被返回給本地DNS服務器。
    • 本地DNS服務器提取到頂級域DNS服務器信息後,會再向其發出請求。頂級域DNS服務器收到請求後,會先查詢本身的緩存,沒有,則將負責的二級域名服務器(如aaa.com域名服務器)返回給本地DNS服務器,以此類推直到查到目標域名的映射信息或查詢失敗。
    • 查到映射信息後返回到本機,中間各層會進行緩存。
  • 查詢方式:性能優化

    • 遞歸方式:一路查下去中間不返回,獲得最終結果才返回信息。
    • 迭代方式:就是上面的本地DNS服務器與其餘域名服務器直接的查詢方式,查到一個可能知道的服務器地址,將此地址返回,從新發送解析請求。
    • 通常默認的方式從本機到本地DNS服務器是遞歸,DNS服務器之間是迭代查詢。

優化

固然針對DNS的優化就是減小DNS解析的時間,因爲瀏覽器緩存機制的存在,咱們只須要對首次訪問進行優化(雖然咱們如今只是請求了一個html文件,可是html文件裏還會有咱們後續要請求的css/js/img等),即適當減小要解析的域名個數,考慮到其餘優化機制能夠將頁面及頁面內資源發佈到2-4個域名上。

2.創建鏈接

TCP鏈接

好了,瀏覽器終於拿到服務器IP了,客戶端想要與服務器間通訊並傳遞消息須要開啓TCP(一種傳輸層協議)鏈接。

過程

  • 客戶端建立socket,向服務器目標端口發送鏈接創建請求,數據段包含位碼SYN(創建聯機標誌位) = 1,隨機數seq(順序號碼)= x,和其餘TCP標誌和選項。
  • 服務器有一個專門處理鏈接請求的welcome socket,接收到鏈接創建請求,置位碼SYN和ACK(確認標誌位)爲1,ack(確認號碼)= x + 1,隨機數seq = y,並返回。
  • 客戶端檢查ack是否等於x + 1,等於時,將ACK置爲1,SYN置爲0,將ack置爲y + 1發送至服務器端。
  • welcome socket檢查ack等於y + 1和ACK等於1後,建立新的socket,此socket由源IP/源端口、目標IP/目標端口標識,以後客戶端發送的數據都被引導向此新的socket,至此,TCP鏈接創建。

簡單來說:

// client: 
send({SYN: 1, seq: x, ...others})
                    |
                    ↓
//server: 
send({SYN: 1, ACK: 1, ack: x + 1, seq: y, ...others})
                    |
                    ↓
//client: 
ack === x + 1 ? send({ACK: 1, SYN: 0, ack: y + 1, ...others}) : 'hehe'
                    |
                    ↓
//server: 
ack === y + 1 && ACK === 1 ? new Socket() : ''
複製代碼

SSL/TLS

若是啓用了HTTPS進行加密,在使用TLS前還須要協商創建加密信道。

過程

  • 客戶端:TCP鏈接創建以後,再以純文本形式發送一些規格說明,隨機數Random1,TLS協議版本,支持的加密套件列表,支持或但願使用的其餘TLS選項。
  • 服務器:
    1. 取得TLS協議版本以備未來通訊使用,從客戶端提供的加密套件列表中選擇一個,生成隨機數Random2發送給客戶端;
    2. 附上本身的證書,將響應發送給客戶端;
    3. 同時,也可發送一個請求,要求客戶端提供證書以及其餘TLS擴展參數。
  • 客戶端:
    1. 同上,可能會向服務器發送本身的證書。
    2. 客戶端收到服務器的證書後,經過證書鏈關係從根CA(證書的簽發機構)驗證證書的合法性,驗證經過後取出證書中的服務器公鑰,生成隨機數Random3,再用服務器公鑰加密Random3(pre master key),發送給服務器;
    3. 告訴服務器能夠開始加密透明信了;
    4. 客戶端用三個隨機數約定的加密方法生成對話密鑰。將前面的握手信息生成完成摘要,使用對話密鑰加密,發送告訴服務器我已完成握手。
    • 除了公鑰加密隨機數和對話密鑰加密的摘要外,其餘全部的數據都是明文形式發送。
  • 服務器:
    • 用私鑰解密出客戶端發來的隨機數,經過驗證消息的MAC檢測消息完整性,用相同的方式生成對話密鑰
    • 解密客戶端發送的完成報文,驗證對話密鑰是否正確。
    1. 告訴客戶端,要開始加密了;
    2. 一樣再返回給客戶端一個加密的完成消息。
  • 客戶端用它以前生成的對話密鑰解密這條消息,肯定對話密鑰是否正確,正確則創建信道而且開始發送應用數據。

其中:

  • 對話密鑰又可稱爲協商密鑰
  • 對話密鑰對稱密鑰,對稱加解密速度很快。
  • 服務器公鑰和私鑰是非對稱密鑰,非對稱加解密速度很慢。
  • 使用非對稱加密生成可靠的對稱密鑰,使用對稱密鑰進行後續數據的加密。
  • 上述帶序號報文可能一次發送,也可能分次連續發送。
  • SSL和TLS能夠看成一個東西。
  • 服務器也能夠不使用CA頒發的證書,而使用本身的證書。

優化

咱們要對TCP和SSL/TLS握手耗時進行優化。有如下幾個因素:

  • 數據往返延遲:主要受地理位置影響,使用較近的服務器進行數據傳輸會減小數據往返的時間,咱們能夠經過在不一樣的地區部署服務器(如:CDN,其也會用到DNS解析,可能在DNS解析階段就完成了對客戶端訪問域名到距離最近的服務器的映射),將數據放到接近客戶端的地方,能夠減小網絡往返時間。
  • 證書鏈:其實數據往返延遲優化不僅是針對TCP握手階段的,後續基於TCP的數據傳輸都會收益,如SSL/TLS握手和後續的請求響應。那麼證書鏈是影響SSL/TLS握手的一個重要因素,證書鏈是服務器向客戶端發送的證書內的信息,由站點證書、中間證書頒發機構的證書、根證書組成(比較相似DNS域名解析服務器之間的關係)。
    • 緣由:
      1. TCP慢啓動:因爲TCP慢啓動(爲避免擁塞,TCP鏈接初始只能發送較少的分組,而後等待客戶端確認,而後翻倍,通過幾回往返直至到達閾值)和TLS/SSL握手數據發送通常位於TCP鏈接慢啓動階段的關係,證書數據過多會超過TCP鏈接的初始值,會形成數據往返次數的成倍增長。
      2. 證書鏈驗證過長:因爲客戶端瀏覽器在驗證證書可靠性時,會遞歸驗證鏈條中的每一個節點至根證書,也會增長握手時間。
    • 方法:
      1. 減小中間證書頒發機構的數量,優化至只有站點證書和一箇中間證書頒發機構。
      2. 不要添加根證書信息,瀏覽器內置信任名單中有根證書。
  • 握手次數:前兩點優化都是針對的握手時間的優化,握手次數也是影響延遲的重要因素。咱們在後面談到大量請求的時候再說這一點。
  • 初始擁塞窗口:適當增大初始擁塞窗口大小,即增大TCP鏈接初始可發送的分組大小。

3.得到頁面響應

重定向響應

若是服務器返回了跳轉重定向(非緩存重定向),那麼瀏覽器端就會向新的URL地址從新走一遍DNS解析創建鏈接。 因此應該避免沒必要要的重定向。

頁面資源響應

在得到了html響應以後,瀏覽器開始解析頁面,進入準備渲染的階段。下載優化一樣放在後面談到大量請求的時候再說這一點。

4.解析渲染頁面

咱們須要將這個過程先分爲兩個部分來看,頁面資源加載渲染

頁面資源加載

瀏覽器在解析頁面的過程當中會去請求頁面中諸如js、css、img等外聯資源。

4.1 創建鏈接

一樣這些資源的加載也是須要創建TCP鏈接的,直接使用也須要進行DNS解析和握手。

優化

此處的請求次數與頻率相對於第一次請求頁面資源時要高不少,因此這裏着重闡述下成批量的請求的優化。

瀏覽器目前使用的HTTP協議版本大可能是1.1和2,兩者有些不一樣,可是底層都是使用TCP進行數據傳輸。因爲TCP握手耗時,和SSL/TLS更加耗時,咱們須要減小整個加載過程當中須要創建的鏈接的次數和耗時。

  • 複用:針對HTTP1.1的最好方法是啓用長鏈接:HTTP 1.1提供了默認開啓長鏈接功能,相對於短鏈接(每請求一個資源創建而後斷開一次TCP鏈接),同一客戶端socket(瀏覽器可能會開多個端口並行請求)針對同一socket(域名+端口)後續請求都會複用一個TCP鏈接進行傳輸,直到關閉這個TCP鏈接。
  • 加速:針對SSL/TLS握手有會話恢復機制,驗證經過後,能夠直接使用以前的對話密鑰,減小握手往返。

4.2 加載以前

在服務器返回響應時,又存在幾種狀況,如:服務器負載大,服務器宕機,沒法及時或較快響應請求,服務器地理位置過遠或跨運營商致使延遲很高。

優化

這裏跟創建鏈接部分的優化實際上是公用的,可是單純的正常創建鏈接消耗資源較少,因此咱們在這個再較完成的闡述一下。

  • 增長帶寬:可是大部分狀況下服務器帶寬並非影響延遲的主要因素。
  • 智能DNS解析:根據客戶端的IP地址,將域名解析爲最近的或不跨運營商的服務器的IP地址,解決地理位置和跨運營商的延遲問題。
  • CDN:使用某種分析方式根據節點服務器的地理位置、負載狀況、資源匹配狀況從遍及各地的節點服務器中找出最合適的靜態資源服務器。
  • 負載均衡:使用DNS負載均衡、IP負載均衡、反向代理負載均衡等方式從一堆服務器(集羣相同職責)或一組服務器(分佈式職責區分)中選擇最合適的服務器處理請求。
  • 這幾種技術多是相互結合的,好比CDN會用到DNS智能解析和負載均衡等。
  • 其中使用了跳轉重定向方式的會從新進行DNS解析和握手,其中一部分優化實際是在域名的DNS解析部分完成的。

4.3 開始加載

好了,服務器終於能夠愉快的返回數據了。

HTTP 1.1

過程

  • 雖然HTTP 1.1有長鏈接,一個TCP鏈接能夠用來請求多個資源,可是這些資源的下載是串行的,好比使用這個TCP通道請求1.css、2.css、1.js,只有在前者傳輸成功完整完成後纔會進行下一個的傳輸。
  • 雖然瀏覽器通常會請求創建多個TCP鏈接(多個端口向服務器一個端口請求資源,新的TCP鏈接的創建會進行新的握手),去並行的請求頁面資源加快總體的下載速度,然而對每一個域名的並行鏈接是有數量限制的(保護服務器負載,並受主機端口限制),因此咱們仍是要進行一些優化。

優化

  • 減小
    • 減小頁面中須要發起的請求總數,如咱們常規使用的代碼合併,雪碧圖(精靈圖/Sprite合併小圖標),將圖片轉爲base64寫入其餘文件,避免空的img src屬性等。
    • 切割拆分數據,讓首屏數據優先加載等。
  • 增長:增長資源的分佈域名,部署在不一樣域名下,「突破」瀏覽器並行鏈接限制(結合DNS部分,不易過於分散,且過多鏈接會共享帶寬,且移動端的解析更加緩慢)。

HTTP 2

因爲HTTP 2提供了多路複用的功能,基於二進制數據幀和流的傳輸,使經過一個TCP鏈接進行數據分散、亂序、並行傳輸成爲現實,即咱們全部的資源均可以經過一個TCP鏈接不阻塞的並行傳輸。
因此咱們針對HTTP 1.1的減小請求數量所作的合併優化、增長資源分佈域名都成爲了無效優化,能夠丟掉。同時因爲文件不用合併,進行文件更新時咱們也不用再修改單個開發模塊更新全部(合併文件)模塊了。

4.4 加載中

總的來講是很簡單的過程,客戶端接收服務器傳輸返回的響應。

優化

傳輸的數據大小越小,那麼傳輸就越快,延遲就越小。

  • 更小

    • Gzip:啓用Gzip可對響應體進行壓縮,可減小70%大小的數據體積。
    • 減小cookie:去除沒必要要的cookie,設置合適過時時間。
    • 捨棄cookie:對於靜態文件請求咱們能夠不要cookie,即HTTP1.1中提到的,分佈在其餘域名下,子域名設置合理的domian(cookie做用域)。
    • 首部壓縮:HTTP2還提供了首部壓縮功能,即經過雙方共有的一些字典,將首部信息(狀態行、請求/響應頭)「映射」爲更簡短的數據。
    • 圖片:使用合適的圖片大小和圖片格式,能夠節省大小。
  • 緩存:小到最小的狀況固然是不接受數據傳輸,使用本地緩存。通常使用服務器前一次返回的響應頭部字段進行控制。

    • 強緩存:強緩存不會向服務器發送請求。
      • Expires:http1.0字段,使用服務器時間作標識。
      • Cache-Control:max-age=seconds,使用相對於請求的時間,不超過這個時長,直接使用緩存。還有其餘的值。
    • 協商緩存:
      • Last-Modified/If-Modified-Since:資源最後修改時間,秒。瀏覽器客戶端發送If-Modified-Since字段,服務器響應Last-Modified字段。
      • ETag/If-None-Match:資源的標識符,客戶端發送If-None-Match字段,服務器響應ETag字段,並比較二者,決定返回緩存重定向仍是其餘,這個標識只比較內容,不關心資源時間。
  • 合理拆分頁面資源,好比外聯js和css就能夠獨立於html進行緩存。

4.5 關閉TCP

在資源下載完畢以後,須要關閉TCP鏈接。這段沒有什麼能夠優化的。

過程:

  • TCP客戶端發送一個FIN = 1(結束標誌位)和隨機數seq = a,用來關閉客戶到服務器的數據傳送。
  • 服務器收到這個關閉請求,返回ACK = 1 ,ack = a + 1,此時服務器以前的數據可能尚未傳輸完成。
  • 數據傳輸完成後,服務器發送一個FIN = 1和隨機數seq = b給客戶端。
  • 客戶端返回ACK = 1,ack = b + 1,並等待一段時間,確保服務器沒有返回沒收到確認報文的重傳申請,後關閉鏈接。
  • 服務器收到確認報文後,驗證關閉鏈接。

總結

HTTP2 真好用。合理使用緩存。

頁面解析渲染

上述的資源加載是發生在頁面解析過程當中的。那麼瀏覽器的頁面解析渲染是怎麼樣的一個過程呢?

過程

簡要來說就是:

  • 處理HTML標記,構建DOM樹。
  • 處理CSS標記,構建CSSOM樹。
  • 將DOM樹和CSSOM樹融合成渲染樹(會忽略不須要渲染的dom)。
  • 根據渲染樹來佈局,計算每一個節點的幾何信息。
  • 在屏幕上繪製各個節點。
  • 中間遇到各類資源時,會進行資源的下載。

問題

  • 資源下載
    • css下載時會阻塞渲染(帶有media屬性除外)。
    • 遇到 script 標籤時,DOM構建中止,此時控制權移交至js,直到腳本(下載)執行完畢,此時瀏覽器有優化通常會下載其餘資源,可是不會解析。若是js中有對CSSOM的操做,還會先確保CSSOM已經被下載並構建。
    • 圖片資源下載不會產生阻塞。
  • 重繪重排致使從新進行渲染樹的生成
    • 重排(迴流):會從新計算佈局,一般由元素的結構、增刪、位置、尺寸變化引發,如:img下載成功後,替換填充頁面img元素,引發尺寸變化;也會由js的屬性值讀取引發,如讀取offset、scroll、cilent、getComputedStyle等信息。
    • 重繪:簡單外觀的改變會引發重繪,如顏色變化等。
    • 重排必定重繪。

優化

  • dom
    • 簡化dom結構,減小DOM樹和渲染樹構建成本,減小頁面元素個數,如使用列表表格數據分頁,簡單表格不要使用複雜第三方組件等方式。
  • js
    • 將js腳本標籤放在頁面body底部,減小對其餘過程的阻塞。
    • 延遲執行:對不修改頁面的外鏈script使用defer屬性,使腳本並行下載不阻塞,下載後不馬上執行,而在全部元素解析以後執行。
    • 減小和合並沒必要要的dom相關操做,如使用DocumentFragment、修改classname而不是各項style等,減小對重繪和重排的觸發。
  • css
    • 將css放入head中,提早加載,並防止html渲染後從新結合css引發頁面閃爍。
    • 減小css層級和css選擇器複雜度,提升解析速度,雖然瀏覽器有優化。
    • 使用更高性能的css樣式,如flex代替float。
    • 開啓複合層,如使用3d變換、opacity等,使該元素及其子元素不致使外部的重排,可是也有坑
    • 合理使用脫離文檔流的樣式,減小對外部重排的影響,如absolute。
  • 文件數量
    • 減小首次下載的文件數量大小,使用圖片懶加載,js的按需加載等方式,也能夠節省用戶流量,甚至使用storage存儲進行js、css文件的緩存。
    • 拆分頁面資源,首屏數據優先加載等。

5.其餘優化措施

咱們還能夠採起一些和延遲、渲染無關的優化措施:

  • 使用PWA,讓用戶在沒有獲得數據時也能看到頁面。
  • 對頁面某些ajax請求數據進行storage存儲。
  • 加載進度、骨架圖、佔位圖等相似讓用戶感受好一點的措施。
  • 及時更新升級服務器,優化措施依賴於服務器支持。

參考

相關文章
相關標籤/搜索