Вот что я пытаюсь сделать: я разрабатываю http-сервер Node.js, который будет удерживать длинные соединения для продвижения цели (сотрудничать с redis) от десятков тысяч мобильных клиентов на одной машине.
Условия тестирования:
1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16
В первый раз я использовал модуль "экспресс", с которым я мог достичь около 120 тыс. параллельных подключений до того, как будет использоваться swap, что означает, что ОЗУ недостаточно. Затем я переключился на собственный "http" модуль, я получил concurrency до около 160k. Но я понял, что по-прежнему существует слишком много функций, которые мне не нужны в родном модуле http, поэтому я переключил его на собственный "чистый" модуль (это значит, что мне нужно обрабатывать HTTP-протокол самостоятельно, но это нормально). теперь я могу достичь около 250 тыс. параллельных подключений на одну машину.
Вот основная структура моих кодов:
var net = require('net');
var redis = require('redis');
var pendingClients = {};
var redisClient = redis.createClient(26379, 'localhost');
redisClient.on('message', function (channel, message) {
var client = pendingClients[channel];
if (client) {
client.res.write(message);
}
});
var server = net.createServer(function (socket) {
var buffer = '';
socket.setEncoding('utf-8');
socket.on('data', onData);
function onData(chunk) {
buffer += chunk;
// Parse request data.
// ...
if ('I have got all I need') {
socket.removeListener('data', onData);
var req = {
clientId: 'whatever'
};
var res = new ServerResponse(socket);
server.emit('request', req, res);
}
}
});
server.on('request', function (req, res) {
if (res.socket.destroyed) {
return;
}
pendingClinets[req.clientId] = {
res: res
};
redisClient.subscribe(req.clientId);
res.socket.on('error', function (err) {
console.log(err);
});
res.socket.on('close', function () {
delete pendingClients[req.clientId];
redisClient.unsubscribe(req.clientId);
});
});
server.listen(3000);
function ServerResponse(socket) {
this.socket = socket;
}
ServerResponse.prototype.write = function(data) {
this.socket.write(data);
}
Наконец, вот мои вопросы:
-
Как уменьшить использование памяти, чтобы увеличить concurrency дальше?
-
Я действительно запутался в том, как рассчитать использование памяти процесса Node.js. Я знаю, что Node.js работает на Chrome V8, есть process.memoryUsage() api, и он возвращает три значения: rss/heapTotal/heapUsed, какая разница между ними, какую часть я должен касаться больше, и каков именно состав памяти, используемой процессом Node.js?
-
Я беспокоился об утечке памяти, хотя я провел несколько тестов и, похоже, не проблема. Есть ли какие-либо вопросы, которые я должен затронуть, или какие-либо рекомендации?
-
Я нашел документ о V8 скрытый класс, как он описал, означает ли это, когда я добавьте свойство с именем clientId к моему глобальному объекту pendingClients, как и мои коды выше, будет создан новый скрытый класс? Доза приведет к утечке памяти?
-
Я использовал webkit-devtools-agent для анализа кучи карты процесса Node.js. Я начал процесс и сделал снимок кучи, затем я отправил ему 10 тыс. Запросов и отключил их позже, после чего я снова сделал снимок кучи. Я использовал сравнение, чтобы увидеть разницу между этими двумя моментальными снимками. Вот что я получил:
Может ли кто-нибудь объяснить это? Количество и размер (массив)/(скомпилированный код)/(строка)/Command/Array значительно увеличились, что это значит?
ИЗМЕНИТЬ:
Как я запустил тест загрузки?
1. Во-первых, я изменил некоторые параметры как на машине сервера, так и на клиентских машинах (для достижения более 60 тыс. concurrency требуется несколько клиентских машин, потому что одна машина имеет только порты 60k + (представлены 16 бит) не более)
1.1. Как сервер, так и клиентские машины, я изменил файловый дескриптор, используя эти команды в оболочке, где будет запущена тестовая программа:
ulimit -Hn 999999
ulimit -Sn 999999
1,2. На машине сервера я также изменил некоторые параметры ядра, связанные с сетью /tcp, наиболее важными из них являются:
net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432
1,3. Что касается клиентских машин:
net.ipv4.ip_local_port_range = 1024 65535
2. Во-вторых, я написал пользовательскую симулирующую клиентскую программу с помощью Node.js, так как большинство инструментов тестирования нагрузки, ab, siege и т.д. Предназначены для коротких соединений, но я использую длинные соединения и имею некоторые особые требования.
3. Затем я запустил серверную программу на одной машине и три клиентские программы на остальных трех разделенных машинах.
ИЗМЕНИТЬ: Я достиг 250k одновременных соединений на одной машине (2 ГБ ОЗУ), но оказалось, что это не очень значимо и практично. Потому что, когда соединение подключено, я просто разрешаю подключение, ничего другого. Когда я попытался отправить ответ на них, число concurrency упало до 150 тыс. Вокруг. Как я и вычислил, объем использования памяти на одно подключение составляет около 4 Кбайт, я полагаю, что он связан с net.ipv4.tcp_wmem, который я установил в 4096 16384 33554432, но даже я модифицировал он меньше, ничего не изменилось. Я не могу понять, почему.
ИЗМЕНИТЬ: На самом деле, теперь меня больше интересует, сколько памяти для подключения tcp используется и каков именно состав памяти, используемой одним соединением? Согласно моим данным теста:
150k concurrency потребляет около 1800M RAM (от free -m), а процесс Node.js - около 600M RSS
Тогда я предположил следующее:
(1800M - 600M)/150k = 8k, это использование памяти стека ядра TCP одного соединения, оно состоит из двух частей: буфер чтения (4KB) + буфер записи (4KB) (на самом деле это не соответствует моей настройке net.ipv4.tcp_rmem и net.ipv4.tcp_wmem, как система определяет, сколько памяти будет использоваться для этих буферов?)
600M/150k = 4k, это использование памяти Node.js для одного соединения
Я прав? Как уменьшить использование памяти в обоих аспектах?
Если бы я нигде не описал, дайте мне знать, я уточню это! Любые объяснения или рекомендации будут оценены, спасибо!