本文主要是側重於 HTTP 的優化,對於 HTTPS 后續文章會講。
既然要做性能優化,那么,我們就需要知道:什么是性能?它都有哪些指標,又應該如何度量,進而采取哪些手段去優化?
“性能”其實是一個復雜的概念。不同的人、不同的應用場景都會對它有不同的定義。對於 HTTP 來說,它又是一個非常復雜的系統,里面有非常多的角色,所以很難用一兩個簡單的詞就能把性能描述清楚。
還是從 HTTP 最基本的“請求 - 應答”模型來着手吧。在這個模型里有兩個角色:客戶端和服務器,還有中間的傳輸鏈路,考查性能就可以看這三個部分。
HTTP 服務器
我們先來看看服務器,它一般運行在 Linux 操作系統上,用 Apache、Nginx 等 Web 服務器軟件對外提供服務,所以,性能的含義就是它的服務能力,也就是盡可能多、盡可能快地處理用戶的請求。
衡量服務器性能的主要指標有三個:吞吐量(requests per second)、並發數(concurrency)和響應時間(time per request)。
吞吐量就是我們常說的 RPS,每秒的請求次數,也有叫 TPS、QPS,它是服務器最基本的性能指標,RPS 越高就說明服務器的性能越好。
並發數反映的是服務器的負載能力,也就是服務器能夠同時支持的客戶端數量,當然也是越多越好,能夠服務更多的用戶。
響應時間反映的是服務器的處理能力,也就是快慢程度,響應時間越短,單位時間內服務器就能夠給越多的用戶提供服務,提高吞吐量和並發數。
除了上面的三個基本性能指標,服務器還要考慮 CPU、內存、硬盤和網卡等系統資源的占用程度,利用率過高或者過低都可能有問題。
在 HTTP 多年的發展過程中,已經出現了很多成熟的工具來測量這些服務器的性能指標,開源的、商業的、命令行的、圖形化的都有。
在 Linux 上,最常用的性能測試工具可能就是 ab(Apache Bench)了,比如,下面的命令指定了並發數 100,總共發送 10000 個請求:
ab -c 100 -n 10000 'http://www.xxx.com'
top // 查看 CPU 和內存占用情況 vmstat 2 // 每 2 秒檢查一次系統狀態 sar -n DEV 2 // 看所有網卡的流量,定時 2 秒檢查
理解了這些性能指標,我們就知道了服務器的性能優化方向:合理利用系統資源,提高服務器的吞吐量和並發數,降低響應時間。
HTTP 客戶端
看完了服務器的性能指標,我們再來看看如何度量客戶端的性能。
客戶端是信息的消費者,一切數據都要通過網絡從服務器獲取,所以它最基本的性能指標就是“延遲”(latency)。
之前在講 HTTP/2 的時候就簡單介紹過延遲。所謂的“延遲”其實就是“等待”,等待數據到達客戶端時所花費的時間。但因為 HTTP 的傳輸鏈路很復雜,所以延遲的原因也就多種多樣。
-
首先,我們必須謹記有一個“不可逾越”的障礙——光速,因為地理距離而導致的延遲是無法克服的,訪問數千公里外的網站顯然會有更大的延遲。
-
其次,第二個因素是帶寬,它又包括接入互聯網時的電纜、WiFi、4G 和運營商內部網絡、運營商之間網絡的各種帶寬,每一處都有可能成為數據傳輸的瓶頸,降低傳輸速度,增加延遲。
-
第三個因素是 DNS 查詢,如果域名在本地沒有緩存,就必須向 DNS 系統發起查詢,引發一連串的網絡通信成本,而在獲取 IP 地址之前客戶端只能等待,無法訪問網站,
-
第四個因素是 TCP 握手,你應該對它比較熟悉了吧,必須要經過 SYN、SYN/ACK、ACK 三個包之后才能建立連接,它帶來的延遲由光速和帶寬共同決定。
建立 TCP 連接之后,就是正常的數據收發了,后面還有解析 HTML、執行 JavaScript、排版渲染等等,這些也會耗費一些時間。不過它們已經不屬於 HTTP 了,所以不在今天的討論范圍之內。
之前講 HTTPS 時介紹過一個專門的網站“SSLLabs”,而對於 HTTP 性能優化,也有一個專門的測試網站“WebPageTest”。它的特點是在世界各地建立了很多的測試點,可以任意選擇地理位置、機型、操作系統和瀏覽器發起測試,非常方便,用法也很簡單。
網站測試的最終結果是一個直觀的“瀑布圖”(Waterfall Chart),清晰地列出了頁面中所有資源加載的先后順序和時間消耗,比如下圖就是對 GitHub 首頁的一次測試。
Chrome 等瀏覽器自帶的開發者工具也可以很好地觀察客戶端延遲指標,面板左邊有每個 URI 具體消耗的時間,面板的右邊也是類似的瀑布圖。
點擊某個 URI,在 Timing 頁里會顯示出一個小型的“瀑布圖”,是這個資源消耗時間的詳細分解,延遲的原因都列的清清楚楚,比如下面的這張圖:
圖里面的這些指標都是什么含義呢?我給你解釋一下:
-
因為有“隊頭阻塞”,瀏覽器對每個域名最多開 6 個並發連接(HTTP/1.1),當頁面里鏈接很多的時候就必須排隊等待(Queued、Queueing),這里它就等待了 1.62 秒,然后才被瀏覽器正式處理;
-
瀏覽器要預先分配資源,調度連接,花費了 11.56 毫秒(Stalled);
-
連接前必須要解析域名,這里因為有本地緩存,所以只消耗了 0.41 毫秒(DNS Lookup);
-
與網站服務器建立連接的成本很高,總共花費了 270.87 毫秒,其中有 134.89 毫秒用於 TLS 握手,那么 TCP 握手的時間就是 135.98 毫秒(Initial connection、SSL);
-
實際發送數據非常快,只用了 0.11 毫秒(Request sent);
-
之后就是等待服務器的響應,專有名詞叫 TTFB(Time To First Byte),也就是“首字節響應時間”,里面包括了服務器的處理時間和網絡傳輸時間,花了 124.2 毫秒;
-
接收數據也是非常快的,用了 3.58 毫秒(Content Dowload)。
從這張圖你可以看到,一次 HTTP“請求 - 響應”的過程中延遲的時間是非常驚人的,總時間 415.04 毫秒里占了差不多 99%。
所以,客戶端 HTTP 性能優化的關鍵就是:降低延遲。
HTTP 傳輸鏈路
以 HTTP 基本的“請求 - 應答”模型為出發點,剛才我們得到了 HTTP 性能優化的一些指標,現在,我們來把視角放大到“真實的世界”,看看客戶端和服務器之間的傳輸鏈路,它也是影響 HTTP 性能的關鍵。
這就是所謂的“第一公里”“中間一公里”和“最后一公里”(在英語原文中是 mile,英里)。
“第一公里”是指網站的出口,也就是服務器接入互聯網的傳輸線路,它的帶寬直接決定了網站對外的服務能力,也就是吞吐量等指標。顯然,優化性能應該在這“第一公里”加大投入,盡量購買大帶寬,接入更多的運營商網絡。
“中間一公里”就是由許多小網絡組成的實際的互聯網,其實它遠不止“一公里”,而是非常非常龐大和復雜的網絡,地理距離、網絡互通都嚴重影響了傳輸速度。好在這里面有一個 HTTP 的“好幫手”——CDN,它可以幫助網站跨越“千山萬水”,讓這段距離看起來真的就好像只有“一公里”。
“最后一公里”是用戶訪問互聯網的入口,對於固網用戶就是光纖、網線,對於移動用戶就是 WiFi、基站。以前它是客戶端性能的主要瓶頸,延遲大帶寬小,但隨着近幾年 4G 和高速寬帶的普及,“最后一公里”的情況已經好了很多,不再是制約性能的主要因素了。
除了這“三公里”,我個人認為還有一個“第零公里”, 就是網站內部的 Web 服務系統。它其實也是一個小型的網絡(當然也可能會非常大),中間的數據處理、傳輸會導致延遲,增加服務器的響應時間,也是一個不可忽視的優化點。
在上面整個互聯網傳輸鏈路中,末端的“最后一公里”我們是無法控制的,所以我們只能在“第零公里”“第一公里”和“中間一公里”這幾個部分下功夫,增加帶寬降低延遲,優化傳輸速度。
但因為我們是無法完全控制客戶端的,所以實際上的優化工作通常是在服務器端。這里又可以細分為后端和前端,后端是指網站的后台服務,而前端就是 HTML、CSS、圖片等展現在客戶端的代碼和數據。
知道了大致的方向,HTTP 性能優化具體應該怎么做呢?
總的來說,任何計算機系統的優化都可以分成這么幾類:硬件軟件、內部外部、花錢不花錢。
投資購買現成的硬件最簡單的優化方式,比如換上更強的 CPU、更快的網卡、更大的帶寬、更多的服務器,效果也會“立竿見影”,直接提升網站的服務能力,也就實現了 HTTP 優化。
另外,花錢購買外部的軟件或者服務也是一種行之有效的優化方式,最“物有所值”的應該算是 CDN 了。CDN 專注於網絡內容交付,幫助網站解決“中間一公里”的問題,還有很多其他非常專業的優化功能。把網站交給 CDN 運營,就好像是“讓網站坐上了噴氣飛機”,能夠直達用戶,幾乎不需要費什么力氣就能夠達成很好的優化效果。
不過這些“花錢”的手段實在是太沒有“技術含量”了,屬於“懶人”(無貶義)的做法,所以我就不再細說,接下來重點就講講在網站內部、“不花錢”的軟件優化。
我把這方面的 HTTP 性能優化概括為三個關鍵詞:開源、節流、緩存。
開源
這個“開源”可不是 Open Source,而是指抓“源頭”,開發網站服務器自身的潛力,在現有條件不變的情況下盡量挖掘出更多的服務能力。
首先,我們應該選用高性能的 Web 服務器,最佳選擇當然就是 Nginx/OpenResty 了,盡量不要選擇基於 Java、Python、Ruby 的其他服務器,它們用來做后面的業務邏輯服務器更好。利用 Nginx 強大的反向代理能力實現“動靜分離”,動態頁面交給 Tomcat、Django、Rails,圖片、樣式表等靜態資源交給 Nginx。
Nginx 或者 OpenResty 自身也有很多配置參數可以用來進一步調優,舉幾個例子,比如說禁用負載均衡鎖、增大連接池,綁定 CPU 等等,相關的資料有很多。
特別要說的是,對於 HTTP 協議一定要啟用長連接。TCP 和 SSL 建立新連接的成本是非常高的,有可能會占到客戶端總延遲的一半以上。長連接雖然不能優化連接握手,但可以把成本“均攤”到多次請求里,這樣只有第一次請求會有延遲,之后的請求就不會有連接延遲,總體的延遲也就降低了。
另外,在現代操作系統上都已經支持 TCP 的新特性“TCP Fast Open”(Win10、iOS9、Linux 4.1),它的效果類似 TLS 的“False Start”,可以在初次握手的時候就傳輸數據,也就是 0-RTT,所以我們應該盡可能在操作系統和 Nginx 里開啟這個特性,減少外網和內網里的握手延遲。
下面給出一個簡短的 Nginx 配置示例,啟用了長連接等優化參數,實現了動靜分離:
server { listen 80 deferred reuseport backlog=4096 fastopen=1024; keepalive_timeout 60; keepalive_requests 10000; location ~* \.(png)$ { root /var/images/png/; } location ~* \.(php)$ { proxy_pass http://php_back_end; } }
節流
“節流”是指減少客戶端和服務器之間收發的數據量,在有限的帶寬里傳輸更多的內容。
“節流”最基本的做法就是使用 HTTP 協議內置的“數據壓縮”編碼,不僅可以選擇標准的 gzip,還可以積極嘗試新的壓縮算法 br,它有更好的壓縮效果。
不過在數據壓縮的時候應當注意選擇適當的壓縮率,不要追求最高壓縮比,否則會耗費服務器的計算資源,增加響應時間,降低服務能力,反而會“得不償失”。
gzip 和 br 是通用的壓縮算法,對於 HTTP 協議傳輸的各種格式數據,我們還可以有針對性地采用特殊的壓縮方式。
HTML/CSS/JS 屬於純文本,就可以采用特殊的“壓縮”,去掉源碼里多余的空格、換行、注釋等元素。這樣“壓縮”之后的文本雖然看起來很混亂,對“人類”不友好,但計算機仍然能夠毫無障礙地閱讀,不影響瀏覽器上的運行效果。
圖片在 HTTP 傳輸里占有非常高的比例,雖然它本身已經被壓縮過了,不能被 gzip、br 處理,但仍然有優化的空間。比如說,去除圖片里的拍攝時間、地點、機型等元數據,適當降低分辨率,縮小尺寸。圖片的格式也很關鍵,盡量選擇高壓縮率的格式,有損格式應該用 JPEG,無損格式應該用 Webp 格式。
對於小文本或者小圖片,還有一種叫做“資源合並”(Concatenation)的優化方式,就是把許多小資源合並成一個大資源,用一個請求全下載到客戶端,然后客戶端再用 JS、CSS 切分后使用,好處是節省了請求次數,但缺點是處理比較麻煩。
剛才說的幾種數據壓縮都是針對的 HTTP 報文里的 body,在 HTTP/1 里沒有辦法可以壓縮 header,但我們也可以采取一些手段來減少 header 的大小,不必要的字段就盡量不發(例如 Server、X-Powered-By)。
網站經常會使用 Cookie 來記錄用戶的數據,瀏覽器訪問網站時每次都會帶上 Cookie,冗余度很高。所以應當少使用 Cookie,減少 Cookie 記錄的數據量,總使用 domain 和 path 屬性限定 Cookie 的作用域,盡可能減少 Cookie 的傳輸。如果客戶端是現代瀏覽器,還可以使用 HTML5 里定義的 Web Local Storage,避免使用 Cookie。
壓縮之外,“節流”還有兩個優化點,就是域名和重定向。
DNS 解析域名會耗費不少的時間,如果網站擁有多個域名,那么域名解析獲取 IP 地址就是一個不小的成本,所以應當適當“收縮”域名,限制在兩三個左右,減少解析完整域名所需的時間,讓客戶端盡快從系統緩存里獲取解析結果。
重定向引發的客戶端延遲也很高,它不僅增加了一次請求往返,還有可能導致新域名的 DNS 解析,是 HTTP 前端性能優化的“大忌”。除非必要,應當盡量不使用重定向,或者使用 Web 服務器的“內部重定向”。
緩存
“緩存”不僅是 HTTP,也是任何計算機系統性能優化的“法寶”,把它和上面的“開源”“節流”搭配起來應用於傳輸鏈路,就能夠讓 HTTP 的性能再上一個台階。
在“第零公里”,也就是網站系統內部,可以使用 Memcache、Redis、Varnish 等專門的緩存服務,把計算的中間結果和資源存儲在內存或者硬盤里,Web 服務器首先檢查緩存系統,如果有數據就立即返回給客戶端,省去了訪問后台服務的時間。
在“中間一公里”,緩存更是性能優化的重要手段,CDN 的網絡加速功能就是建立在緩存的基礎之上的,可以這么說,如果沒有緩存,那就沒有 CDN。
利用好緩存功能的關鍵是理解它的工作原理,為每個資源都添加 ETag 和 Last-modified 字段,再用 Cache-Control、Expires 設置好緩存控制屬性。
其中最基本的是 max-age 有效期,標記資源可緩存的時間。對於圖片、CSS 等靜態資源可以設置較長的時間,比如一天或者一個月,對於動態資源,除非是實時性非常高,也可以設置一個較短的時間,比如 1 秒或者 5 秒。
這樣一旦資源到達客戶端,就會被緩存起來,在有效期內都不會再向服務器發送請求,也就是:“沒有請求的請求,才是最快的請求。”
HTTP/2
在“開源”“節流”和“緩存”這三大策略之外,HTTP 性能優化還有一個選擇,那就是把協議由 HTTP/1 升級到 HTTP/2。
通過“飛翔篇”的學習,你已經知道了 HTTP/2 的很多優點,它消除了應用層的隊頭阻塞,擁有頭部壓縮、二進制幀、多路復用、流量控制、服務器推送等許多新特性,大幅度提升了 HTTP 的傳輸效率。
實際上這些特性也是在“開源”和“節流”這兩點上做文章,但因為這些都已經內置在了協議內,所以只要換上 HTTP/2,網站就能夠立刻獲得顯著的性能提升。
不過你要注意,一些在 HTTP/1 里的優化手段到了 HTTP/2 里會有“反效果”。
對於 HTTP/2 來說,一個域名使用一個 TCP 連接才能夠獲得最佳性能,如果開多個域名,就會浪費帶寬和服務器資源,也會降低 HTTP/2 的效率,所以“域名收縮”在 HTTP/2 里是必須要做的。
“資源合並”在 HTTP/1 里減少了多次請求的成本,但在 HTTP/2 里因為有頭部壓縮和多路復用,傳輸小文件的成本很低,所以合並就失去了意義。而且“資源合並”還有一個缺點,就是降低了緩存的可用性,只要一個小文件更新,整個緩存就完全失效,必須重新下載。
所以在現在的大帶寬和 CDN 應用場景下,應當盡量少用資源合並(JS、CSS 圖片合並,數據內嵌),讓資源的粒度盡可能地小,才能更好地發揮緩存的作用。
小結
-
性能優化是一個復雜的概念,在 HTTP 里可以分解為服務器性能優化、客戶端性能優化和傳輸鏈路優化;
-
服務器有三個主要的性能指標:吞吐量、並發數和響應時間,此外還需要考慮資源利用率;
-
客戶端的基本性能指標是延遲,影響因素有地理距離、帶寬、DNS 查詢、TCP 握手等;
-
從服務器到客戶端的傳輸鏈路可以分為三個部分,我們能夠優化的是前兩個部分,也就是“第一公里”和“中間一公里”;
-
有很多工具可以測量這些指標,服務器端有 ab、top、sar 等,客戶端可以使用測試網站,瀏覽器的開發者工具。
-
花錢購買硬件、軟件或者服務可以直接提升網站的服務能力,其中最有價值的是 CDN;
-
不花錢也可以優化 HTTP,三個關鍵詞是“開源”“節流”和“緩存”;
-
后端應該選用高性能的 Web 服務器,開啟長連接,提升 TCP 的傳輸效率;
-
前端應該啟用 gzip、br 壓縮,減小文本、圖片的體積,盡量少傳不必要的頭字段;
-
緩存是無論何時都不能忘記的性能優化利器,應該總使用 Etag 或 Last-modified 字段標記資源;
-
升級到 HTTP/2 能夠直接獲得許多方面的性能提升,但要留意一些 HTTP/1 的“反模式”。
系列文章
-
HTTP 概述
-
TCP 三次握手和四次揮手圖解(有限狀態機)
-
從你輸入網址,到看到網頁——詳解中間發生的過程
-
深入淺出 HTTPS (詳解版)
-
漫談 HTTP 連接
-
漫談 HTTP 性能優化
-
HTTP 報文格式簡介
-
深入淺出:HTTP/2