高性能 Nginx HTTPS 調優!爲 HTTPS 提速 30%

爲什麼要優化 Ngin HTTPS 延遲

Nginx 常作爲最常見的服務器,常被用作負載均衡 (Load Balancer)、反向代理 (Reverse Proxy),以及網關 (Gateway) 等等。一個配置得當的 Nginx 服務器單機應該可以期望承受住 50K 到 80K 左右每秒的請求,同時將 CPU 負載在可控範圍內。

但在很多時候,負載並不是需要首要優化的重點。比如對於卡拉搜索來說,我們希望用戶在每次擊鍵的時候,可以體驗即時搜索的感覺,也就是說,每個搜索請求必須在 100ms - 200ms 的時間內端對端地返回給用戶,才能讓用戶搜索時沒有“卡頓”和“加載”。因此,對於我們來說,優化請求延遲纔是最重要的優化方向。

這篇文章中,我們先介紹 Nginx 中的 TLS 設置有哪些與請求延遲可能相關,如何調整才能最大化加速。然後我們用優化卡拉搜索Nginx 服務器的實例來分享如何調整 Nginx TLS/SSL 設置,爲首次搜索的用戶提速 30% 左右。我們會詳細討論每一步我們做了一些什麼優化,優化的動機和效果。希望可以對其它遇到類似問題的同學提供幫助。

TLS 握手和延遲

很多時候開發者會認爲:如果不是絕對在意性能,那麼瞭解底層和更細節的優化沒有必要。這句話在很多時候是恰當的,因爲很多時候複雜的底層邏輯必須包起來,才能讓更高層的應用開發複雜度可控。比如說,如果你就只需要開發一個 APP 或者網站,可能並沒有必要關注彙編細節,關注編譯器如何優化你的代碼——畢竟在蘋果或者安卓上很多優化在底層就做好了。

那麼,瞭解底層的 TLS 和應用層的 Nginx 延遲優化有什麼關係呢?

答案是多數情況下,優化網絡延遲其實是在嘗試減少用戶和服務器之間的數據傳輸次數,也就是所謂的 roundtrip。由於物理限制,北京到雲南的光速傳播差不多就是要跑 20 來毫秒,如果你不小心讓數據必須多次往返於北京和雲南之間,那麼必然延遲就上去了。

因此如果你需要優化請求延遲,那麼瞭解一點底層網絡的上下文則會大有裨益,很多時候甚至是你是否可以輕鬆理解一個優化的關鍵。本文中我們不深入討論太多 TCP 或者 TLS 機制的細節,如果有興趣的話請參考 High Performance Browser Networking[4] 一書,可以免費閱讀。

舉個例子,下圖中展示瞭如果你的服務啓用了 HTTPS,在開始傳輸任何數據之前的數據傳輸情況。

微信圖片_20210125105331.png


可以看到,在你的用戶拿到他需要的數據前,底層的數據包就已經在用戶和你的服務器之間跑了 3 個來回。

假設每次來回需要 28 毫秒的話,用戶已經等了 224 毫秒之後纔開始接收數據。

同時這個 28 毫秒其實是非常樂觀的假設,在國內電信、聯通和移動以及各種複雜的網絡狀況下,用戶與服務器之間的延遲更不可控。另一方面,通常一個網頁需要數十個請求,這些請求不一定可以全部並行,因此幾十乘以 224 毫秒,頁面打開可能就是數秒之後了。

所以,原則上如果可能的話,我們需要儘量減少用戶和服務器之間的往返程 (roundtrip),在下文的設置中,對於每個設置我們會討論爲什麼這個設置有可能幫助減少往返程。

Nginx 中的 TLS 設置

那麼在 Nginx 設置中,怎樣調整參數會減少延遲呢?

開啓 HTTP/2

HTTP/2 標準是從 Google 的 SPDY 上進行的改進,比起 HTTP 1.1 提升了不少性能,尤其是需要並行多個請求的時候可以顯着減少延遲。在現在的網絡上,一個網頁平均需要請求幾十次,而在 HTTP 1.1 時代瀏覽器能做的就是多開幾個連接(通常是 6 個)進行並行請求,而 HTTP 2 中可以在一個連接中進行並行請求。HTTP 2 原生支持多個並行請求,因此大大減少了順序執行的請求的往返程,可以首要考慮開啓。

