1. 程式人生 > >本部落格 Nginx 配置之效能篇

本部落格 Nginx 配置之效能篇

提醒:本文最後更新於 1323 天前,文中所描述的資訊可能已發生改變,請謹慎使用。

在介紹完我部落格(imququ.com)的 Nginx 配置中與安全有關的一些配置後,這篇文章繼續介紹與效能有關的一些配置。WEB 效能優化是一個系統工程,涵蓋很多方面,做好其中某個環節並不意味效能就能變好,但可以肯定地說,如果某個環節做得很糟糕,那麼結果一定會變差。

首先說明下,本文提到的一些 Nginx 配置,需要較高版本 Linux 核心才支援。在實際生產環境中,升級伺服器核心並不是一件容易的事,但為了獲得最好的效能,有些升級還是必須的。很多公司伺服器運維和專案開發並不在一個團隊,一方追求穩定不出事故,另一方希望提升效能,本來就是矛盾的。好在我們折騰自己 VPS 時,可以無視這些限制。

TCP 優化

Nginx 關於 TCP 的優化基本都是修改系統核心提供的配置項,所以跟具體的 Linux 版本和系統配置有關,我對這一塊還不是非常熟悉,這裡只能簡單介紹下:

http {
    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        on;

    keepalive_timeout  60;
    ... ...
}

第一行的 sendfile 配置可以提高 Nginx 靜態資源託管效率。sendfile 是一個系統呼叫,直接在核心空間完成檔案傳送,不需要先 read 再 write,沒有上下文切換開銷。

TCP_NOPUSH 是 FreeBSD 的一個 socket 選項,對應 Linux 的 TCP_CORK,Nginx 裡統一用 tcp_nopush 來控制它,並且只有在啟用了 sendfile 之後才生效。啟用它之後,資料包會累計到一定大小之後才會傳送,減小了額外開銷,提高網路效率。

TCP_NODELAY 也是一個 socket 選項,啟用後會禁用 Nagle 演算法,儘快傳送資料,某些情況下可以節約 200ms(Nagle 演算法原理是:在發出去的資料還未被確認之前,新生成的小資料先存起來,湊滿一個 MSS 或者等到收到確認後再發送)。Nginx 只會針對處於 keep-alive 狀態的 TCP 連線才會啟用 tcp_nodelay

可以看到 TCP_NOPUSH 是要等資料包累積到一定大小才傳送,TCP_NODELAY 是要儘快傳送,二者相互矛盾。實際上,它們確實可以一起用,最終的效果是先填滿包,再儘快傳送。

配置最後一行用來指定服務端為每個 TCP 連線最多可以保持多長時間。Nginx 的預設值是 75 秒,有些瀏覽器最多隻保持 60 秒,所以我統一設定為 60。

另外,還有一個 TCP 優化策略叫 TCP Fast Open(TFO),這裡先介紹下,配置在後面貼出。TFO 的作用是用來優化 TCP 握手過程。客戶端第一次建立連線還是要走三次握手,所不同的是客戶端在第一個 SYN 會設定一個 Fast Open 標識,服務端會生成 Fast Open Cookie 並放在 SYN-ACK 裡,然後客戶端就可以把這個 Cookie 存起來供之後的 SYN 用。下面這個圖形象地描述了這個過程:

tcp fast open

關於 TCP Fast Open 的更多資訊,可以檢視 RFC7413,或者這篇文章:Shaving your RTT with TCP Fast Open。需要注意的是,現階段只有 Linux、ChromeOS 和 Android 5.0 的 Chrome / Chromium 才支援 TFO,所以實際用途並不大。

5 月 26 日釋出的 Nginx 1.9.1,增加了 reuseport 功能,意味著 Nginx 也開始支援 TCP 的 SO_REUSEPORT 選項了。這裡也先簡單介紹下,具體配置方法後面統一介紹。啟用這個功能後,Nginx 會在指定的埠上監聽多個 socket,每個 Worker 都能分到一個。請求過來時,系統核心會自動通過不同的 socket 分配給對應的 Worker,相比之前的單 socket 多 Worker 的模式,提高了分發效率。下面這個圖形象地描述了這個過程:

tcp reuseport

開啟 Gzip

我們在上線前,程式碼(JS、CSS 和 HTML)會做壓縮,圖片也會做壓縮(PNGOUT、Pngcrush、JpegOptim、Gifsicle 等)。對於文字檔案,在服務端傳送響應之前進行 GZip 壓縮也很重要,通常壓縮後的文字大小會減小到原來的 1/4 - 1/3。下面是我的配置:

