Продолжение предыдущей статьиСао Ниан, а как насчет Коа и Вебпака?
В этой статье в основном рассказывается о том, как создать стабильный веб-сервер с помощью Node. Если вы думаете о pm2 и других инструментах, когда видите их здесь, то вы можете сначала отказаться от pm2 и зайти и посмотреть, есть ли какие-либо неподходящие места, пожалуйста, укажите .
Какие проблемы необходимо решить для создания стабильного веб-сервера.
- Как использовать ресурсы многоядерного процессора.
- Управление состоянием выживания нескольких рабочих процессов.
- Изящный перезапуск рабочих процессов.
- Обработка ошибок процесса.
- Количество перезапусков рабочих процессов ограничено.
Как использовать многоядерные ресурсы ЦП
Существуют различные решения для использования ресурсов многоядерного процессора.
-
Развертывая несколько служб Node на одном компьютере, а затем прослушивая разные порты, балансируя нагрузку через Nginx.
Этот подход обычно используется для нескольких машин Этот подход используется в кластерах серверов, но мы его здесь не используем.
-
Запустите главный процесс на одном компьютере, а затем разветвите несколько дочерних процессов.После того, как главный процесс отправит дескриптор дочернему процессу, закройте порт прослушивания и позвольте дочернему процессу обработать запрос.
Этот подход также распространен в кластерах Node с одним компьютером.
К счастью, новый кластерный модуль Node в версии 0.8 избавляет нас от необходимости использоватьchild_processШаг за шагом, чтобы разобраться с таким количеством деталей кластера Node.
Итак, эта статья посвящена решению вышеуказанных проблем на основе кластерного модуля.
Сначала создайте веб-сервер, сторона Node используетKoaРамка. Если вы еще не использовали его, вы можете сначала проверить его ===>портал
Следующий код представляет собой конфигурацию, необходимую для создания базовой веб-службы.Те, кто читал предыдущую статью, могут напрямую отфильтровать этот код и посмотреть на обратную сторону.
const Koa = require('koa');
const app = new Koa();
const koaNunjucks = require('koa-nunjucks-2');
const koaStatic = require('koa-static');
const KoaRouter = require('koa-router');
const router = new KoaRouter();
const path = require('path');
const colors = require('colors');
const compress = require('koa-compress');
const AngelLogger = require('../angel-logger')
const cluster = require('cluster');
const http = require('http');
class AngelConfig {
constructor(options) {
this.config = require(options.configUrl);
this.app = app;
this.router = require(options.routerUrl);
this.setDefaultConfig();
this.setServerConfig();
}
setDefaultConfig() {
//静态文件根目录
this.config.root = this.config.root ? this.config.root : path.join(process.cwd(), 'app/static');
//默认静态配置
this.config.static = this.config.static ? this.config.static : {};
}
setServerConfig() {
this.port = this.config.listen.port;
//cookie签名验证
this.app.keys = this.config.keys ? this.config.keys : this.app.keys;
}
}
//启动服务器
class AngelServer extends AngelConfig {
constructor(options) {
super(options);
this.startService();
}
startService() {
//开启gzip压缩
this.app.use(compress(this.config.compress));
//模板语法
this.app.use(koaNunjucks({
ext: 'html',
path: path.join(process.cwd(), 'app/views'),
nunjucksConfig: {
trimBlocks: true
}
}));
this.app.use(async (ctx, next) => {
ctx.logger = new AngelLogger().logger;
await next();
})
//访问日志
this.app.use(async (ctx, next) => {
await next();
// console.log(ctx.logger,'loggerloggerlogger');
const rt = ctx.response.get('X-Response-Time');
ctx.logger.info(`angel ${ctx.method}`.green,` ${ctx.url} - `,`${rt}`.green);
});
// 响应时间
this.app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
this.app.use(router.routes())
.use(router.allowedMethods());
// 静态资源
this.app.use(koaStatic(this.config.root, this.config.static));
// 启动服务器
this.server = this.app.listen(this.port, () => {
console.log(`当前服务器已经启动,请访问`,`http://127.0.0.1:${this.port}`.green);
this.router({
router,
config: this.config,
app: this.app
});
});
}
}
module.exports = AngelServer;
После запуска сервераthis.app.listen
назначить наthis.server
, который будет использован позже.
Обычно мы делаемАвтономный кластеркогда мыfork
Количество процессов — это количество процессоров машины. Конечно, больше не ограничивается, но вообще не рекомендуется.
const cluster = require('cluster');
const { cpus } = require('os');
const AngelServer = require('../server/index.js');
const path = require('path');
let cpusNum = cpus().length;
//超时
let timeout = null;
//重启次数
let limit = 10;
// 时间
let during = 60000;
let restart = [];
//master进程
if(cluster.isMaster) {
//fork多个工作进程
for(let i = 0; i < cpusNum; i++) {
creatServer();
}
} else {
//worker进程
let angelServer = new AngelServer({
routerUrl: path.join(process.cwd(), 'app/router.js'),//路由地址
configUrl: path.join(process.cwd(), 'config/config.default.js')
//默认读取config/config.default.js
});
}
// master.js
//创建服务进程
function creatServer() {
let worker = cluster.fork();
console.log(`工作进程已经重启pid: ${worker.process.pid}`);
}
Способ использования процесса на самом деле черезcluster.isMaster
а такжеcluster.isWorker
судить.
Код процесса ведущий-ведомый может быть не совсем понятен, если он написан одним куском. Этот способ написания также является официальным способом написания Node.Конечно, есть и более понятный способ написания.cluster.setupMaster
Реализация здесь подробно не объясняется.
Выполните код через Node и посмотрите, что именно происходит.
Судите первымcluster.isMaster
Существует ли он, затем циркулировать звонокcreateServer()
,fork4 рабочих процесса. Процесс печати заданияpid.
cluster
При запуске он запускается внутриTCPобслуживание, вcluster.fork()
Когда подпроцесс, поместите этоTCPСерверsocket
Дескриптор файла отправляется рабочему процессу. Если рабочий процесс существуетlisten()
Слушайте вызовы на сетевом порту, он получит файловый дескриптор файла черезSO_REUSEADDRПовторное использование порта, позволяющее нескольким дочерним процессам совместно использовать порты.
Управление процессами, плавный перезапуск и обработка ошибок.
Вообще говоря, главный процесс относительно стабилен, а рабочий процесс не очень стабилен.
Поскольку рабочий процесс имеет дело с бизнес-логикой, нам нужно добавить в рабочий процессавтоматический перезапускФункция , то есть, если дочерний процесс сообщает об ошибке из-за неконтролируемых причин в бизнесе и блокируется, в это время мы должны остановить процесс от получения каких-либо запросов, а затеммилостьчтобы закрыть рабочий процесс.
//超时
let timeout = null;
//重启次数
let limit = 10;
// 时间
let during = 60000;
let restart = [];
if(cluster.isMaster) {
//fork多个工作进程
for(let i = 0; i < cpusNum; i++) {
creatServer();
}
} else {
//worker
let angelServer = new AngelServer({
routerUrl: path.join(process.cwd(), 'app/router.js'),//路由地址
configUrl: path.join(process.cwd(), 'config/config.default.js') //默认读取config/config.default.js
});
//服务器优雅退出
angelServer.app.on('error', err => {
//发送一个自杀信号
process.send({ act: 'suicide' });
cluster.worker.disconnect();
angelServer.server.close(() => {
//所有已有连接断开后,退出进程
process.exit(1);
});
//5秒后退出进程
timeout = setTimeout(() => {
process.exit(1);
},5000);
});
}
// master.js
//创建服务进程
function creatServer() {
let worker = cluster.fork();
console.log(`工作进程已经重启pid: ${worker.process.pid}`);
//监听message事件,监听自杀信号,如果有子进程发送自杀信号,则立即重启进程。
//平滑重启 重启在前,自杀在后。
worker.on('message', (msg) => {
//msg为自杀信号,则重启进程
if(msg.act == 'suicide') {
creatServer();
}
});
//清理定时器。
worker.on('disconnect', () => {
clearTimeout(timeout);
});
}
мы создаем экземплярAngelServer
После этого получитьangelServer
, получивangelServer.app
получатьKoa
например, таким образом слушая Коаerror
мероприятие.
Отправлять сигнал самоубийства при обнаружении ошибкиprocess.send({ act: 'suicide' })
.
передачаcluster.worker.disconnect()
метод, вызов этого метода закроет все серверы, дождется выполнения события «закрыть» этих серверов, а затем закроет канал IPC.
передачаangelServer.server.close()
Метод, когда все соединения закрыты, канал IPC к рабочему процессу будет закрыт, что позволит рабочему процессу изящно завершиться.
Если процесс не завершился в течение 5 с, в это время процесс будет принудительно закрыт через 5 с.
Коаapp.listen
даhttp.createServer(app.callback()).listen();
синтаксический сахар, поэтому метод close можно назвать.
workerмониторmessage
, если это сигнал, перезапустите новый процесс в это время.
Мониторить одновременноdisconnect
событие, таймер очистки.
Обычно мы должны контролироватьprocess
изuncaughtException
мероприятие,Событие uncaughtException запускается, если неперехваченное исключение Javascript передается обратно по пути вызова кода обратно в цикл обработки событий.
ноKoa
Уже тутmiddlewareдобавлено снаружиtryCatch
. Поэтому его нельзя поймать в uncaughtException.
Вот отдельное спасибобольшое глубокое мореБрат, посреди ночи, дай мне совет в группе.
Ограниченный перезапуск
Информирование основного процесса с помощью сигнала самоубийства может создать новые соединения, всегда обслуживаемые процессом, но все же бывают крайние случаи. Рабочие процессы нельзя перезапускать часто без ограничений.
Поэтому количество перезапусков можно указать только в единицу времени, а событие отказа срабатывает при превышении лимита.
//检查启动次数是否太过频繁,超过一定次数,重新启动。
function isRestartNum() {
//记录重启的时间
let time = Date.now();
let length = restart.push(time);
if(length > limit) {
//取出最后10个
restart = restart.slice(limit * -1);
}
//1分钟重启的次数是否太过频繁
return restart.length >= limit && restart[restart.length - 1] - restart[0] < during;
}
В то же время измените createServer на
// master.js
//创建服务进程
function creatServer() {
//检查启动是否太过频繁
if(isRestartNum()) {
process.emit('giveup', length, during);
return;
}
let worker = cluster.fork();
console.log(`工作进程已经重启pid: ${worker.process.pid}`);
//监听message事件,监听自杀信号,如果有子进程发送自杀信号,则立即重启进程。
//平滑重启 重启在前,自杀在后。
worker.on('message', (msg) => {
//msg为自杀信号,则重启进程
if(msg.act == 'suicide') {
creatServer();
}
});
//清理定时器。
worker.on('disconnect', () => {
clearTimeout(timeout);
});
}
Изменить политику балансировки нагрузки
По умолчанию стоит вытеснение операционной системы, то есть в куче рабочих процессов простаивающие процессы конкурируют за входящие запросы, и кто схватит сервис, тот и обслуживает.
Занят ли он, определяется ЦП и вводом-выводом, но именно ЦП влияет на вытеснение.
Для разных сервисов некоторые операции ввода-вывода заняты, но ЦП простаивает, что приводит к несбалансированной нагрузке.
Поэтому мы используем другую стратегию узла, называемую циклической системой.
cluster.schedulingPolicy = cluster.SCHED_RR;
наконец
Конечно, создание стабильного веб-сервиса также требует много внимания, например, оптимизации связи между процессами обработки, обмена данными и так далее.
Эта статья носит ознакомительный характер, если есть неуместные места, укажите.
Посмотреть полный кодGithub.
Использованная литература:Подробное объяснение nodejs