如果你想自己看一下 HTTP 1.1 和 HTTP 2.0 的速度差異,可以試一下:https://www.httpvshttps.com/。我的網絡測試下來 HTTP/2 比 HTTP 1.1 快了 66%。

image.png

在 Nginx 中開啓 HTTP 2.0 非常簡單,只需要增加一個 http2 標誌即可

listen 443 ssl;
# 改爲
listen 443 ssl http2;

如果你擔心你的用戶用的是舊的客戶端,比如 Python 的 requests,暫時還不支持 HTTP 2 的話,那麼其實不用擔心。如果用戶的客戶端不支持 HTTP 2,那麼連接會自動降級爲 HTTP 1.1,保持了後向兼容。因此,所有使用舊 Client 的用戶,仍然不受影響,而新的客戶端則可以享受 HTTP/2 的新特性。

如何確認你的網站或者 API 開啓了 HTTP 2

在 Chrome 中打開開發者工具,點開 Protocol 之後在所有的請求中都可以看到請求用的協議了。如果 protocol 這列的值是 h2 的話,那麼用的就是 HTTP 2 了image.png當然另一個辦法是直接用 curl 如果返回的 status 前有 HTTP/2 的話自然也就是 HTTP/2 開啓了。

➜  ~ curl --http2 -I https://kalasearch.cn
HTTP/2 403
server: Tengine
content-type: application/xml
content-length: 264
date: Tue, 22 Dec 2020 18:38:46 GMT
x-oss-request-id: 5FE23D363ADDB93430197043
x-oss-cdn-auth: success
x-oss-server-time: 0
x-alicdn-da-ups-status: endOs,0,403
via: cache13.l2et2[148,0], cache10.l2ot7[291,0], cache4.us13[360,0]
timing-allow-origin: *
eagleid: 2ff6169816086623266688093e

調整 Cipher 優先級

儘量挑選更新更快的 Cipher,有助於減少延遲:

# 手動啓用 cipher 列表
ssl_prefer_server_ciphers on;  # prefer a list of ciphers to prevent old and slow ciphers
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

啓用 OCSP Stapling

在國內這可能是對使用 Let's Encrypt 證書的服務或網站影響最大的延遲優化了。如果不啓用 OCSP Stapling 的話,在用戶連接你的服務器的時候,有時候需要去驗證證書。而因爲一些不可知的原因(這個就不說穿了)Let's Encrypt 的驗證服務器並不是非常通暢,因此可以造成有時候數秒甚至十幾秒延遲的問題,這個問題在 iOS 設備上特別嚴重

解決這個問題的方法有兩個

  • 不使用 Let's Encrypt,可以嘗試替換爲阿里雲提供的免費 DV 證書
  • 開啓 OCSP Stapling

開啓了 OCSP Stapling 的話,跑到證書驗證這一步可以省略掉。省掉一個 roundtrip,特別是網絡狀況不可控的 roundtrip,可能可以將你的延遲大大減少。

在 Nginx 中啓用 OCSP Stapling 也非常簡單,只需要設置:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/full_chain.pem;

如何檢測 OCSP Stapling 是否已經開啓?

可以通過以下命令

openssl s_client -connect test.kalasearch.cn:443 -servername kalasearch.cn -status -tlsextdebug < /dev/null 2>&1 | grep -i "OCSP response"



來測試。如果結果爲

OCSP response:
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response


則表明已經開啓。

調整 ssl_buffer_size

ssl_buffer_size 控制在發送數據時的 buffer 大小,默認設置是 16k。這個值越小,則延遲越小,而添加的報頭之類會使 overhead 會變大,反之則延遲越大,overhead 越小。

因此如果你的服務是 REST API或者網站的話,將這個值調小可以減小延遲和 TTFB,但如果你的服務器是用來傳輸大文件的,那麼可以維持 16k。

如果是網站或者 REST API,建議值爲 4k,但是這個值的最佳取值顯然會因爲數據的不同而不一樣,因此請嘗試 2 - 16k 間不同的值。在 Nginx 中調整這個值也非常容易

