2018年9月11日 V8项目组发布了庆祝V8十周年博文,而于2009年第一次发版内嵌l了V8 引擎的的Node.js项目也将在2019年迎来自己的第一个10年;其实Node.js并不是第一个前端脚本语言在非服务端浏览器的尝试, 前端脚本应用于服务端再最早的Netscape LiveWire 就开始运用了,但是在那个年代这种尝试并没有取得的成功;后来经过早期各浏览器厂商的激烈竞赛、WEB 2.0 兴起以及ECMAScript标准的完善和演进后,JS才被认真对待;Google的JS 引擎V8的推出,赋予了JS更强大的生命力;让非浏览器环境下的JS应用成为可能;于是Node.js就此诞生,
JS生态系统得以壮大和发展;从2009发布到发展到现在的10年里;凭借着轻量级,高性能,易部署,特别是非阻塞I/O特别在处理高并发网络请求有优势等方面的优点,让其快速发展在后端/中间层占有一席之地,成为各个大小项目服务中间层的一个优秀解决方案;
使用NodeJs的场景和背景各不相同,在此就简要介绍下猕猴桃某边缘部门在NodeJs上的应用和经验;
服务框架的选择(Express/Koa/Hapi)
- Express.js: 于2009年由TJ创建的 Express.js作为最早之一的node服务应用框架,Express.js可能仍然是目前最为流行的NodeJs Web应用框架, 一些应用较广的应用框架,诸如Sails.js, kraken.js, KeystoneJS等都是基于Express.js的基础上;
- Koa: Koa由Express的原班人马于2013年开发推出,"它被称为Express的未来,支持generators and async/await 等JS新特
- 代码都将变得更加优雅"
- Hapi.js: Hapi.js由沃尔玛实验室推出的,它更专注于配置,并提供了比Koa和Express.js更多的开箱即用功能
通过分析Hapi.js更强调框架项目的配置而不是编写功能,它是模块化的,适用于大型团队的大型应用程序。但是hapi.js是偏离Node.js开发人员习惯的标准的框架。 因此,在尝试构建快速简便的应用程序时,hapi.js可能不是最佳选择,因为它需要一些时间来习惯。在构建中小型应用程序时,Express仍然是一个很好的选择,它虽然没有类似hapi.js那样健全的开箱急用的模块,但是支持自定义插件及其独特的路由方法。通过比较我们决定在Express.js和Koa之间做出选择;Express的优势是它已经是一个非常稳定的框架了,社区支持也足够;但是自从 TJ宣告不在维护后 ,Express的未来多了些不确定性;而Koa像Express.js改进版,非常适合许多简单的Node.js项目。 它只包含最低限度(它没有内置中间件),并鼓励开发人员通过构建或利用可用的外部中间件来添加它们所需的内容。 它充分利用了现代JavaScript生成器函数和async / await,这使得它的方法具有未来感,尤其是中间件级联模式 让你开发更加容易好理解;于是我们选择了KOA;
模块化开发
- 组件化,模块化 这些热门词汇想必前端的同学每天都会接触到或者被提起,这些概念也代表了目前前端开发模式的方向,她们不但有效的解决重复造轮子的问题,也提高了开发的效率,和代码的健壮性;而作为作为服务端的Nodejs,不但提供最早的Javascript模块化支持(CommonJS),还提供诸如Npm这样的工具方便开发人员开发和维护项目;
- 初创项目由于业务简单我们很容易的通过文件夹或者文件名作区分来做模块的组织和区分;对于跨项目的模块共享,我们只能手动的用copy大法来解决,这种方式不但效率低,容出错,而且可能由于缺乏有效的使用文档导致使用错误;随着项目的业务越来越复杂,跨项目的需求越来越多,代码的模块化就显得格外的重要;不但能避免制作轮子,而且易复用,易移植,易测试;
于是我们在代码层面积极推进代码的模块化和文档化,以便管理越来越复杂的业务组件或者模块;另外涉及到公司业务的代码模块跟普通模块相比的最重要的特点那就是非公开性,这意味着我们需要搭建一个私有的npm仓库来提供对公司业务组件快速发布和安装;现有比较成熟的解决方案有sinopia 和cnpmjs ;考虑到cnpm文档更友好,项目中我们选择在虚机中启用cnpm服务来实现我们的私有npm架构;
部署+日志生成+日志收集展示(pm2+log4js+Venus)
部署
- 单进程单线程是Nodejs最重要的特点之一这里指的是JS主线程,Node本身通过libuv来调度多线程),通过实现非阻塞IO操作和事件驱动,Node.js在处理高并发网络请求有较大的优势; 但也抛出了别的一些问题,其中一个便是如何充分利用多CPU的问题,尤其是在生产环境中如何实现多进程的部署和高并发网络请求时多进程处理请求的负载均衡,成为很多开发人员需要考虑的一个问题;常见的解决方案有:
- 比如Node.js自带的 "child_process"(简称cp)模块,提供了原生的fork方法来生成可执行Node.js程序的子进程;但是创建子进程只是解决了最基本的问题,至于高并发网络请求时要求多线程处理请求的负载均衡等额外的机制和方法,都需要自己手动维护和实现;
- 于是在Node V0.6.0版本便内置了 cluster模块,cluster在cp.fork的基础上,还提供了负载均衡的功能;优化后的的cluster消除了惊群现象,默认使用round-robin算法来保证子进程任务的平均分配;凭借着可根据服务器上可用的CPU核心数量进行方便扩展和易管理,且不依赖于任何其他模块/服务,易实现过程通信等特点 cluster 成为很多生产项目的实现多进程部署的选择;
- 但是随着项目壮大,和现在对服务越来越高的要求,cluster的缺点也愈发明显;
- 我们需要手动管理进程间的状态.
- 我们不能在不影响app的情况下启动/停止/重启多进程,因为它们是耦合的.
- 进程因意外停止后,我们需要手动实现新进程的重动策略.
在这种需求背景下,pm2便变成了一个优秀的解决方案;pm2基于cluster进行了封装,提供更简单更方便的Node.js应用程序的进程管理,不但内置多进程和负载均衡的功能,而且启用只需通过配置启动脚本参数来实现。还提供日志管理,终端监控,守护进程的作用,让服务的无缝重启成为可能,
日志生成
- 日志是分析应用程序,流量和用户行为的宝贵资源,在本地/开发环境中,我们可以用简单的cosole.log和chalk大法来定位发现错误、查看debug信息;但是生产环境中,这种方法不但不方便,也是不现实的;特别是像春晚直播这种百万请求的直播,如果没有有效的日志策略,到时候想定位某个请求错误,几乎是不可能;于是选用靠谱的日志模块也是生产中比较重要的一个部分;
- 现在主流的nodejs日志框架主要有bunyan,debug,winston,,log4js;
我们在爱奇艺直播Node.js服务层中选用了log4js,主要是log4j是java最流行的日志框架,项目组的部分成员有java背景所以选用了log4js作为日志管理解决方案,log4js易使用,配置简单,而且日志等级分类非常丰富;这里就不介绍使用教程了,具体可以点击这里;
需要注意的是PM2与log4js搭配使用的时候,如果pm2启用cluster模式启动的话,需要在log4js配置
{
...
pm2:true,
pm2InstanceVar: 'INSTANCE_ID' //pm2InstanceVar跟要跟pm2的启动配置项中的pm2中的InstanceVar配置保持要一致的
...
}
pm2 部分配置:
{
...
"instances": "max",
"instance_var": "INSTANCE_ID",
...
}
同时需要安装 pm2-intercom模块;上面配置中pm2启用和cup数量一致的进程数,log4js在运行在pm2启用cluster模式时的运行策略是当worker进程需要处理记录日志时会先给master发信息,master进程只给符合process.env[config.pm2InstanceVar] === '0'的woker进程发送广播统一让它处理,这过程当中的进程通信需要pm2-intercom模块处理;
日志采集&&分析
- Venus 是爱奇艺-智能平台部-服务云-自研的实时数据采集处理平台,可以收集来自物理机,虚拟机,QAE 弹性容器,Pingback,API网关,数据库 binlog 等来源的日志数据,并提供数据获取、查询,统计等功能。所以利用Venus,我们可以有效的分析线上日志,定位线上问题,发现包括网络问题,业务逻辑问题,或者服务错误;
通过多进程部署,合理的日志策略和线上报警功能,我们能看第一时间发现和定位问题,并且及时修复线上问题, 再进行无缝重启部署应用;整个流程形成一个健全的开发,发布和分析,修复,部署闭环;
动静分离
- 传统动静不分离的网站架构随着访问量的增长很快会出现性能瓶颈;于是动静分离成为很多大型网站提高网站性能的一个重要解决方案;其中CDN(Content Delivery Network,内容分发网络)的运用是比较广泛的一个技术手段;通过CDN加速,将静态资源缓存到离用户很近的相同网络运营商的CDN节点上,能极大的提升用户的访问速度,还能节省服务器的带宽消耗,降低负载。通常通过CDN我们可以把很方便的将项目组中 JavaScript,CSS以及img 等静态资源部署到静态服务器;
- 但是这只是CDN的常有使用方法,由于爱奇艺直播不但经营 游戏直播 小剧场等基本业务,还承担这大型演唱会,春晚,明星生日会这样的大型直播业务,这些直播的特别是用户多,访问量多,特别是节目刚开播时期的页面访问量非常的巨大;为提高用户体验,提升服务质量,保障服务器稳定;我们决定将动态页面请求也对接到CDN,且让前端的接口请求和页面请求剥离开;也就是实现接口请求和页面请求的动静分离;我们选择了灵活的方案:Node服务负责页面渲染,并且在页面响应头设置缓存相关响应头字段,最后同步缓存到CDN;页面的缓存路径,缓存时间,过期策略都是可配置;剥离后的接口服务只负责数据的吐出和聚合,并将处理结果通过JSON格式返回至前端
Node.js的更新和安全策略
Node.js作为一个非常优秀的服务端运行环境受到很开发人员的青睐;但是再优秀的应用,也存在潜在的缺陷和漏洞;所以一个合理的更新和安全策略是非常有必要的;
- Node发布启用LTS(Long Term Support)计划,官方每年4月份会从master分支开出一个新分支出来并发布一个偶数版本,在接下来的4月到10月之中这个版本被称为LTS current,这段时间这个版本主要被修复 bug,增加新特性,不断改善,还可能删掉一些兼容性影响太大的改进; 到了10月份会出一个LTS(Long Term Support) Active的版本, 这个版本几乎不会再有任何不兼容的变更,除了安全相关的 OpenSSL 以外其他的依赖(比如 v8)也不会进行大的更新,active状态会持续18个月,也就是持续到第三年的4月份。等active结束后将会有12个月的维护(maintenance)期,这个过程中该版本只会有bug修复和安全更新,持续到第四年的4月份这个偶数版本将不会有任何更新,也就是每个LTS偶数版本只有3年的寿命; 介绍完LTS发布策略后,对于Node.js开发人员来说应该如何选择升级策略呢?个人建议在每年10月份LTS active偶数版本发布后就可以升级了,active不会有不兼容的升级,而且还可以及时体验相关安全更新和bug修复 (每年10月份Node.js将会推出比较激进的奇数版本,这里就不做详细介绍了)
- 至于Node.js的安全更新也是保持服务稳定策略中特别重要的环节;
比较出名的目录穿越漏洞(CVE-2017-14849),原因就是 Node.js 8.5.0 对目录进行normalize操作时出现了逻辑错误,会造成目录穿越漏洞读取任意文件;所以经常注意漏洞信息, 且及时做对应的升级和防御策略是非常重要的;推荐如下:cvedetails,nodejs vulnerability,cve
编程小贴士
..未完待续