附圖,個(gè)人對(duì)http.Agent的理解:
一個(gè) Agent是在client端用來管理鏈接的持久性和重用速种。對(duì)于一個(gè)host+port維持著一個(gè)請(qǐng)求隊(duì)列齿穗,這些請(qǐng)求重復(fù)使用一個(gè)socket控硼,直到這個(gè)隊(duì)列空自点,這時(shí)韩脏,這個(gè)socket會(huì)被destroy或者放到pool里,在pool里時(shí)這個(gè)socket將會(huì)被再次重用(這兩個(gè)行為取決于keepAlive的配置)
keepAlive
keepAliveMsecs
maxSockets
maxFreeSockets
在pool中的鏈接已經(jīng)開啟了tcp的Keep-Alive窒升,然而在server端會(huì)有如下行為影響pool中的鏈接:
測(cè)試代碼準(zhǔn)備:
client.js
const http = require('http');
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 4,
maxFreeSockets: 2
});
const test = () => {
return new Promise((resolve, reject) => {
const option = {
protocol: 'http:',
host: 'localhost',
port: 9990,
path: `/`,
agent: agent,
// agent: agent,
headers: {"Connection": "keep-alive"},
method: 'GET'
};
const req = http.request(option, function(res) {
res.setEncoding('utf8');
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
resolve(body)
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
console.log(e.stack)
});
req.end();
})
};
const sendReq = (count) => {
let arr = [];
for (let i=0;i<count;i++) arr.push(test())
Promise.all(arr).then(function(){
console.log('======end======')
})
}
server.js
const http = require('http');
let server = http.createServer(function(req, res) {
console.log(req.connection.remotePort);
res.end('200');
}).listen(9990);
server.keepAliveTimeout = 5000; // 這個(gè)值默認(rèn)就是5s,可以直接賦值修改
- server端主動(dòng)關(guān)閉空閑鏈接:client收到通知后缀遍,當(dāng)前socket會(huì)從pool中移除,下一次請(qǐng)求時(shí)會(huì)創(chuàng)建一個(gè)新的socket
Pooled connections have TCP Keep-Alive enabled for them, but servers may still close idle connections, in which case they will be removed from the pool and a new connection will be made when a new HTTP request is made for that host and port.
client.js補(bǔ)充
sendReq(1); // 先發(fā)送一個(gè)req
setTimeout(() => {sendReq(1)}, 10 * 1000); //隔10s后再次發(fā)送一次req
server.js輸出如下:
// console.log(req.connection.remotePort);
53957 // 發(fā)送第一個(gè)請(qǐng)求的socket port
54011 // 隔10s后發(fā)送第二個(gè)請(qǐng)求的socket port饱须。port不同域醇,說明第一個(gè)socket已經(jīng)被關(guān)閉
wireshark抓包如下:
可以看到每隔1s發(fā)送向server端發(fā)送了一次TCP Keep-Alive探測(cè)。由于server端設(shè)置的keepAliveTimeout
為5s(默認(rèn)就是5s)蓉媳,所以在5s后關(guān)閉了這個(gè)tcp鏈接譬挚,相應(yīng)的,client端收到關(guān)閉的信號(hào)就會(huì)close到當(dāng)前的socket酪呻,并從pool中移除這個(gè)socket
_http_agent.js
Agent.prototype.removeSocket = function removeSocket(s, options) {
var name = this.getName(options);
debug('removeSocket', name, 'writable:', s.writable);
var sets = [this.sockets];
// If the socket was destroyed, remove it from the free buffers too.
if (!s.writable)
sets.push(this.freeSockets);
for (var sk = 0; sk < sets.length; sk++) {
var sockets = sets[sk];
if (sockets[name]) {
var index = sockets[name].indexOf(s);
if (index !== -1) {
sockets[name].splice(index, 1);
// Don't leak
if (sockets[name].length === 0)
delete sockets[name];
}
}
}
// 省略其他代碼
};
- server端拒絕多個(gè)請(qǐng)求共用一個(gè)tcp鏈接减宣,在這種情況下,在每次請(qǐng)求時(shí)鏈接都會(huì)建立并且不能被pool玩荠。agent仍然會(huì)處理請(qǐng)求的發(fā)送漆腌,只是每個(gè)請(qǐng)求都會(huì)建立在一個(gè)新的tcp鏈接上
Servers may also refuse to allow multiple requests over the same connection, in which case the connection will have to be remade for every request and cannot be pooled. The Agent will still make the requests to that server, but each one will occur over a new connection.
client.js不變
server.js添加如下代碼
res.shouldKeepAlive = false; // 禁用shouldkeepAlive
res.end('200');
wireshark抓包如下:
可以看到,請(qǐng)求結(jié)束后阶冈,server就會(huì)關(guān)閉socket
When a connection is closed by the client or the server, it is removed from the pool. Any unused sockets in the pool will be unrefed so as not to keep the Node.js process running when there are no outstanding requests. (see socket.unref()).
當(dāng)想要保持一個(gè)http請(qǐng)求很長(zhǎng)時(shí)間并不在pool中闷尿,可以調(diào)用“agentRemove”(這個(gè)時(shí)間取決于server端socket close的時(shí)間)
Sockets are removed from an agent when the socket emits either a 'close' event or an 'agentRemove' event. When intending to keep one HTTP request open for a long time without keeping it in the agent, something like the following may be done:
client.js
// new
req.on('socket', (socket) => {
socket.emit('agentRemove');
});
server.js
server.keepAliveTimeout = 20000; // 為了清楚,服務(wù)端設(shè)置20s后再關(guān)閉
wireshark抓包如下:
可以看到女坑,觸發(fā)“agentRemove”后填具,當(dāng)前socket并沒有發(fā)送探測(cè)包,并且知道server端通知關(guān)閉才關(guān)閉堂飞。
當(dāng)agent參數(shù)設(shè)置為false時(shí)灌旧,client將會(huì)為每一個(gè)http請(qǐng)求都創(chuàng)建一個(gè)鏈接。
node keep-alive還是很有必要開啟的绰筛,尤其時(shí)作為中間層代理枢泰,當(dāng)有如下模式時(shí):高并發(fā)下優(yōu)勢(shì)更明顯
browser瀏覽器 -> nginx -> node -> nginx -> java
當(dāng)nginx和node都開啟keep-alive時(shí),性能測(cè)試如下:
參考資料mark:
http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
https://stackoverflow.com/questions/19043355/how-to-use-request-js-node-js-module-pools
https://github.com/nodejs/node/issues/10774
這篇文章很詳細(xì)铝噩,贊一個(gè)
https://www.zhuxiaodong.net/2018/tcp-http-keepalive/