http {
    gzip               on;
    gzip_vary          on;

    gzip_comp_level    6;
    gzip_buffers       16 8k;

    gzip_min_length    1000;
    gzip_proxied       any;
    gzip_disable       "msie6";

    gzip_http_version  1.0;

    gzip_types         text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
    ... ...
}

這部分內容比較簡單,只有兩個地方需要解釋下:

gzip_vary 用來輸出 Vary 響應頭,用來解決某些快取服務的一個問題,詳情請看我之前的部落格:HTTP 協議中 Vary 的一些研究

gzip_disable 指令接受一個正則表示式,當請求頭中的 UserAgent 欄位滿足這個正則時,響應不會啟用 GZip,這是為了解決在某些瀏覽器啟用 GZip 帶來的問題。特別地,指令值 msie6 等價於 MSIE [4-6]\.,但效能更好一些。另外,Nginx 0.8.11 後,msie6 並不會匹配 UA 包含 SV1 的 IE6(例如 Windows XP SP2 上的 IE6),因為這個版本的 IE6 已經修復了關於 GZip 的若干 Bug。

預設 Nginx 只會針對 HTTP/1.1 及以上的請求才會啟用 GZip,因為部分早期的 HTTP/1.0 客戶端在處理 GZip 時有 Bug。現在基本上可以忽略這種情況,於是可以指定 gzip_http_version 1.0 來針對 HTTP/1.0 及以上的請求開啟 GZip。

開啟快取

優化程式碼邏輯的極限是移除所有邏輯;優化請求的極限是不傳送任何請求。這兩點通過快取都可以實現。

服務端

我的部落格更新並不頻繁,評論部分也早就換成了 Disqus,所以完全可以將頁面靜態化,這樣就省掉了所有程式碼邏輯和資料庫開銷。實現靜態化有很多種方案,我直接用的是 Nginx 的 proxy_cache(注:本部落格為了做更精細的靜態化,已經將快取邏輯挪到 Web 應用裡實現了):

proxy_cache_path  /home/jerry/cache/nginx/proxy_cache_path levels=1:2 keys_zone=pnc:300m inactive=7d max_size=10g;
proxy_temp_path   /home/jerry/cache/nginx/proxy_temp_path;
proxy_cache_key   $host$uri$is_args$args;

server {
    location / {
        resolver                  127.0.0.1;  
        proxy_cache               pnc;
        proxy_cache_valid         200 304 2h;
        proxy_cache_lock          on;
        proxy_cache_lock_timeout  5s;
        proxy_cache_use_stale     updating error timeout invalid_header http_500 http_502;

        proxy_http_version        1.1;

        proxy_ignore_headers      Set-Cookie;
        ... ...
    }
    ... ...
}

首先,在配置最外層定義一個快取目錄,並指定名稱(keys_zone)和其他屬性,這樣在配置 proxy_pass 時,就可以使用這個快取了。這裡我對狀態值等於 200 和 304 的響應快取了 2 小時。

預設情況下,如果響應頭裡有 Set-Cookie 欄位,Nginx 並不會快取這次響應,因為它認為這次響應的內容是因人而異的。我的部落格中,這個 Set-Cookie 對於使用者來說沒有用,也不會影響輸出內容,所以我通過配置 proxy_ignore_header 移除了它。

客戶端

服務端在輸出響應時,可以通過響應頭輸出一些與快取有關的資訊,從而達到少發或不發請求的目的。HTTP/1.1 的快取機制稍微有點複雜,這裡簡單介紹下:

首先,服務端可以通過響應頭裡的 Last-Modified(最後修改時間) 或者 ETag(內容特徵) 標記實體。瀏覽器會存下這些標記,並在下次請求時帶上 If-Modified-Since: 上次 Last-Modified 的內容If-None-Match: 上次 ETag 的內容,詢問服務端資源是否過期。如果服務端發現並沒有過期,直接返回一個狀態碼為 304、正文為空的響應,告知瀏覽器使用本地快取;如果資源有更新,服務端返回狀態碼 200、新的 Last-Modified、Etag 和正文。這個過程被稱之為 HTTP 的協商快取,通常也叫做弱快取。

可以看到協商快取並不會節省連線數,但是在快取生效時,會大幅減小傳輸內容(304 響應沒有正文,一般只有幾百位元組)。另外為什麼有兩個響應頭都可以用來實現協商快取呢?這是因為一開始用的 Last-Modified 有兩個問題:1)只能精確到秒,1 秒內的多次變化反映不出來;2)時間採用絕對值,如果服務端 / 客戶端時間不對都可能導致快取失效在輪詢的負載均衡演算法中,如果各機器讀到的檔案修改時間不一致,有快取無故失效和快取不更新的風險。HTTP/1.1 並沒有規定 ETag 的生成規則,而一般實現者都是對資源內容做摘要,能解決前面兩個問題。

