转载自掘金网络,原文链接:https://juejin.im/post/5c0cf55c51882530544f22e2
本篇文章主要讲述的是如何通过Node创建一个稳定的web服务器,如果你看到这里想起了pm2等工具,那么你可以先抛弃pm2,进来看看,如果有哪些不合适的地方,恳请您指出。
创建一个稳定的web服务器需要解决什么问题。
- 如何利用多核CPU资源。
- 多个工作进程的存活状态管理。
- 工作进程的平滑重启。
- 进程错误处理。
- 工作进程限量重启。
如何利用多核CPU资源
利用多核CPU资源有多种解决办法。
通过在单机上部署多个Node服务,然后监听不同端口,通过一台Nginx负载均衡。
这种做法一般用于多台机器,在服务器集群时,采用这种做法,这里我们不采用。
通过单机启动一个master进程,然后fork多个子进程,master进程发送句柄给子进程后,关闭监听端口,让子进程来处理请求。
这种做法也是Node单机集群普遍的做法。
所幸的是,Node在v0.8版本新增的cluster模块,让我们不必使用child_process一步一步的去处理Node集群这么多细节。
所以本篇文章讲述的是基于cluster模块解决上述的问题。
首先创建一个Web服务器,Node端采用的是Koa框架。没有使用过的可以先去看下 ===> 传送门
下面的代码是创建一个基本的web服务需要的配置,看过上篇文章的可以先直接过滤这块代码,直接看后面。
1 | const Koa = require('koa'); |
在启动服务器之后,将this.app.listen
赋值给this.server
,后面会用到。
一般我们做单机集群时,我们fork
的进程数量是机器的CPU数量。当然更多也不限定,只是一般不推荐。
1 | const cluster = require('cluster'); |
使用进程的方式,其实就是通过cluster.isMaster
和cluster.isWorker
来进行判断的。
主从进程代码写在一块可能也不太好理解。这种写法也是Node官方的写法,当然也有更加清晰的写法,借助cluster.setupMaster
实现,这里不去详细解释。
通过Node执行代码,看看究竟发生了什么。
首先判断cluster.isMaster
是否存在,然后循环调用createServer()
,fork4个工作进程。打印工作进程pid。
cluster
启动时,它会在内部启动TCP服务,在cluster.fork()
子进程时,将这个TCP服务端socket
的文件描述符发送给工作进程。如果工作进程中存在listen()
监听网络端口的调用,它将拿到该文件的文件描述符,通过SO_REUSEADDR端口重用,从而实现多个子进程共享端口。
进程管理、平滑重启、和错误处理。
一般来说,master进程比较稳定,工作进程并不是太稳定。
因为工作进程处理的是业务逻辑,因此,我们需要给工作进程添加自动重启的功能,也就是如果子进程因为业务中不可控的原因报错了,而且阻塞了,此时,我们应该停止该进程接收任何请求,然后优雅的关闭该工作进程。
1 | //超时 |
我们在实例化AngelServer
后,得到angelServer
,通过拿到angelServer.app
拿到Koa
的实例,从而监听Koa的error
事件。
当监听到错误发生时,发送一个自杀信号process.send({ act: 'suicide' })
。 调用cluster.worker.disconnect()
方法,调用此方法会关闭所有的server,并等待这些server的 ‘close’事件执行,然后关闭IPC管道。
调用angelServer.server.close()
方法,当所有连接都关闭后,通往该工作进程的IPC管道将会关闭,允许工作进程优雅地死掉。
如果5s的时间还没有退出进程,此时,5s后将强制关闭该进程。
Koa的app.listen
是http.createServer(app.callback()).listen();
的语法糖,因此可以调用close方法。
worker监听message
,如果是该信号,此时先重启新的进程。 同时监听disconnect
事件,清理定时器。
正常来说,我们应该监听process
的uncaughtException
事件,如果 Javascript 未捕获的异常,沿着代码调用路径反向传递回事件循环,会触发 ‘uncaughtException’ 事件。
但是Koa
已经在middleware外边加了tryCatch
。因此在uncaughtException捕获不到。
在这里,还得特别感谢下大深海老哥,深夜里,在群里给我指点迷津。
限量重启
通过自杀信号告知主进程可以使新连接总是有进程服务,但是依然还是有极端的情况。 工作进程不能无限制的被频繁重启。
因此在单位时间规定只能重启多少次,超过限制就触发giveup事件。
1 | //检查启动次数是否太过频繁,超过一定次数,重新启动。 |
同时将createServer修改成
1 | // master.js |
更改负载均衡策略
默认的是操作系统抢占式,就是在一堆工作进程中,闲着的进程对到来的请求进行争抢,谁抢到谁服务。
对于是否繁忙是由CPU和I/O决定的,但是影响抢占的是CPU。
对于不同的业务,会有的I/O繁忙,但CPU空闲的情况,这时会造成负载不均衡的情况。
因此我们使用node的另一种策略,名为轮叫制度。
1 | cluster.schedulingPolicy = cluster.SCHED_RR; |
最后
当然创建一个稳定的web服务还需要注意很多地方,比如优化处理进程之间的通信,数据共享等等。
本片文章只是给大家一个参考,如果有哪些地方写的不合适的地方,恳请您指出。
完整代码请见 Github。
参考资料:深入浅出nodejs