node http keep-alive demo

admin發表於2020-06-15

http keep-alive 相關資料非常多,如果深挖,那可能就長篇大論了,不合適普及,這只是一篇新手入門引導,

主要講解 node 下 http 請求的坑,以及 keep-alive 的簡單使用,後續才會詳細剖析原理。


起因

我司使用 node 做中間層開發,所以 api 都是 node 代理轉發的,雖然目前 qps 不是特別高,都能滿足需求,但壓測總歸是有的。

不壓不知道,一壓,emmmmm。。。


搭建個純淨的 node 請求測試環境

先不說業務,我們來搭建個最簡單的測試環境,看看壓測工具能跑到多少 QPS。

然後我們寫個 api 代理模組看看能跑到多少。


使用 node 官方例子 https://nodejs.org/en/about/。

[JavaScript] 純文字檢視 複製程式碼
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

然後我們用 wrk 來壓測,因為簡單方便。


  引數 -c 併發數,就想象成同時請求後臺 api 的數量,一個頁面往往會呼叫3-5個後臺介面。

  引數 -d 持續時間,這就是壓測本質啊,持續越久,效果越真實,因為很多時候後面會掛掉的。


看看 10 併發,持續 10 秒的情況。

[Shell] 純文字檢視 複製程式碼
$ wrk -c 10 -d 10 http://localhost:3000
Running 10s test @ http://localhost:3000
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   523.02us  129.44us   4.04ms   92.33%
    Req/Sec     9.59k   550.11    10.55k    82.67%
  192745 requests in 10.10s, 25.37MB read
Requests/sec:  19083.81
Transfer/sec:      2.51MB

可以看出 Req/Sec 的 Avg 值,就是平均 QPS 為 9.59k,因為預設開了2執行緒,所以總 QPS 為 19k。


好了,測試環境和測試結果已經有了直觀的展現。

下面搭建 node 介面代理然後重新壓測。


node 介面代理

我們由於業務需求,沒用 http-proxy 做直接的代理轉發,而是基於 got 模組,自定義的介面模型。


我們先不考慮業務開銷,直接代理轉發看看結果。

[JavaScript] 純文字檢視 複製程式碼
const http = require('http');
const got = require('got');

const hostname = '127.0.0.1';
const port = 8000;

const server = http.createServer((req, res) => {
  got.get('http://localhost:3000/').then(({ body }) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end(body);
  }).catch((err) => console.log(err));
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

程式碼跟剛才一樣,只是多了一層 got 代理介面。

這裡沒用 got stream 直接 pipe 給 res,因為我們的代理介面中會做其他業務操作。


現在來壓測下這個服務的情況吧。

[Shell] 純文字檢視 複製程式碼
$ wrk -c 10 -d 10 http://localhost:8000
Running 10s test @ http://localhost:8000
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    13.63ms   20.29ms 193.78ms   94.45%
    Req/Sec   522.58    126.90   660.00     89.23%
  10237 requests in 10.03s, 1.35MB read
Requests/sec:   1020.52
Transfer/sec:    137.53KB

有點慌,什麼情況,怎麼會相差這麼多。


我們在 3000 服務中加入 console.log(req.headers); 看看 headers 欄位。

[JavaScript] 純文字檢視 複製程式碼
const server = http.createServer((req, res) => {
  console.log(req.headers); // 輸出 headers 
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

然後訪問 8000 服務,看到控制檯輸出:

[JavaScript] 純文字檢視 複製程式碼
{ 'user-agent': 'got/9.3.2 (https://github.com/sindresorhus/got)',
  'accept-encoding': 'gzip, deflate',
  host: 'localhost:3000',
  connection: 'close' }

其中 connection 所以每次請求都會重新建立 tcp 連線,浪費了不少效能。


接下來我們要開啟 keep-alive 提升代理效能。


開啟 keep-alive

安裝 agentkeepalive 模組,這是 fengmk2 大佬封裝的模組,我們直接使用看看效果先。

[Shell] 純文字檢視 複製程式碼
$ yarn add agentkeepalive

然後在 8000 服務中開啟代理。

[JavaScript] 純文字檢視 複製程式碼
const http = require('http');
const got = require('got');
const Agent = require('agentkeepalive');

const agent = new Agent();

const hostname = '127.0.0.1';
const port = 8000;

const server = http.createServer((req, res) => {
  got.get('http://localhost:3000/', { agent }).then(({ body }) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end(body);
  }).catch((err) => console.log(err));
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

重新啟動,訪問 8000 服務,檢視 3000 服務控制檯。

[JavaScript] 純文字檢視 複製程式碼
{ 'user-agent': 'got/9.3.2 (https://github.com/sindresorhus/got)',
  'accept-encoding': 'gzip, deflate',
  host: 'localhost:3000',
  connection: 'keep-alive' }

看到已經開啟了 keep-alive,我們把 3000 服務中的 log 先關掉,否則影響結果。

[Shell] 純文字檢視 複製程式碼
$ wrk -c 10 -d 10 http://localhost:8000
Running 10s test @ http://localhost:8000
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.32ms    1.84ms  19.30ms   89.87%
    Req/Sec     0.96k    69.30     1.06k    78.50%
  19141 requests in 10.02s, 2.52MB read
Requests/sec:   1910.43
Transfer/sec:    257.46KB

可以看到效能提升了 1 倍,將近 1k 了,但跟原服務 9.59k 還是相差太多。


目前我們只能開多程式來提升整體 QPS 了。

比如開 9 程式,每個程式都能吃到近 1k,差不多可以吃滿源服務。

但如果你開了 10 程式甚至更多,那瓶頸就會在源服務上,就需要給源服務加 cpu 或加伺服器。


小結

這裡還有個會影響結果的因素,而且影響會比較大。

因為是我本地測試,兩個服務都跑本地,要嚴謹的話,應該跑在伺服器上,或相同配置的虛擬機器中。

不過本文意圖是體驗 keep-alive 和怎麼用 node 吃滿介面服務。

所以就不做那麼嚴謹的測試了。


還要吐槽一點,node http 請求,效能真是低下啊,或者是我不知道怎麼正確的使用。

因為 autocannon 壓測工具,也是 node 寫的,但他能吃滿,我們直接寫請求,只能達到 1/10。


我自己寫了個基於 net 的 http 1.1 請求 demo 測試,只提升了100左右,並沒有特別大的改進。

因為水平有限,沒寫基於 net + keep-alive 的 http 1.1 測試,所以不能下結論。


後續我會慢慢研究 keep-alive 然後記錄分享的。

相關文章