У нас проблема с нашей средой node, работающей под высоким который нам не удалось найти.
Немного фона: мы запускаем кластерное приложение node, используя Экспресс для инфраструктуры http. В настоящее время существует 3 коробки с 8 Процессорные ядра на каждом, и каждый ящик запускает кластер из 6 node работников. Настройка, похоже, отлично работает, и я исследовал все предлагаемые методологии, так что я считаю, что настройка прочная. Мы node.js 0.8.1 с Express 2.5.11 и XMLHttpRequest 1.4.2.
Здесь проблема: мы делаем тест "темного запуска" этого продукта (то есть код клиента браузера имеет javascript-вызовы ajax для наших API-интерфейсов в фон, но не используется на странице или отображается пользователю). После нескольких минут работы система бросает:
[RangeError: Maximum call stack size exceeded]
Мы поймаем ошибку с событием "uncaughtException" в кластерный контроллер (который запускает каждого рабочего), однако нет стека на этом уровне. Я провел обширные исследования по эта проблема и, похоже, не может найти никого с подобной ошибкой. После расчесывая КАЖДУЮ строку кода в системе, вот что я знаю:
- Я не могу найти рекурсии или круглые ссылки. (Я читал, что эта ошибка не всегда означает проблему рекурсии, но мы проверили; мы фактически запускаем тесты, удаляя большую часть кода в любом случае, и это все еще происходит, см. ниже);
- Я перешел к одному рабочему процессу в поле, чтобы попытаться устранить кластер как проблема - проблема все еще происходит;
- Проблема ТОЛЬКО происходит при высокой нагрузке. Наш трафик составляет ок. 1500 страниц в секунду и, во время интенсивного движения, может достигать 15000 страниц в секунду (мы не смогли реплицировать на dev окружающая среда);
- Время обнаружения ошибки меняется, но обычно составляет 15 минут;
- Ошибка, похоже, не влияет на работу! Под этим я подразумеваю, что нет коррумпированных ответов и, кроме случайного таймаута, система никогда не сбой;
- Рабочий процесс, который перехватывает ошибку, восстанавливается и начинает выполнять снова запрашивает через несколько секунд;
- Я получил ошибку, которая произошла в самом базовом дизайне - нет вызываются дополнительные API. Просто сделайте запрос и ответьте простой ответ json. Это самая любопытная часть. Это не кажется например, система не работает ни в одном из моих кодов - она не работает без создавая экземпляр любого из классов для выполнения реальной работы. Очевидно, что я начался с большего количества кода, но медленно доставал кусочки, пока он не был по-прежнему не удается выполнить настройку "без костей".
Самый яркий симптом, я считаю, заключается в том, что ошибка всегда происходит ПОСЛЕ того, как запрос был полностью подан. То есть сервер принимает запрос, находит правильный экспресс-маршрут, вызывает res.send и законченный. Это действительно похоже на сборку мусора для меня! я прочел что двигатель V8 имеет очень хороший двигатель GC, но мне интересно, как большая часть нашей тяжелой нагрузки влияет на вещи.
Как я уже сказал, код выдает ошибку даже при базовом дизайне. имеющий вытащили большую часть нашего пользовательского кода, это основа настройки. Извините, что я режу здесь, поэтому не все объявления переменных и т.д. быть включенным, однако код действительно работает, и все это находится в реальный код:
Кластерный контроллер. Это очищенная версия того, что запущено в командной строке.
cluster = require('cluster');
path = require('path');
fs = require('fs');
app = require('./nodeApi');
_ = require('underscore');
nodeUtil = require(./nodeUtil);
process.on('uncaughtException', function(err) {
var stamp;
stamp = new Date();
console.log("***************************** Exception Caught, " + stamp);
return console.log("Exception is:", err);
});
if (cluster.isMaster) {
if ((nodeUtil.isLiveServer() || nodeUtil.isCluster()) && process.env.IS_CLUSTER !== '0') {
numCPUs = require("os").cpus().length - 2;
if (numCPUs <= 0) {
numCPUs = 1;
}
} else {
numCPUs = 1;
}
console.log("Forking " + numCPUs + " workers...");
for (i = _i = 1; 1 <= numCPUs ? _i <= numCPUs : _i >= numCPUs; i = 1 <= numCPUs ? ++_i : --_i) {
worker = cluster.fork();
}
} else {
app.start();
}
nodeWorker.. Использование Express и простой маршрут для обслуживания запрос. Запрос завершается обратным вызовом, если используется jsonp (для нашего тестирование с помощью ajax, это было необходимо)
(function() {
var crypto, express, fs, modroot, path, staticroot, _;
express = require('express');
_ = require('underscore');
fs = require('fs');
path = require('path');
module.exports.start = function() {
logFile = fs.createWriteStream("" + logpath + "/access.log", {
flags: 'a'
});
app = express.createServer();
app.configure(function() {
app.use(express.logger({
stream: logFile,
format: ':remote-addr - [:date] - ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" :response-time ms'
}));
app.use(express.errorHandler({
dumpExceptions: true,
showStack: true
}));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({
secret: "ourMemStoreSecret",
cookie: {
domain: ".ourdomain.com"
},
maxAge: new Date(Date.now() + 7200000),
// The store WAS a redis store. I took it out to eliminate redis as the issue. We don't use sessions anyway.
store: new require('express').session.MemoryStore({
reapInterval: 60000 * 15
})
}));
app.use(express["static"](staticroot));
app.set('view engine', 'underscore'); // For our template rendering. Not used in this test.
app.set('views', __dirname + '/views/src');
app.set('view options', {
layout: false
});
app.use(app.router);
});
ignore = function(req, res, next) {
if (req.params.api === 'favicon.ico') {
return next('route');
}
return next();
};
wrapCallback = function(req, res, next) {
var callbackName;
if (callbackName = req.query.callback) {
req.wrapCallback = true;
res._send = res.send;
res.send = function(data, status) {
var dataString;
if (_.isObject(data)) {
dataString = encodeURI(JSON.stringify(data));
res.setHeader('Content-Type', 'application/javascript');
return res._send("" + callbackName + "(\"" + dataString + "\")", status);
} else {
data = encodeURI(data);
return res._send("" + callbackName + "(\"" + data + "\")", status);
}
};
}
return next();
};
app.error(function(err, req, res, next) {
console.log("[" + process.pid + "] Error Handler. Ok.", err);
return res.send({
error: err.msg
}, err.statusCode);
});
// Does anyone know how to hard-code a path AND put it into a variable at the same time?
// Kind of like: "/:api=MyTestAPI" ?? That why this route is here.
setAPIName = function(req, res, next) {
req.params.api = 'MyTestAPI';
return next();
};
app.get("/MyTestAPI", setAPIName, wrapCallback, function(req, res) {
res.send({
hello: 'world'
}, 200);
return console.log("[" + process.pid + "] res.send (no cacher) is done");
});
process.setMaxListeners(0);
process.send({
// For IPC - the controller has a handler for this message
cmd: 'isStarted'
});
return app.listen(process.env.APP_PORT);
};
}).call(this);
Как выглядит ошибка. В принципе, я никогда не вижу, чтобы это происходило в в середине запроса. На ошибке нет стека вызовов либо - это только сообщение. Здесь вы можете увидеть 2 каждый обрабатывает ответ, тогда ошибка на одном из их.
[660] res.send (no cacher) is done
[654] res.send (no cacher) is done
***************************** Exception Caught, Fri Nov 02 2012 10:23:48 GMT-0400 (EDT)
Я бы очень признателен за некоторые отзывы об этом. Система работает красиво и способно обрабатывать наш огромный трафик тремя ящиками. Нагрузка на ящики составляет около 40% и напевает. Мне бы очень хотелось найти источник этой проблемы, чтобы другие могли гордиться этой системой, как я, и покажите node.js неверующим, что это отличный продукт!