我看了這個 feature 的歷史原因,覺得蠻有趣的
簡單來說,由於 http.Agent 跟 HTTP/1.1 KeepAlive 的特性,HTTP client "通常" 會盡可能的使用已經建立好的 TCP connection,導致舊的 K8S Pod 一直處於 High CPU Usage,而新的 K8S Pod 則沒辦法分散流量。設定 maxRequestsPerSocket 之後,就可以讓老舊的 TCP connection 關閉,從而達到負載均衡。
maxRequestsPerSocket + pipeline 實測server 設定 maxRequestsPerSocket = 3
import http from "http";
const httpServer = http.createServer();
httpServer.maxRequestsPerSocket = 3;
httpServer.listen(5000);
httpServer.on("request", (req, res) => res.end(req.url));
client 用 HTTP/1.1 pipeline 的概念,發送 4 個 raw HTTP requests
GET /1 HTTP/1.1
Host: 123
Content-Length: 1
1GET /2 HTTP/1.1
Host: 123
Content-Length: 1
1GET /3 HTTP/1.1
Host: 123
Content-Length: 1
1GET /4 HTTP/1.1
Host: 123
Content-Length: 1
1
Keep-Alive: timeout=5, max=3
Connection: close,但此時還不會真的關閉連線503 Service Unavailable
HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=3
Content-Length: 2
/1HTTP/1.1 200 OK
Connection: keep-alive
Keep-Alive: timeout=5, max=3
Content-Length: 2
/2HTTP/1.1 200 OK
Connection: close
Content-Length: 2
/3HTTP/1.1 503 Service Unavailable
Connection: close
Transfer-Encoding: chunked
0
Keep-Alive: timeout=5, max=3 屬於 "歷史遺留的非標準擴充",詳細請參考 RFC 2068 Section 19.7.1.1
on("dropRequest")若 user program 想要在 server 回傳 503 Service Unavailable 之前加上一些監控的邏輯,可以使用 server.on("dropRequest")
httpServer.on("dropRequest", (req, socket) => {
// 監控是否為惡意 User-Agent
console.log(req.headers["user-agent"]);
// ❌ 不建議使用 socket.write, socket.destroy, socket.end 等等會影響 socket 狀態機的操作
// 因爲 Node.js 會幫忙回 503 Service Unavailable
});
maxRequestsPerSocket + request 按照順序正常情境(非 pipelined requests),超過 maxRequestsPerSocket 的請求,就會導到新的 TCP 連線
server 設定 maxRequestsPerSocket = 3
import http from "http";
const httpServer = http.createServer();
httpServer.maxRequestsPerSocket = 3;
httpServer.listen(5000);
httpServer.on("request", (req, res) => {
console.log("remotePort: ", req.socket.remotePort);
res.end();
});
client 用 http.request 依序發送 4 個 request
import http from "http";
const req1 = http.request({ host: "localhost", port: 5000 }).end();
await new Promise((resolve) => req1.on("close", resolve));
const req2 = http.request({ host: "localhost", port: 5000 }).end();
await new Promise((resolve) => req2.on("close", resolve));
const req3 = http.request({ host: "localhost", port: 5000 }).end();
await new Promise((resolve) => req3.on("close", resolve));
const req4 = http.request({ host: "localhost", port: 5000 }).end();
await new Promise((resolve) => req4.on("close", resolve));
從 server log 可以觀察到,第 4 個 request 就會開啟新的連線,remotePort 跟前 3 個 request 不一樣
remotePort: 53443
remotePort: 53443
remotePort: 53443
remotePort: 53444
在這篇文章,我們學到了
maxRequestsPerSocket 可以強制汰換掉老舊的 TCP connectionmaxRequestsPerSocket 遇到 pipelined requests 的情境on("dropRequest") 的用法最後比較一下 maxRequestsPerSocket 遇到 pipeline 跟 non-pipeline requests 的差異