跳至主要內容
Skip to content

持久連線與 Keep-Alive:讓 TCP 連線物盡其用

在 HTTP/1.0 時代,每個請求都需要新建 TCP 連線,這是巨大的效能浪費。HTTP/1.1 引入了持久連線(Persistent Connection),徹底改變了這個局面。本篇將深入探討持久連線的運作機制。


一、 為什麼需要持久連線?

1.1 TCP 連線的代價

建立一個 TCP 連線需要經過三向交握,這需要 1 個 RTT(往返時間):

如果是 HTTPS,還需要 TLS 握手,額外 1-2 個 RTT

1.2 短連線的問題

HTTP/1.0 預設每個請求都開新連線:

問題

問題影響
重複握手每次都要 1-3 RTT
TCP 慢啟動每次都從低速開始
伺服器負擔頻繁建立和關閉連線
埠號耗盡高並發時可能耗盡

二、 持久連線的運作

2.1 HTTP/1.1 預設持久連線

HTTP/1.1 預設開啟持久連線,不用每次都重建:

2.2 Connection 標頭

控制連線行為的關鍵標頭:

http
# HTTP/1.1 預設保持連線,不需要明確指定
Connection: keep-alive    # 通常可省略

# 明確要求關閉連線
Connection: close

版本差異

版本預設行為保持連線關閉連線
HTTP/1.0短連線Connection: keep-alive不指定
HTTP/1.1持久連線不指定Connection: close

2.3 何時關閉連線?

連線會在以下情況關閉:

  1. 客戶端發送 Connection: close
  2. 伺服器發送 Connection: close
  3. 超時:閒置超過 Keep-Alive timeout
  4. 請求數量上限:達到 max requests
  5. 錯誤:發生不可恢復的錯誤

三、 Keep-Alive 配置

3.1 Keep-Alive 標頭

提供連線保持的參數資訊:

http
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=100
參數說明
timeout閒置超時秒數
max此連線允許的最大請求數

3.2 Nginx 配置範例

nginx
http {
    # 保持連線超時時間
    keepalive_timeout 65;

    # 單個連線最大請求數
    keepalive_requests 100;

    # 客戶端 Body 超時
    client_body_timeout 60;

    # 客戶端 Header 超時
    client_header_timeout 60;
}

3.3 Node.js 配置範例

javascript
const http = require("http");

const server = http.createServer((req, res) => {
  res.end("Hello World");
});

// 設置超時(毫秒)
server.keepAliveTimeout = 5000; // 閒置超時
server.headersTimeout = 60000; // Headers 等待超時
server.timeout = 120000; // 總請求超時

server.listen(3000);

四、 管線化(Pipelining)

4.1 什麼是管線化?

在同一個持久連線上,不等待回應就連續發送多個請求:

4.2 隊頭阻塞問題

管線化的致命缺陷:回應必須按順序返回

快的請求被慢的請求「堵住」了!

4.3 為什麼瀏覽器不使用管線化?

原因說明
隊頭阻塞一個慢請求阻塞所有後續
代理不支援很多中間代理處理不正確
伺服器相容性不是所有伺服器都支援
錯誤恢復困難失敗時難以判斷哪個請求出問題

NOTE

由於這些問題,主流瀏覽器都停用了 HTTP/1.1 管線化。HTTP/2 用多路複用徹底解決了這個問題。


五、 連線池(Connection Pool)

5.1 瀏覽器的策略

由於管線化不可靠,瀏覽器選擇開多個連線

瀏覽器連線限制

瀏覽器每個域名最大連線數
Chrome6
Firefox6
Safari6
Edge6

5.2 域名分片(Domain Sharding)

為了突破連線限制,有時會使用多個域名:

# 將資源分散到多個域名
static1.example.com  → 6 個連線
static2.example.com  → 6 個連線
static3.example.com  → 6 個連線

總共 18 個並行連線!

WARNING

這在 HTTP/1.1 時代是有效的優化,但在 HTTP/2 時代反而有害(因為無法使用單一連線的多路複用)。


六、 最佳實踐

6.1 伺服器端

nginx
# Nginx 推薦配置
http {
    # 長一點的超時,適合大多數場景
    keepalive_timeout 65;

    # 足夠的請求數
    keepalive_requests 1000;

    # 對上游(後端服務)也使用 keepalive
    upstream backend {
        server 127.0.0.1:8080;
        keepalive 32;  # 保持 32 個空閒連線
    }
}

6.2 客戶端

javascript
// Node.js HTTP Agent 配置
const http = require("http");

const agent = new http.Agent({
  keepAlive: true, // 啟用持久連線
  keepAliveMsecs: 1000, // 閒置時每秒發送 TCP keep-alive
  maxSockets: 100, // 每個主機最大連線數
  maxFreeSockets: 10, // 最大空閒連線數
  timeout: 60000, // 超時
});

http.get(
  {
    hostname: "example.com",
    agent: agent,
  },
  (res) => {
    // ...
  }
);

6.3 監控指標

應該監控的連線相關指標:

指標說明警戒線
活躍連線數正在使用的連線接近上限
空閒連線數等待復用的連線過高浪費記憶體
連線建立率每秒新建連線數過高表示 keep-alive 無效
平均連線復用次數每個連線完成的請求數過低需調整配置

七、 TCP Keep-Alive vs HTTP Keep-Alive

這是兩個完全不同的概念!

面向TCP Keep-AliveHTTP Keep-Alive
層級傳輸層應用層
用途偵測死連線復用連線
機制發送探測封包Connection 標頭
預設通常關閉HTTP/1.1 預設開啟

TCP Keep-Alive

# Linux 預設值
tcp_keepalive_time = 7200    # 閒置 2 小時後開始探測
tcp_keepalive_intvl = 75     # 每 75 秒探測一次
tcp_keepalive_probes = 9     # 失敗 9 次判定斷線

HTTP Keep-Alive

http
Connection: keep-alive
Keep-Alive: timeout=5, max=100

總結

概念說明
持久連線HTTP/1.1 預設開啟,複用 TCP 連線
Keep-Alive 標頭控制連線超時和最大請求數
管線化理論上可行,實際因隊頭阻塞問題被棄用
連線池瀏覽器開多個連線繞過限制
域名分片HTTP/1.1 優化技巧,HTTP/2 時代已過時

> **核心要點**:持久連線減少了 TCP 握手開銷,但因為隊頭阻塞問題,HTTP/1.1 仍需開多個連線。HTTP/2 的多路複用才是終極解決方案。


進階挑戰

  1. 使用 netstatss 命令觀察瀏覽器與網站建立了多少個 TCP 連線。
  2. 配置一個 Nginx 伺服器,設置不同的 keepalive_timeout 值,觀察對效能的影響。
  3. 思考:為什麼 HTTP/2 只需要一個 TCP 連線,卻能達到比 HTTP/1.1 多個連線更好的效能?

延伸閱讀與資源