另外一種快取機制是服務端通過響應頭告訴瀏覽器,在什麼時間之前(Expires)或在多長時間之內(Cache-Control: Max-age=xxx),不要再請求伺服器了。這個機制我們通常稱之為 HTTP 的強快取。

一旦資源命中強快取規則後,再次訪問完全沒有 HTTP 請求(Chrome 開發者工具的 Network 面板依然會顯示請求,但是會註明 from cache;Firefox 的 firebug 也類似,會註明 BFCache),這會大幅提升效能。所以我們一般會對 CSS、JS、圖片等資源使用強快取,而入口檔案(HTML)一般使用協商快取或不快取,這樣可以通過修改入口檔案中對強快取資源的引入 URL 來達到即時更新的目的。

這裡也解釋下為什麼有了 Expires,還要有 Cache-Control。也有兩個原因:1)Cache-Control 功能更強大,對快取的控制能力更強;2)Cache-Control 採用的 max-age 是相對時間,不受服務端 / 客戶端時間不對的影響。

另外關於瀏覽器的重新整理(F5 / cmd + r)和強刷(Ctrl + F5 / shift + cmd +r):普通重新整理會使用協商快取,忽略強快取;強刷會忽略瀏覽器所有快取(並且請求頭會攜帶 Cache-Control:no-cache 和 Pragma:no-cache,用來通知所有中間節點忽略快取)。只有從位址列或收藏夾輸入網址、點選連結等情況下,瀏覽器才會使用強快取。

預設情況下,Nginx 對於靜態資源都會輸出 Last-Modified,而 ETagExpiresCache-Control 則需要自己配置:

location ~ ^/static/ {
    root      /home/jerry/www/blog/www;
    etag      on;
    expires   max;
}

expires 指令可以指定具體的 max-age,例如 10y 代表 10 年,如果指定為 max,最終輸出的 Expires 會是 2037 年最後一天,Cache-Controlmax-age 會是 10 年(準確說是 3650 天,315360000 秒)。

使用 SPDY(HTTP/2)

我的部落格之前多次講到過 HTTP/2(SPDY),現階段 Nginx 只支援 SPDY/3.1,這樣配置就可以啟用了(編譯 Nginx 時需要加上 --with-http_spdy_module 和 --with-http_ssl_module):

server {
    listen             443 ssl spdy fastopen=3 reuseport;
    spdy_headers_comp  6;
    ... ...
}

那個 fastopen=3 用來開啟前面介紹過的 TCP Fast Open 功能。3 代表最多隻能有 3 個未經三次握手的 TCP 連結在排隊。超過這個限制,服務端會退化到採用普通的 TCP 握手流程。這是為了減少資源耗盡攻擊:TFO 可以在第一次 SYN 的時候傳送 HTTP 請求,而服務端會校驗 Fast Open Cookie(FOC),如果通過就開始處理請求。如果不加限制,惡意客戶端可以利用合法的 FOC 傳送大量請求耗光服務端資源。

reuseport 就是用來啟用前面介紹過的 TCP SO_REUSEPORT 選項的配置。

HTTPS 優化

建立 HTTPS 連線本身就慢(多了獲取證書、校驗證書、TLS 握手等等步驟),如果沒有優化好只能是慢上加慢。

server {
    ssl_session_cache        shared:SSL:10m;
    ssl_session_timeout      60m;

    ssl_session_tickets      on;

    ssl_stapling             on;
    ssl_stapling_verify      on;
    ssl_trusted_certificate  /xxx/full_chain.crt;

    resolver                 8.8.4.4 8.8.8.8  valid=300s;
    resolver_timeout         10s;
    ... ...
}

我的這部分配置就兩部分內容:TLS 會話恢復和 OCSP stapling。

TLS 會話恢復的目的是為了簡化 TLS 握手,有兩種方案:Session Cache 和 Session Ticket。他們都是將之前握手的 Session 存起來供後續連線使用,所不同是 Cache 存在服務端,佔用服務端資源;Ticket 存在客戶端,不佔用服務端資源。另外目前主流瀏覽器都支援 Session Cache,而 Session Ticket 的支援度一般。

