第五篇:koa2-web应用进阶—系统稳定性

本篇主要介绍如何提高koa web系统的稳定性,能够尽量多得捕捉&处理系统异常,减少系统崩溃的几率。
本篇将从一下几个角度介绍如何捕获/处理系统异常

一、回调函数, 返回异常

异步调用中回调函数里的异常无法被外部捕获,因此在一个函数 /API 内部发送异常是,将异常作为地一个参数传递给回调函数,例如: readFile这个函数:

1
2
3
4
5
fs.readFile(filePath,
(err, data) => { //回调函数(第一个参数为异常, 第二个为输出值)
if(err) console.log('failed to read file')
else console.log('success to read file')
})

如何编写如readFile这样的API, 下面以 建立 redis 链接为例:
a、 在 ioredis 中, 在 node 与 redis 建立连接的时候,ioredis会输出一个 node 事件 ‘connect’, 此时回调函数返回 连接对象;
b、如果在链接过程中出现异常,则 ioredis 输出事件 ‘error’,此时回调函数返回异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
let clientCreate = (config, callback_) => {
let redis = new ioredis(config) //链接请求
redis.on('connect', () => {
callback_(null, redis) //链接成功
})
redis.on('error', (err) => {
callback_(err, null) //捕捉异常
})
}
clientCreate(config, (err, conn) => {
if(err) console.log('connect failed')
else console.log('connect successfuly')
})

通过回调返回异常的方法,可以把相关异常锁在一个函数中, 而不是放在两个函数(node 事件)中,这样函数使用时,可以有序处理异常。

二、 try/catch 异常捕获

1、try/catch 这个是最容易想到的,但是这个方法在回调/异步函数的满天飞的 nodejs 这边几乎无用武之地, 例如:

1
2
3
4
5
6
7
try {
setTimeout(function() {
throw new Error('test error')
}, 5)
}catch(err) {
alert('err', err)
}

上面这个例子抛出的一场,就无法被 catch捕获。 如何捕获回调/异步函数产生的异常?
2、co+Generator函数中使用try/catch,这个在之前的篇幅中已经有所涉及例如:

1
2
3
4
5
6
7
8
9
10
11
route.get('/api/user', co.wrap(function* (ctx, next) {
try {
let users = JSON.parse(yield readFromFile()) //readFromFile为异步Promise函数
logger.debug('users', users)
ctx.body = JSON.stringify({status: 'success', data: users})
} catch(err) {
logger.error('err', err)
ctx.status = 500
ctx.body = JSON.stringify({status: 'failed'})
}
}))

在 co 包裹的 generator 函数中, 由于 需要执行完 yield 那一步才会继续执行下一部,因此,抛出的异常可以被 try/catch 捕获。

三、使用Promise对象

co+Generator 其实使用的也是Promise对象, 原理是将异步函数封装成 Promise 对象, co 源码。Promie 对象使用方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//用promise 封装异步函数
let readFromFile = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, (err, data) => {
if(err) {
reject(err) //抛出异常
}
resolve(data) //输出结果
})
})
}
//执行promise函数
readFromFile('./user.json').then(result => {console.log('result', result)})
.catch(err => { console.log('err', err)})

在 promise 对象中出现异常,可以使用 reject 函数把 Promise 对象变为失败,在 Promise 对象执行时,能够通过 catch() 方法捕获异常

四、gracefu+cluster方案

引入依赖 graceful, npm install –save graceful
原理:

graceful: 如果系统有uncaughtException(即未被捕获的异常), 延迟一段时间后退出进程
cluster: 实现多进程,如果有进程退出,自动重启

1、 cluster部分实现: 新建文件 dispatch.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const cluster = require('cluster')
const path = require('path')
cluster.setupMaster({ //设置线程参数
exec: path.join(__dirname, 'worker.js') // ./worker.js 为 graceful 部分
})
cluster.fork() //创建一个进程
cluster.on('disconnect', function(worker) { //如果线程断开
let w = cluster.fork() //重新新起一个线程,这里是gracefu+cluster方案核心之一
console.error('[%s] [master:%s] wroker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid)
})
cluster.on('exit', function(worker) { //线程退出事件
console.error('[%s] [master:%s] wroker:%s exit!',
new Date(), process.pid, worker.process.pid)
})

2、 graceful部分实现: 新建文件 worker.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const graceful = require('graceful') //引入 graceful
const app = require('./app.js') // koa web 应用
graceful({
server: [app], //监听 koa web运行状态
killTimeout: 3000, //延时 3000ms
error: function (err, throwErrorCount) { //异常
if(err.message) {
err.message += ' (uncaughtException throw ' + throwErrorCount +
' times on pid:' + process.pid + ')'
console.log(err.message)
}
}
})

3、 启动时通过 dispatch.js ==> node –harmony dispatch.js, 如果系统有未捕获的异常,3秒后会自动重启

五、PM2

gracefu+cluster 这套方案在配置层面相对复杂, 可以使用更简单的 PM2 模块启动监控 node 服务,他可以自动利用 多核cpu, 监控线程等,功能非常强大。
PM2 github
PM2 全局安装: npm install pm2 -g
PM2 启动服务: pm2 start app.js
PM2 管理的进程: pm2 list
更多的命令可以参看: pm2 github。

more

对于node 来说还有一个 domain 模块可以捕获(uncaughtException)异常, 但是这个模块相对不怎么好用,使用 gracefu+cluster 或 PM2 可以解决相应的问题。