ssl_buffer_size 4k;

啓用 SSL Session 緩存

啓用 SSL Session 緩存可以大大減少 TLS 的反覆驗證,減少 TLS 握手的 roundtrip。雖然 session 緩存會佔用一定內存,但是用 1M 的內存就可以緩存 4000 個連接,可以說是非常非常划算的。同時,對於絕大多數網站和服務,要達到 4000 個同時連接本身就需要非常非常大的用戶基數,因此可以放心開啓。

#這裏 ssl_session_cache 設置爲使用 50M 內存,以及 4 小時的連接超時關閉時間 ssl_session_timeout
# Enable SSL cache to speed up for return visitors
ssl_session_cache   shared:SSL:50m; # speed up first time. 1m ~= 4000 connections
ssl_session_timeout 4h;

卡拉搜索如何減少 30% 的請求延遲

卡拉搜索是國內的 Algolia,致力於幫助開發者快速搭建即時搜索功能(instant search),做國內最快最易用的搜索即服務。

開發者接入後,所有搜索請求通過卡拉 API 即可直接返回給終端用戶。爲了讓用戶有即時搜索的體驗,我們需要在用戶每次擊鍵後極短的時間內(通常是 100ms 到 200ms)將結果返回給用戶。因此每次搜索需要可以達到 50 毫秒以內的引擎處理時間和 200 毫秒以內的端對端時間。

我們用豆瓣電影的數據做了一個電影搜索的 Demo,如果感興趣的話歡迎體驗一下即時搜索,嘗試一下搜索“無間道”或者“大話西遊”體驗一下速度和相關度:https://movies-demo.kalasearch.cn/

對於每個請求只有 100 到 200 毫秒的延遲預算,我們必須把每一步的延遲都考慮在內。

簡化一下,每個搜索請求需要經歷的延遲有

image.png

總延遲 = 用戶請求到達服務器(T1) + 反代處理(Nginx T2) + 數據中心延遲(T3) + 服務器處理 (卡拉引擎 T4) + 用戶請求返回(T3+T1)

在上述延遲中,T1 只與用戶與服務器的物理距離相關,而 T3 非常小可以忽略不計。

所以我們能控制的大致只有 T2 和 T4,即 Nginx 服務器的處理時間和卡拉的引擎處理時間。

Nginx 在這裏作爲反向代理,處理一些安全、流量控制和 TLS 的邏輯,而卡拉的引擎則是一個在 Lucene 基礎上的倒排引擎。

我們首先考慮的第一個可能性是:延遲是不是來自卡拉引擎呢?

在下圖展示的 Grafana 儀表盤中,我們看到除了幾個時不時的慢查詢,搜索的 95% 服務器處理延遲小於 20 毫秒。對比同樣的數據集上 benchmark 的 Elastic Search 引擎的 P95 搜索延遲則在 200 毫秒左右,所以排除了引擎速度慢的可能。image.png而在阿里雲監控中,我們設置了從全國各地向卡拉服務器發送搜索請求。我們終於發現 SSL 處理時間時常會超過 300 毫秒,也就是說在 T2 這一步,光處理 TLS 握手之類的事情,Nginx 已經用掉了我們所有的請求時間預算。

同時檢查之後我們發現,在蘋果設備上搜索速度格外慢,特別是第一次訪問的設備。因此我們大致判斷應該是因爲我們使用的 Let's Encrypt 證書的問題。

我們按照上文中的步驟對 Nginx 設置進行了調整,並將步驟總結出來寫了這篇文章。在調整了 Nginx TLS 的設置後,SSL 時間從平均的 140ms 降低到了 110ms 左右(全國所有省份聯通和移動測試點),同時蘋果設備上首次訪問慢的問題也消失了。


image.png在調整過後,全國範圍內測試的搜索延遲降低到了 150 毫秒左右。

總結

調整 Nginx 中的 TLS 設置對於使用 HTTPS 的服務和網站延遲有非常大的影響。本文中總結了 Nginx 中與 TLS 相關的設置,詳細討論各個設置可能對延遲的影響,並給出了調整建議。


https://mp.weixin.qq.com/s/aGgtmzVRUwfpebO3PHa6hQ 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章