ssl_stapling 開始的幾行用來配置 OCSP stapling 策略。瀏覽器可能會在建立 TLS 連線時線上驗證證書有效性,從而阻塞 TLS 握手,拖慢整體速度。OCSP stapling 是一種優化措施,服務端通過它可以在證書鏈中封裝證書頒發機構的 OCSP(Online Certificate Status Protocol)響應,從而讓瀏覽器跳過線上查詢。服務端獲取 OCSP 一方面更快(因為服務端一般有更好的網路環境),另一方面可以更好地快取。有關 OCSP stapling 的詳細介紹,可以看這裡

這些策略設定好之後,可以通過 Qualys SSL Server Test 這個工具來驗證是否生效,例如下圖就是本部落格的測試結果(via):

Qualys server ssl test

在給 Nginx 指定證書時,需要選擇合適的證書鏈。因為瀏覽器在驗證證書信任鏈時,會從站點證書開始,遞迴驗證父證書,直至信任的根證書。這裡涉及到兩個問題:1)伺服器證書是在握手期間傳送的,由於 TCP 初始擁塞視窗的存在,如果證書太長很可能會產生額外的往返開銷;2)如果服務端證書沒包含中間證書,大部分瀏覽器可以正常工作,但會暫停驗證並根據子證書指定的父證書 URL 自己獲取中間證書。這個過程會產生額外的 DNS 解析、建立 TCP 連線等開銷。配置服務端證書鏈的最佳實踐是包含站點證書中間證書兩部分。有的證書提供商簽出來的證書級別比較多,這會導致證書鏈變長,選擇的時候需要特別注意。

好了,我的部落格關於安全和效能兩部分 Nginx 配置終於都寫完了。實際上很多策略沒辦法嚴格區分是為了安全還是效能,比如 HSTS 和 CHACHA20_POLY1305,兩方面都有考慮,所以寫的時候比較糾結,早知道就合成一篇來寫了。

--EOF--

提醒:本文最後更新於 1323 天前,文中所描述的資訊可能已發生改變,請謹慎使用。

相關推薦

部落 Nginx 配置效能

提醒:本文最後更新於 1323 天前,文中所描述的資訊可能已發生改變,請謹慎使用。 在介紹完我部落格(imququ.com)的 Nginx 配置中與安全有關的一些配置後,這篇文章繼續介紹與效能有關的一些配置。WEB 效能優化是一個系統工程,涵蓋很多方面,做好其中某個環節並不意味效能就能變好,但可

部落 Nginx 配置安全篇

提醒:本文最後更新於 1326 天前,文中所描述的資訊可能已發生改變,請謹慎使用。 之前有細心的朋友問我,為什麼你的部落格副標題是「專注 WEB 端開發」,是不是少了「前端」的「前」。我想說的是,儘管我從畢業到現在七年左右的時間一直都在專業前端團隊從事前端相關工作,但這並不意味著我的知識體系就必

部署Django部落全記錄Gunicorn(二)

什麼是Gunicorn Gunicorn是一個被廣泛使用的高效能的Python WSGI UNIX HTTP伺服器,移植自Ruby的獨角獸(Unicorn )專案,使用pre-fork worker模式,具有使用非常簡單,輕量級的資源消耗,以及高效能等特點。 為什麼使用

部署Django部落全記錄Nginx(一)

Nginx的優點 注:具體原理沒搞清,先記著 Nginx更安全;Nginx能更好地處理靜態資源(通過一些http request header)。 Nginx也可以快取一些動態內容;Nginx可以更好地配合CDN。 Nginx可以進行多臺機器的負載均

Web項目Nginx配置文件

Nginx nginx.conf 2)配置篇2.1:nginx.conf為主配置文件2.1.1:nnginx.conf文件是以{}作區域分隔的純文本配置文件。2.1.2?Nginx配置文件目錄結構 使用tree命令查看目錄結構,若提示-bash:?tree: command not found?則使用

送人玫瑰,手留餘香,如果部落幫助到你了,幫忙點開本投上一票

送人玫瑰,手留餘香,如果本部落格幫助到您了,幫忙點開本篇,為我投上一票,謝謝,2019年,我們相約,繼續加油! 而選擇投票的您,也將有機會獲得免費開跑車的機會。 請選擇第4位候選人:004  Soyoger   投票即可。 投票地址:https://bs

Node.js安裝及環境配置Windows

npm安裝 data- 緩存 裏的 環境配置 cmd命令 服務 string ext 1、本機系統:Windows 10 Pro(64位)2、Node.js:v6.9.2LTS(64位) 二、安裝Node.js步驟 1、下載對應你系統的Node.js版本:https:/

Nginx配置location模塊和proxy模塊

