時延
時延的定義和標準
時延簡單的說是從原點到目標點傳送一條信息或者一個數據包,所花費的時間。 時延=發送時延+傳播時延+處理時延+排隊時延:
Propagation delay 傳播時延
傳播時延這個概念,是指電磁信號或者光信號在傳輸介質中傳輸的時延,而在光纖或者銅線中,光信號和電磁信號的傳播速度都在20萬公里/秒以上,在傳輸介質中傳輸的是電磁信號或者光信號,而不是數據!
Transmission delay 傳送時延
發送時延是指結點在發送數據時使數據塊從結點進入到傳輸媒體所需的時間,也就是從數據塊的第一個比特開始發送算起,到最後一個比特發送完畢所需的時間。發送時延又稱爲傳輸時延
Processing delay 處理時延
處理數據包的頭部,校對位錯誤, 並確定數據包要傳輸的方向
Queuing delay 排隊時延
數據包等待處理的時間
總的時延是從客戶端到服務器所有時延的總各,Propagation 時間是由兩者之間的距離和傳播的介質(光纖或銅線)決定。另一方面,Transmission delay 是由兩者建立起來傳輸鏈接的傳輸速率決定的,跟兩者之者的距離沒有關係。 舉例來說, 假設我們分別在1Mb的鏈接和100Mb的鏈接,傳遞一個10Mb的文件。 前者的時間爲10秒,而100Mbps時,只需花費0.1秒。
下一步, 當數據包到達一個路由器, 路由器必需檢測數據包的頭,從而決定下一個路由, 同時還需要對數據進行校驗, 所有的這些邏輯都是在路由器硬件上完成,所以Processing delay是非常小的, 但它也確時存在。 最後來講講Queuing delay. 當數據包從較快的傳速速率(光釺)到一個100MB的路由器,這就常過了路由器的處理速度, 那麼這個數據包就需要進入到緩存區提排隊。 這個排隊時間,就是Queuing Delay.
光速與Propagation delay
光速是299, 792, 458米每秒, 但光速在介質中傳播會有能量損耗, 這只是一個理論的值,通過以下圖表,可以清楚的瞭解光纖實際的速度和造成的Propagation 延時
路徑 | 距離 | 真空光速延時 | 光纖延時 | 光纖信號往返時間 |
---|---|---|---|---|
New York to San Francisco | 4,148 km | 14 ms | 21 ms | 42 ms |
New York to London | 5,585 km | 19 ms | 28 ms | 56 ms |
New York to Sydney(悉尼) | 15,993 km | 53 ms | 80 ms | 160 ms |
繞赤道一週 | 40,075 km | 133.7 ms | 200 ms | 200 ms |
光纖上的傳輸速度是挺快的, 但從紐約到悉尼還是花費了160毫秒. 這也僅僅是理論上的值(兩個城市是直接通過光纖相連), 實際上數據包會經過不同的路徑(悉尼到舊金山, 在到紐約等等)。經理的路徑越多,就會引入更多的處理延時,等待延時, 傳輸延時。最終RTT(Round-trip time)將達到 200 - 300ms.
通常100-200ms是正常的值, 如果超過了300ms, 那麼整個交互就變慢了. 可以使用Content delivery network(CDN), 將服務器的資料緩存在離客戶端最近的服務器。 減少兩者之者的距離.
構建TCP
互聯網由兩個重要的協議組成,IP 和 TCP. IP(Internet Protocol) 提供了通信雙方之間的路由以及尋址,它只管發送數據,而不保證數據是否完速的傳送到了接收端。TCP(Transmission Control Protocal), 提供了在不可靠的通道上,建立一個抽像的可靠網絡。
TCP 提供了一個可靠的抽像網絡。它嚮應用程序隱藏了複雜的網絡通信細節: 重發丟失的數據, 按序發送,擁塞控制, 數據的完整性等。 當你使用TCP時,你可以保證發送和接受到的數據是一樣的,而且順序也是一樣的。 所以TCP是保證準確傳輸最佳化的協議。
TCP 並不是 HTTP唯一的傳送協議, HTTP也可以建立在用戶數據報協議(User Datagram Protocal) 或者 UDP, 也可以選擇其它傳送協議。 但在實際中, HTTP在互聯網的通信都是通過 TCP。
所以理解TCP的核心運行原理,是我們優化web性能的基礎知識。
三次握手
所有的TCP都是從三次握手開始, 在客戶端或者服務器交換任意應用數據之前,它們必須先建立連接,確認數據包的序列號, 以及其它用於連接的特殊變量。 由於安全原因,序列號是從雙方中隨機挑選。
SYN
客戶端會挑選出一個數字序列號 X, 並且發送一個SYN 數據包(包含額外的TCP 標記和選項)
SYN ACK
服務器接受到 syn 包, 在序列號X上加1. 並且挑選一個序列號Y, 並附上它自己的標記和選項, 並響應這個數據包
ACK
客戶端對x, y 加1 , 併發回ACK包, 完成握手。
圖1-1
當三次握手完成, 應用層數據就可以在客戶端和服務器之間傳送。 客戶端可以在ACK 包發送完後立即傳送數據包。 但服務器必須在接受到ACK包之後,纔可以。 整個的啓動過程(三次握手) , 每個TCP連接都需要進行, 所以對於使用TCP的應用程序, 這個過程會非常重要: 每一個新的連接在數據傳輸之前都會有一個響應延時。
舉一個例子, 如果我們的客戶端在New York, 但服務器是在London, TCP連接是建立在光纖上的, 三次握手將至少花費56 毫秒(客戶端可以在第三次ACK後,立即傳輸數據): 28ms 是一個方向的Propogation delay.
三次握手產生的延時,使得建立新的TCP連接是昂貴的, 這也是爲什麼在使用TCP時,需要對已有連接進行重要最大的原因之一.
Slow-Start 慢啓動
比如瀏覽器(圖1-1的第二步, y+1, x+1 後就可以發送數據了, 那麼此時瀏覽器的cwnd 爲 1MSS, 在一個RTT時間內,收到帶ACKed的數據包後,會增加1個MSS. 可以發送兩個數據包了。)
而服務器要在接到收到瀏覽器的ACK包後,纔可以發送數據, 它的cwnd 爲1MSS, 在下一次接收到客戶端的ACKed包後,會增加1MSS.
方程式 2-1 表式的是, 達到指定的傳輸速度所需要的時間:
讓我們假設以下情下:
- 客戶端和服務器的速度達到 65,535 (64KB)
- 初始cwnd: 4 MSS (RFC2581)
- RTT(一個響應用的時間): 56ms (London to New York)
每一個新建的TCP連接的吞吐量都被限制爲 cwnd的大小, 實際上, 要達到64 KB的限制, 我們將增加cwnd的大小爲45 MSS, 這需要花費244毫秒:
爲了減小在增加擁塞空口大小,所花的時間,我們可以減小 服務器與客戶端 RTT的時間。 例如, 將服務器佈置到離客戶端更進的位置。 或者,我們可以增加初始的 cwnd的大小 到 10 MSS (RFC 6928).
對於視頻以及大型文件來說, Slow-start不是特別大的問題, 但對於很多短的和突發的http來說, 因爲它限制了帶寬吞吐量的能力(高帶寬,但使用不上). 這就會對性能造成影響。
- $> sysctl net.ipv4.tcp_slow_start_after_idle
- $> sysctl -w net.ipv4.tcp_slow_start_after_idle=0
爲了瞭解三次握手和慢啓動對http請求造成的影響,我們假設 New York的客戶 從 London的服務器請求一個20 KB的文件。新的TCP連接,如圖 2-5所示。
- RTT 的時間爲 56ms
- Client 和 Sever 之間的帶寬 5 Mbps;
- Client 和 server 的 receive window(rwnd): 65,535 bytes (64KB)
- 初始 cwnd : 4 MSS (4 × 1460 bytes ≈ 5.7 KB)
- 服務器處理響應的時間: 40 ms
- GET請求大小,小於單個 segement( 1460)
圖2-5
0 ms Client 通過SYN包,進行TCP第一次握手
28ms 服務器返回一個SYN-ACK, 並且有指定 rwnd 大小
56ms Client 發送一個對SYN-ACK所的確認包, 並指定 自已的 rwnd大小, 並立即發送一個 HTTP GET 請求
84ms 服務器接受 HTTP request, 並花 40 ms處理
124ms 服務器完成,併產生一個20kb的響應, 並且發送4 TCP segments的數據(5480 bytes), 並暫停,等待客戶端返回一個 ACK包
152ms 客戶端接收到 4 個片段的數據, 並確認每一段數據, 並返回一個ACKed 包。
180ms 服務器增加cwnd的值, 併發送8 個數據段
208ms 客戶端接收到 8個數據段,並確認每一段數據
236ms 服務器增加每個ACK 包的 cwnd, 併發送剩餘的數據段
264ms 客戶端接收剩餘數據段,並返回每一個段的ACKs包
在一個新的TCP傳遞一個20KB的數據, 花費了264ms . 讓我們對比一下,重用相同的TCP連接, 並請求相同的內容
圖2-6
0 ms 客戶端發送請求
28ms 服務器接收到請求
68ms 服務器處理完請求,並生成20KB的響應,但是當前的cwnd爲15 segments, 因此可以一次性發送完20KB的數據
96ms 客戶端接收到15個數據段,並ACKs 它們
同樣相同的請求在相同的連接上(除了三次握手)。 性能上提升了275%;
在這兩個例子中, 5 Mbps的帶寬沒有對性能產生任何影響,主要的影響因素是擁塞窗口大小。
擁塞避免
Bandwidth-Delay Product (帶寬遲延乘積)
TCP 內製的擁塞控制和擁塞避免帶來了另一個重要的性能優化: 發生者和接收者最佳值,這取決於RTT 和 兩者之前的帶寬( data rate)
我們回憶一下之前內容, 在發送者和接收者之間傳送中的數據大小(unacknowledged),取決於 rwnd 和 cwnd 中最小的一個。 rwnd 的大小每次都會出現在ACK包中(固定), 而 cwnd 依據發送者的擁塞控制和擁塞避免,動態調整的。
如果發生者 或 接收者 超過了最大的unacknowledged data(未確認數據), 它必須停下來,等待其它之前發送過的包的確認信息(ACK). 那麼它需要等待多久呢? 這取決於兩者之者的響應時間RTT.
Bandwidth-Delay Product
數據鏈路上帶寬與 end-to-end 之間的延時乘積。 它所表示的是, 任一時間點, 可以發送的最大 未確認(unacknowledged)的數據.
sender 或 receiver 在停下來等待之前發送的包確認信息, 就會有一段時間空隙(data gap), 在這段空隙內,發送端是不能在發送數據的。 爲了解決這個問題, 窗口的大小就需要調整到足夠大, 這樣服sender在接受到receiver 返回的ACK包之前,也可以繼續發送數據。 這樣就沒有gaps。 因此最優的窗口大小依賴於 Round-trip time (RTT). 當窗口比較小的時候, 你將會限制連接的吞吐量。
Head-of-Line Blocking 線頭阻塞
線頭阻塞使我們的應用程序避免了對數據包進行重新排序, 使得應用程序代碼容易編寫。 可是, 這會引入不確定的延時,以等待丟失的數據包重發。 這種延時可以稱爲 (jjitter, http://blog.csdn.net/junllee/article/details/6110912) ,這影響到程序的性能。
Packet Loss is OK (丟包的發生也是有好處的)
實際上, 數據包的丟失會使TCP獲得更好的性能。 丟失的包作爲一種反饋機制,TCP知道網絡擁塞, 就會調整接收者和發送者之間的發送速率。 避免造成整個網絡癱瘓。 而且, 有的應用程序可以忍受數據包丟失: audio, video , 遊戲狀態更新。 它們都不需要可靠和有序的數據傳遞。 順便說一樣, 這也是什麼WebRTC(網頁實時通信()是基於UDP作爲傳輸協議的原因.
如果一個包丟失, 視頻解碼器會簡單的在視頻中插入一小段空白, 並繼續處理之後的處據包, 如果這個空隙非常小,視頻解碼都不會通知使用者,這樣就不用在視頻輸出的時候, 以暫停的方式等待丟失的包。
同樣的,如果我們正在傳遞的數據是 3D遊戲中一個角色的狀態更新, 這時我們等待的數據是用來描述 T-1 時間點的狀態, 而T時間點的數據包已經接收,那麼T-1 秒的數據包就是不需要的了。 理想的是,我們可以接收到每一個狀態更新, 但是爲了保證遊戲的不發生延遲。 我們可以接愛間歇性的丟包, 以保證較低的延遲.
TCP 優化
TCP 協議是一種自適應的協議,以保證公平的對待網絡的各節點,並最有效的利用各種網絡。(公平, 有效利用)。 因此, 優化TCP最好的方法是讓TCP知道當前的網絡條件, 並且依據上次和下層的協議類型和要求,調整TCP的行爲。 比如, 無線網絡,它需要不同的擁塞算法; 有的應用程序爲了達到最好的體驗,會自定義QoS語義。不同的應用程序有不同的要求, 而每一個TCP算法,也有很多影響因子,這些都導致TCP的優化,成爲了學術和商業研究中永恆的課題。 在目前爲前,我們只是簡單的介紹了影響到TCP 性能的一些因素。 當然還有一些其它的因素,比如 selctive acknowledgments(SACK 選擇性確認), delayed acknowledgments (遲延性確認), 快速重發等等。 這使得每個TCP會話都變得複雜, 難以理解, 分析, 調整。
雖然每一個TCP算法都有自已特定的細節,並繼續發展出不同的反饋(feedback)機制,但核心的原理依然相同, 這些原理導致的性能問題也沒有改變:
- TCP 三次握手 引入的延遲 2 RTT
- TCP slow-start 都會發生在每個新的TCP連接
- TCP 流量 和 擁塞控制, 影響到所有TCP連接的吞吐量(帶寬)
- TCP 吞吐量會受到cwnd(擁塞窗口)大小的影響
因此, 每個tcp 連接在現代化的高速網絡下,傳送數據的速率也是受限於 發送者和接受者之間的 roundtrip 時間的影響(可以看看帶寬遲延積)。或者這麼解釋, 雖然可以無限制的增加帶寬, 採用光速傳送數據,從New York 到 London的遲延還是有28ms. 在很多情況下,TCP的瓶頸不是帶寬的大小,而是延遲的大小。 可以查看圖2-5.
調優服務器配置
- 提升TCP's 的初始化擁塞窗口的大小 原書 26頁: cwnd 在一開始如果有一個很大的值, 它允許TCP 在第一次RTT內傳遞更多的數據。 同時意味着它可以加速window的增長。 特別是 在優化突發性,短連接中起來決定性的作用。
- Slow-Start Restart 原書23頁 : 禁用在TCP閒置時,進行慢啓動。 這將明顯的做優化長連接TCP的性能。
- Window Scaling 增加 接收窗口的大小 原書18 : 增加 rwnd 大小,達到網絡最好的吞吐量
- TCP 快速打開 原書 16: 在某些情況下, 允許應用程序數據可以在初始的SYN包,一起發送。 TFO是一種新的優化方式, 要求在客戶端和服務器都支持。
調優應用程序行爲
- 儘量減小要發送數據的大小
- 我們不能改變數據發送大小的速度, 但是可以將數據放到離客戶端更近的地方。
- TCP 連接的重用,對性能的改善很明顯
性能檢測清單
- 升級你的系統內核到最新的版本
- 確保cwnd 的大小設置爲了 10 MSS
- 禁用 slow-start after idle
- 允許窗口可伸縮
- 消除多餘數據的傳輸
- 壓縮傳送的數據
- 將服務器部署在離客戶最近的地方,減小RTT
- 在可能的情況下, 重用TCP
UDP
TLS
加密、 認證、 完整性
TLS 握手
應用層協議協商(ALPN)
- 客戶端向ClientHello消息中,添加一個, 新的ProtocolNameList 字段, 這個字段包含了它所支持的應用協議列表
- 服務器檢查ProtocolNameList 字段, 並在返回的SeverHello 消息中包含 ProtocolName字段,包含選中的協議
Server Name Indication (SNI)
爲了解決這個問題, Server Name Indication(SNI) 作爲一個擴展,被引入到TLS協議, 它允許客戶端在握手開始的時候,通過hostname字段,標識出它想要建立服務器的名稱。 web 服務器會檢查SNI 中的 hostname. 並選擇合適的證書, 並繼續完成握手。
但在許多老的客戶端並不支持SNI, 比如運行在 Windows XP的瀏覽器, Android 2.2 等。
TLS Session Resumption
在所有使用安全通信的應用程序中, 握手階段導致了額外的延遲和CPU計算, 嚴重的影響了應用程序的性能。爲了減小相同的浪費, TLS 提供了在多個連接中,共用相同的安全密鑰(生成的對稱密鑰)的能力。
Session Indentifiers
會話標識重用是在SSL 2.0版本被引入, 它充許服務器創建併發送 32 字節的會話標識,作爲 ”ServerHello" 消息的一部份。
在服務器內部,它需要爲每一個客戶端緩存 session ID 以及TLS連接參數 (key, 加密算法)。 同樣, 客戶端也需要保存sessionID 信息, 並且在下次的TLS請求中, 在ClientHello 消息中帶上sessionID. 當服務器收到sessionID時, 就知道客戶端依然保存了之前加密套件和 從上次握手所獲得的 對稱密鑰。如圖 4-3所示, 一個簡短的握手過程。如果雙方沒有從緩存在找到之前的信息,那麼將會產生一個新的 session ID.
圖4-3 簡短的握手過程
利用會話標識,讓我們減小了一個RTT時間, 以及計算開銷。
可是,在實際的工作中,使用服務器創建和保存session 標識符 具有侷限性。 比如每天都會有成千上萬的連接需要保存到緩存,這會導致緩存被使用完畢, 有的網站是採用多個服務器,如果共享這些session也是一個問題。 (session 保存在服務器會帶來哪些實際問題,可以查看 http://nil-zhang.iteye.com/blog/1279214)
Session Tickets
會話船票會保存在客戶端, 在之後的連接中,會以 SessionTick 擴展,包含進ClientHello 信息中。 這樣所有的信息就僅保存在客戶端, 而且 ticket 依然是安全的, 因爲它是使用服務器才知道的密鑰加密的。
session indentifiers 和 session ticket 機制可以分別稱爲 會話緩存 和 無狀態恢復。 無狀態恢復的最大改進是不需要服務器端緩存, 客戶端只需在新的連接中提供 session ticket, 除非ticket 已經過期。
信息鏈 和 CA
我們怎樣才能知道,在建立加密遂道是我們信任的一方,而不是一個攻擊者, 所以身份證認證在TLS 連接中是不可分割的一部分。爲了知道如何驗證雙方的身份, 我們舉了在 Alice 和 Bob 之間的一個例子:
- Alice 和 Bob 都有自己的公共密鑰和私有密鑰
- 雙方都會隱藏自己的私有密鑰
- Alice 向 Bob 分享自己的公共密鑰, Bob 也向 Alice 分享了自己的公共密鑰
- Alice 向 Bob 發了一條消息,這條信息是用她自己的私有密鑰加密的
- Bob 使用 Alice's 的公共密鑰來校對信息提供者的簽名, 這條消息是由Alice發送過來,而不是其它人
信任是交換數據之前的關鍵, 公鑰加密算法,允許我們使用信息發送者的公共密鑰, 確認被簽名的信息是正確人發送過來的。 但這種信任完全是基於 Alice 和 Bob 相互認識的基礎上,才交換的公共密鑰。
下一步, Alice 從Clarlie那裏接收到一條信息, Alice 沒有見過Clarlie, 但Clarlie 聲稱它是 Bob's 的朋友。 實際上, Clarlie 爲了證明自己是Bob的朋友, Clarlie 要求Bob 對自己的公開密鑰 使用 Bob 的私有密鑰進行籤時。 並以消息的附件,一起發送給Alice. 如圖4-4. 在這種情況下, Alice 首先確認 Clarlie的公共密鑰中Bob的簽名。 她已經有了Bob 的公開密鑰, 所以知道Clarlie 的公開密鑰的確是由 Bob簽過的。Alice
接受了這條消息, 並使用消息中的Clarlie 公開密鑰, 確證發送條消息的人確實是Clarlie.
圖4-4
互聯網和你的瀏覽器之間的認證也是採用相同的處理, 瀏覽器可以通過以下幾種方式,添加信任
- 手動指定證書: 每一個瀏覽器和操作系統都提供了一種機制,你可以手動的導入你信息的證書
- CA認證中心的證書
- 瀏覽器和操作系統自帶的證書
在實際中,你不可能存放所有網站的證書。因此最通用的解決方法是使用CAs來幫我們進行校驗. 如圖 4-5. 網站會讓認證中心對他的名字和公鑰進行簽名,並將這個簽名發送到瀏覽器,如果瀏覽器的CAs的根目錄下,有認證中心的公開密鑰,就能確認這個網站是真實的。
圖4-5
圖 4-6
Certificate Revocation
不重要,忽略
TLS Record Protocol
不同於IP 或者 TCP協議, 在TLS會話中的所有數據交換都是由TSL Record協議負責。 該協議需要負責識別不同類型的消息(握手信息,警告,數據), 並校對每一個信息的完整性。
圖 4-8 TLS record 結構
傳遞應用程序的數據的流程如下:
- Record 協議獲得應用程序的數據
- 對接收到的數據進行分塊: 最大爲2的14次 bytes 或者16KB一個記錄
- 應用數據可以選擇壓縮
- Message autentication code(MAC) 或 HMAC, 添加消息的校驗碼
- 使用協商好的加密算法,對數據進行加密
完成以上步聚, 被加密的數據就會向下傳入TCP層,進行傳輸。 在接收端, 則是相反的過程。
這個過程看起來很簡單,但需要注間幾個地方:
- TLS record 最大爲16KB
- 每一個記錄會包含 5-byte header, 一個MAC (SSLv3, TLS1.0, TLS1.1 爲 20 bytes, TLS 1.2 需要32字節), 如果使用了塊分組密碼, 還需要把密碼加上。
- 爲了解密和校對record, 整個record 必須有效。
爲你的應用程序挑選出最佳的record 的大小,對性能的優化非常重要,小的records 會導致很大的開銷, 然而大的records 會被TCP重新進行組裝
優化TLS
TLS的優化主要是配置你的服務器,比如TLS records 的大小, 內存緩衝區, 證書的大小, 是否支持簡短的握手等等, 通過對這些參數的正確配置,會明顯改善用戶體驗,降低操作開銷。
計算開銷
我們之前說過, 使用公鑰加密 對比 對稱密鑰加密 會導致非常大的計算開銷。 早先的網站通常需要額外的硬件來完成 SSl計算。但現在的硬件的性能有很大的提升,都能直接完成
但對於TLS Session 的恢復, 還是可以減少使用公鑰加密。 除低計算的開銷。
提前終止
不管是新建還是重用一個連接,都會有延遲的發生, 對於優化來說, 連接的建立是一個重要的區域。 我們看看一個TLS連接有哪些: TCP 三次握手, 之後是TLS握手, 增加兩個RTT時間(新建)。 或者一個RTT(重用)。
在最壞的情況下,數據交換之前, TCP 和 TLS連接的設置過程就將花費三個 RTT. 以我們之前New York到London的例子, 一個RTT 時間爲56ms, 那麼新建一個TLS 將花費168ms, 而重用一個將花費112ms.
因爲TLS是運行在TCP之上,所以對TCP的優化,也適用於這裏。 通過CDN將服務器位置到離客戶端最近的地方,減小RTT的值。 CDN不僅可以優化靜態資源,你也可以應用動態內容上,提前終止TLS 會話。 如圖 4-9所示,在本地代理服務器上,建立與客戶端的連接(加速完成握手),代理服務器與原始服務器之間建立一個長久(沒有握手,只負責數據傳送),加密的連接。這樣所有的請求和響應都是從原服務器獲取
圖4-9 提前終止客戶端連接
要做到這一點非常容易,很多CDN都提供這樣的服務, 你也喜歡冒險,也可以以最小的花費搭建自己的基礎設施: 在全球各地的數據中心部署雲服務器,並配置代理服務器,將請求轉發到你的原始服務器中。 並可以加入基於地理位置的DNS 負載均衡。
Session Caching and Stateless Resumption
瞭解概念即可TLS Record Size
- 電路中傳輸的數據包, 會包含IPv4 20bytes 地址, 如果是IPv6會是40 bytes
- TCP 會有20 bytes頭部信息
- 40 個字節的TCP 可選信息, 比如 timestamps, SACKs
MTU通用的大小爲1500 bytes, 那麼在IPv4中 TLS record 大小就是1420 (1500- IP 20 - TCP 20 - TCP Option 40), 而對於IPv6,這個大小爲1400. 爲了兼容未來, 所以最佳的值爲1400.
如果你的服務器需要處理大量的TLS 連接,則需要爲每個連接分配最小的內存使用。 OpenSSL 通常會爲每個連接分配50KB 的內存, 但正如調整record 大小一樣,最好還是查看OpenSSL 的文檔。 Google的服務器能常將OpenSSL的值設置爲5KB.
TLS 壓縮
TLS 內部通過 record協議, 支持對傳輸的數據進行無損壓縮: 壓縮的算法是通過握手協議,雙方協商好的。 壓縮會發生在對每條record 加密之前。 但通常,你要禁止服務器的壓TLS 壓縮功能, 有以下幾個原因:
- 在2012年, 黑客利用過TLS 壓縮獲得加密認證的cookie, 這就允許黑客執行會劫持, 這種功能方法叫作"CRIME"
- 傳輸級別上的TLS 不知道要壓縮的內容是什麼,可能會壓縮已經壓縮過的數據,比如,圖片,視頻
兩次的壓縮,會浪費服務器和客戶端的CPU, 而且導致非常嚴重的安全問題, 雖然很多的瀏覽器禁用了TLS 壓縮,但爲了更好的保護你的客戶, 你需要明確的禁止服務器壓縮.
認證鏈長度
瀏覽器驗證證書的過程是: 從網站的證書開始, 在遍歷父證書, 直到信任的根目錄( 證書是分級的,全球, 國家, 省份). 因此,第一條優化的原則是,在服務器上配置所有的中級證書。 如果忘記了, 那麼許多瀏覽器依然可以工作,但是它會暫停, 等待, 並向中間證書的服務器, 獲取中間證書, 然後繼續驗證,直至根目錄信任的證書。 這過程中會有新的DNS 查詢, TCP連接, 和 HTTP GET請求, 會有數百毫秒的握手延遲。
可是瀏覽器怎麼知道如何獲取中間證書的呢? 每一個子證書通常都包含了父證書的URL
相反的, 你必須在你的信任鏈裏不會包含沒必要的證書, 或者通俗點說, 你應該減少信任鏈的大小。 回憶一下, 服務器在TLS 握手期間, 會向客戶端發送證書, 很有可能證書的發送是在發生在新TCP連接的slow-start 階段。 如果證書鏈的大小超過了TCP 初始 cwnd 的大小, 則只能先發送一部份(TCP cwnd大小), 等待客戶端的 ACK後,在發送剩下的部分, 這就會導致多個RTT時間。 如圖 4-11
圖4-11 WireShack 軟件截圖 TLS 證書鏈的大小爲 5, 323-byte
圖4-11中, 證書鏈超過了5KB的大小,它超過了一些的老的服務器的擁塞窗口的大小。這就在握手階段引入了其它RTT的大小。 有一個解決方案是增加初始的擁塞窗口的大小。 可以查看 "Increasing TCP's Initial Congestion Window " 原書 26頁。 另外, 你也可以減少證書的大小:- 減小認證的層級, 理想的是隻包含你自己的證書和中間人證書,第三個證書就是根目錄證書(瀏覽器已經信任, 不需要在發送)
- 不要發送root 證書, 如果你的瀏覽器連root證書都沒有,那麼你發了,它也會不信任它
- 將證書鏈的大小減小到 2 KB 或 者3KB, 雖然爲瀏覽器提供所有必要的證書,可以避免瀏覽器自己發起新的連接,獲取中間代理人證書,從而引發不必要的RTT. 但這可能也就發生一次,而過大的證書會導致每一次新的TLS連接,有會有額外的RTT, 這種性能損失更大
OCSP 裝訂
針對OCSP的優化, 每一個新的TLS連接都需要瀏覽器檢測認證鏈,但有一個步聚我們不能忘記, 那就是瀏覽器還需要檢測證書是否被呆銷,在這裏,瀏覽器有兩個方法,一個是下載CRL文件並且緩存, 還有一個就是通過OCSP進行實時的檢測, 從認證中心的服務器獲取響應, 並使用認證中心的公開對響應進行加密。 以證實證書是否吊銷。所以我們可以在服務器 發送一個請求到認證中心的響應,並且把它包含(裝訂)到認識鏈中(部分瀏覽器可以識別)。 這樣我們自己的服務器就可以緩存被簽名過的OCSP響應,可以節省多個客戶端的額外請求。
但這裏也需要注意一些問題:- OCSP的大小從400到 4000不等, 裝訂到你的認證鏈中, 可能會超過你TCP擁塞窗口的大小
- 只能裝訂一個OCSP響應, 那麼對於中間(發證中心)的證書,瀏覽器還是會發送OCSP請求。
最近,你需要在配置服務器,允許有OCSP裝訂的功能。 好的消息是,主流的服務器, 比如Nginx, Apache, IIS是有這個功能的。 你可以查看這些服務器的文檔說明。
HTTP Strict Transport Security (HSTS)
HTTP Strict Transport Security 是一個安全策略機制, 它允許服務器通過簡單的HTTP 頭, 比如 Strict-Transport-Security: max-age = 3153600. 向順從的瀏覽器(知道這類http頭的瀏覽器) 聲明訪問規則。 這條規則表示,客戶端必須強制執行以下規則:
- 所有到初始請求,都必須使用HTTPS
- 所有不安全的連接 和客戶端請求,在請求發送之前, 應該自動轉化爲 HTTPS
- 如果發生認識錯誤, 顯示錯誤信息, 並且不允許繞過吃警告
- max-age 表明 HSTS規則的有效期, 單位爲秒
HSTS 不僅可以將原始連接轉變爲 HTTPS, 還可以保護應程序, 在性能上, 可以減小從 HTTP-to-HTTS的跳轉(301 或者302)。
在2013年時, 支持HSTS的瀏覽器有 Firefox 4+, Chrome 4+, Opera 12+ 以及Android 版的Chrome 和 Firefox. 最新的瞭解可以查看 caniuse.com/stricttransportsecurity.
TLS 性能檢測清單
- 優化TCP, 可以查看 "Optimizing for TCP" 在原書第32
- 升級TLS 庫到最新的版本
- 允許並配置 TLS session 的緩存 和 無狀態重用
- 監測你的TLS session 緩存命中率, 並調整相應的配置
- 使用CDN, 在客戶端最近的寺方, 提前終止 TLS sesion, 降低往返導致的延遲
- 配置TLS record 的大小,以適應單個TCP段的大小, 1400 bytes
- 確保你的認證不會超過初始化擁塞窗口的大小, 低於 2 or 3KB
- 從認證鏈中刪除沒必要的證書, 減小認證鏈的深度
- 禁用服務器的TLS 壓縮
- 配置你的服務器,以支持SNI, 域名識別
- 配置OCSP 裝訂功能
- 添加 HTTP Strict Transport Security header
測試和檢驗
最後, 檢驗和測試你的配置, 你可以使用在線的服務, 比如 Qualys SSL Server Test 掃描你公開的服務器的通用配置 和安全缺陷。 另外,你也可以使用 openssl 命令, 它將幫助你檢驗整個握手過程和本地的服務器配置
$> openssl s_client -state -CAfile startssl.ca.crt -connect igvita.com:443
CONNECTED(00000003)
SSL_connect:before/connect initialization
SSL_connect:SSLv2/v3 write client hello A
SSL_connect:SSLv3 read server hello A
depth=2 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing
/CN=StartCom Certification Authority
verify return:1
depth=1 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing
/CN=StartCom Class 1 Primary Intermediate Server CA
verify return:1
depth=0 /description=ABjQuqt3nPv7ebEG/C=US
/CN=www.igvita.com/[email protected]
verify return:1
SSL_connect:SSLv3 read server certificate A
SSL_connect:SSLv3 read server done A
SSL_connect:SSLv3 write client key exchange A
SSL_connect:SSLv3 write change cipher spec A
SSL_connect:SSLv3 write finished A
SSL_connect:SSLv3 flush data
SSL_connect:SSLv3 read finished A
---
Certificate chain
0 s:/description=ABjQuqt3nPv7ebEG/C=US
/CN=www.igvita.com/[email protected]
i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing
/CN=StartCom Class 1 Primary Intermediate Server CA
1 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing
/CN=StartCom Class 1 Primary Intermediate Server CA
i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing
/CN=StartCom Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
... snip ...
---
No client certificate CA names sent
---
SSL handshake has read 3571 bytes and written 444 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : RC4-SHA
Session-ID: 269349C84A4702EFA7 ...
Session-ID-ctx:
Master-Key: 1F5F5F33D50BE6228A ...
Key-Arg : None
Start Time: 1354037095
Timeout : 300 (sec)
Verify return code: 0 (ok)
- 客戶端完成 認證鏈的驗證
- 接收到認證鏈(兩個證書)
- 接收到的認證鏈的大小
- 有狀態的TLS session 標識符
在此之例子中, 我們通過TLS的默認端口(443) 連接到 igvita.com , 並執行TLS 握手。 由於 s_client 不清楚root證書,我們需要手動將 StartSSL Certifiecate Authority 導入到根目錄(非常重要的一步) , 否則 s_client 會看到一個校驗失敗的錯誤日誌。
檢測證書鏈的過程中,我們看到服務器發送了兩個證書, 總的大小爲3,571 bytes, 非常接進 3 到 4 個段 (TCP 老的服務器初始擁塞窗口的大小爲 4 個段) 。最後, 我們檢測的協商 SSL 的變量 - 最新的協議, 加密算法 , 對稱密鑰 - 我們也可以看到服務器發磅了一個session 標識。