event end 虛擬主機 include 當我 服務 文件的 域名 時間 1、location指令的用法介紹 Location主要用來匹配url,如:http://www.beyond.com/nice,在這裏對於location來說www.beyond.com是域名

nginx配置location模式匹配

jpeg ack fetch 定義 正則表達式 con 搜索 其他 error 1、location正則 ~ 表示一個正則匹配,區分大小寫 ~* 表示一個正則匹配,不區分大小寫 ^~ 表示普通字符匹配,一般用來匹配目錄 = 表示

Node.js 安裝及環境配置 Windows

per 完成後 reg 瀏覽器中 expressjs ext mod 企業 developer 一、安裝環境 1、本機系統:Windows 10 企業版(64位)2、Node.js:node-v8.9.4-x64.msi(64位) 二、安裝Node.js步驟 1、

筆記搬到部落 - nginx一步步整合lua模組

經過網路搜尋、整理、實踐筆記   前置條件:已安裝nginx   一 、安裝LuaJit 1.下載LuaJit  http://luajit.org/download.html [[email protected]_6 source]$

筆記搬到部落 - Nginx代理websocket超時連線斷開問題

經過網路搜尋、整理、實踐筆記   配置nginx使之支援websockt的反響代理後,發現websockt連線上就立即斷開,狀態碼為:1006 最後查到原因是proxy_read_timeout 太小,這便是websockt的有效時間 現在配置成3600,也就是維持一個小時

部落開發總結頁面編寫

前面是一堆廢話,正文下拉!!! 學習Java斷斷續續也快兩年了(實際應該是一年多),從大學第一次接觸C語言,就發現自己其實是喜歡程式設計的。在高中的時候,聽說了C語言後就想要學習,覺得寫程式碼是一件很酷的事情,於是在網上找了一份教程,試著在命令列介面printf("hello,world");

部落開發總結後臺程式碼

寫這個部落格專案也是我第一次使用Spring Boot框架,用過之後,發現真的回不去了。省去了一大堆配置檔案,簡直不能再爽。之前用Spring MVC,Spring,Hibernate,MyBatis等框架,繁瑣的配置檔案,每一個專案都是那一套,感覺不是在學框架內容,而是在學寫配置檔案。Spring

部落程式碼與mallocfree相關申明

首先 先看我的舊部落格 http://www.dongj.cc/blog/ 部落格中 正常人都能知道我是mallocfree出來的學員 本部落格程式碼不大部分程式碼是基於微軟的驅動演示包進行修改的 malllocfree宣稱程式碼有“著作權” 我想說: 在mal

C與C++實現高維陣列的動態開闢(部落學習於51CTO 鮑松山 C++高階教程)

目錄 1.VS安裝VLD記憶體洩露檢測工具 2.C語言下實現二維陣列的動態開闢 3.C++下實現二維陣列的動態開闢 4.C++下實現高維陣列的動態開闢 1.VS安裝記憶體洩漏檢測工具VLD 參考這篇部落格即可:https://blog.csdn.net/GZrhaunt/

這是我既C語言作業寫部落後寫的第一部落

這篇部落格應該算是寫給我自己的部落格吧,所以這裡我想用繁體字寫,因為我漸漸地發現我已經很少使用到繁體字了,日常QQ聊天都使用簡體字,繁體字都懶得切換了,但是為了不讓別人麻煩,在外界交流的時候我會使用簡體字的。心中可能有些惋惜吧,因為考慮到各種因素而越來越少地使用繁體字了,曾經是那麼想認識繁體字,現在的自己確是

部落園上的第一部落

我是誰 本人,來自江蘇鹽城,現目前數學系大三學生一枚,目前主要的任務是為考研做準備。外加有空時學習機器學習的知識,學習實現一些簡單演算法。 why 寫部落格 主要是為了學習一些技術,一些演算法,通過自己的理解表達出來,加以分享,從而來加深自己的理解。 其次通過寫技術部落格,來形成自己的知識框架

部落園中配置MathJax

對於經常在部落格中撰寫學術與技術類文章的網友來說,能夠直接插入LaTeX數學公式是一項非常實用與方便的功能。幸好部落格園已經提供了對MathJax數學公式的支援,只要在部落格後臺管理介面中的“選項”一欄中勾選“啟用數學公式支援”即可。接下來,為了使用自定義的LaTeX命令與數學符號,一般來說,就需要使用Jav

部落去廣告規則設定

本規則適合Firefox 與 Chrome上面的 AdBlocker Ultimate 1.安裝 外掛安裝過程略 2.FireFox外掛新增自定義規則 依次填入以下規則,然後新增即可: blog.csdn.net###nav-left-menu > LI:fir