• 导航

18年Node.js 项目经验和心得

前端杂烩 记事本 2018-04-27 14 次浏览
从框架选型,本地开发调试,线上部署到性能监控一站式开发 Node.js 项目经验和心得
前言
提到 Node.js 有些同学可能会有抵触心理,究其原因主要是 Node.js 不如其他后端语言生态健全。
本次主要聊聊在我们实际项目中如何使用 Node.js 以及从框架选型,开发,调试,线上部署到性能监控的一些开发经验和心得。
主要从以下几个方面进行。
目前现状
目前公司前后端都是分离开的(主要是项目分离) ,前端负责页面定义格式和相应业务处理,后端只有静态资源的承载页面,即使页面上有相应的页面代码,也是非常简单。这种方式明显有几个问题,首先不利于与seo;其次,前后端的沟通成本很高。比如,页面结构调整,元素样式调整等等;最后,不论前端还是后端,前期开发调试的成本也非常高。比如,需要利用代理工具或者NG搭建后端页面等。
在进一步,前端也负责写后端的「vm」 页面,只是在发布时,发布系统把在前端项目中的「vm」 页面同步到后端相应的目录。这种模式看起来比上面那个好些,后端就不用操心「vm」页面的编写工作。虽然解决了SEO,沟通成本也相应减小,同时也引出了新的问题。比如,需要前端学习 velocity 语法,搭建能够解释 velocity 的解释器,以及 「vm」 同步的路径还要和后端沟通确定等。虽然看似获得了一门新能力,但是这其中的成本非常之高。
另外,在使用 React 的 SSR 技术时,前端和后端如何共享代码,也是问题。
如何最大化降低沟通成本,提高开发效率,是我们每一个人都绕不开话题,我们只能依据我们自身技术优势来克服或者优化这些问题。
我们的场景
我们大部分项目运行在手机端(App 端和 Touch 端),对页面的体验和响应要求非常高。比如对于一些市场活动页面,需要让用户快速看到页面(如果页面较长,就看首屏),并且是无缓存的(有些模块的显示逻辑依赖当前时间)。
如果还依赖以上两种开发方式或者离线包的话,实际的开发,运营和维护成本大家可想而知。
基于这个场景,Node.js 就在我们考虑范围内了。
它主要能解决我们三方面问题。
首先,和前端无缝对接(前后端都是使用 javascript 语言);
其次,前端也负责页面渲染工作,从而和后端数据隔离开,职责划分更为清晰,降低了沟通成本;
最后,使用 React 的 SSR 技术,前端和“后端”(页面渲染)能够互相共享,做到页面同构直出,让用户快速看到页面,提高了用户体验。
一些担忧
Node.js 从发布至今,已经块10年了,而且,前端开发人员也从之前小打小闹,转变为一个单独的职位,话语权也越来越强,按理说使用 Node.js 的机会和场景也会很多。但事实却并非如此,现在前端开发很少使用 Node.js ,更不用说搭建应用并部署线上了。
参照 Node.js 官网的样例启动一个http服务,最多不超过5分钟,按理说,这么容易就入门了,那为什么大家都不用呢?
个人认为有以下几方面的原因
1.
一些前端开发,只关注浏览器端,服务器端开发关注很少,或者根本就不关注;
2.
认为 Node.js 只适合开发一些工具类的功能,对于后端开发是个玩具;
3.
Node.js 的生态不如其他后端语言生态健全。比如从开发,调试,到部署,以及性能监控和分析;
4.
涉及到后端开发的知识面比较广,在没有这些基础知识或者经验积累的基础上,考虑问题比较片面,最终做出的系统问题比较多,容易被后端鄙视;
5.
对于 Node.js 开发后端,对项目负责人要求比较高(项目的目录规范,开发规范,系统的安全性,稳定性,可靠性,扩展性,维护成本等);
6.
以往前端不需要 7 x 24 保持待命状态,但是接触后端后,需要接收报警短信,有时出现问题还需要马上随时随地解决问题。
7.
不敢做吃螃蟹的第一人。
虽然问题很多,但我们不能被问题所吓倒,既然它能够解决我们的需求和问题,我们就可以大胆的进行尝试。
框架选型
提到 Node.js 框架,大家首先想到的应该是 express,或者 koa ,没有之一。
大家在使用这些框架时,不知道有没有想过以下这些问题:
如何确定某项功能所对应的 npm 模块名称
如何从众多相似功能的包中,选择一项稳定的,可靠的,并且文档健全的模块
如何确定项目目录划分的规范,命名规范
确定规范后,如何保证大家都认可,并且严格遵守
如何保证系统的安全性,稳定性,可扩展性
如何利用系统 cpu 多核,以及多进程之间的通信
守护进程程序的选择
如何提高调试的效率
多环境运行规则
如何使用同步写法解决异步调用
线上部署的流程性
。。。
当然,在实际开发过程中,还有许多细节问题需要考虑,以及这些细节问题如何和团队其他同事达成一致。
然而这些问题就不是 express 和 koa 这些基础性框架所能解决的。我们需要找到一款增强性的框架,对于开发人员,通过简单的文档学习,就能够快速进入业务;对于团队负责人,只需通过框架的约定和规范就能规避一些繁琐的共性的问题。 避免为了一些琐碎,不必要的问题而浪费时间。
目前 360 的 ThinkJS 和 阿里的 EggJS 就是解决此类问题而诞生的企业级开发框架。我们内部针对这两款框架,分别做了尝试,最终选择的是 EggJS 。因为 EggJS 在文档说明,框架扩展,插件数量,以及部署流畅性都有比较好的体验。同时经历了淘宝内部的双十一双十二压力测试,框架质量和性能还是有保证的。
开发模式
有些同学可能会有这样的疑问,你用 Node.js 不就是做了一个静态资源的承载页面,简单替代了后端的工作吗?也没能发挥出 Node.js 的优势啊!
确实,如果你的系统只是简单的 SPA 系统,是没有发挥出 Node.js 的优势。但如果你的系统,需要考虑 SEO ,需要使用页面直出,哪就不是简单的页面了。
首先是思维上的转变:
有些前端同学认为,前端的工作就是处理浏览器端的业务,只要涉及到页面路由,接口格式,以及接口定义都是后端的工作,认为这就是所谓的前后端分离。现在如果使用 Node.js,前端的工作,除了负责浏览器端,还需要负责后端页面的渲染,页面路由,接口的定义和格式(包装成前端使用最为合适的格式)等等工作。后端只负责数据的生产,至于谁消费,以何种方式消费,都不需要关心。双方职责界限更为明确,更为单一,一旦出现问题,也更容易定位和解决。
其次是开发体验上的转变:
以前和后端对接接口,需要反复沟通,以便能让前端使用更为方便的格式。现在,我们可以直接调用自己的接口,格式上更为灵活,沟通也更为方便,同时也避免了跨域的情况。
另外,在 Node.js 7.6 版本之前,解决同步调用方式,需要用的 generator 方式,虽然进步非常大了,但是对于一些初学者,要充分理解和使用稍微有难度。而 Node.js 8 版本出来后,对 async 和 await 语法全面的支持,更为接近后端的开发方式(书写方式)。
本地调试
应用代码中的日志推荐使用第一种方式。
框架和插件开发推荐使用第二种方式。
如果以上两种方式都不满足或者习惯使用 DevTools 调式方式,可以使用第三种。
除了以上三种还支持在 WebStorm 和 VSCode 中调试,具体细节可以参考 Eggjs 官网的 【本地开发】章节。
线上发布
node 发布
去年 11 月份之前,部署 Node.js 是通过 service_name 方式发布的。首先需要在项目目录下建立一个特殊的目录,增加 qunar_xx 服务文件,并写上“ 启停 ” 逻辑,在发布的时候,发布系统会把这个 qunar_xx 服务同步到机器系统中 /etc/init.d 目录中,启动时调用 qunar_xx start,停止时调用 qunar_xx stop。这个过程听起来没有什么问题,但实际操作上,问题确很突出,主要体现在:
不能利用发布系统中相应的端口和目录字段,只能在 qunar_xx 服务中写死,不友好
不能区分多环境策略。比如:beta 环境和 prod 环境配置规则应该都不一样
启动过程中出现错误,不方面定位问题,需要到机器上排查
写系统服务需要了解 shell 命令和系统服务的书写格式,对于前端开发同学,成本稍高
启动和停止服务除了端口,项目路径,运行环境,node.js 启动方式外,处理逻辑都一样,这部分可以提出来
系统服务在 Docker 中有限制
鉴于这些问题,后来和 CM 团队的同事进行沟通,最好做成通用的,类似 Java 那种启停逻辑。一方面能够充分利用发布系统的参数,另一方面可以把共性逻辑提出来,减少这些问题的干扰。
在沟通过程中,唯一遇到的问题是,Node.js 的启动逻辑不通用,比如有直接用 node xx.js 启动的,有用守护进程启动的(supervisor, pm2)等等。
确定共性和差异后,就借鉴之前利用系统服务的思路,把 Node.js 启动逻辑放在各自的项目中。停止服务的逻辑(根据端口号杀掉进程)和调用这个 Node.js 逻辑统一放到了发布系统中,形成了现在的 Node 发布流程。
1.
在项目中建立 deploy_scripts 目录,新增 start.sh (名称可以随便命名)
2.
在 start.sh 中填入 Node.js 启动逻辑,比如 node index.js (之前是 N 行,如今最多两行)
3.
在发布系统选择 node 发布方式,填入端口号,发布路径,以及启动脚本名称(start.sh),停止脚本填入发布系统内置的 stop.sh (按照端口杀掉进程)
发布系统调用 start.sh 的时候,会传入相应的端口($app_port),项目路径($deploy_dst)以及发布环境($deploy_type),内部拿到这些参数后,就可以做任意的事情了。
这样我们就从繁琐的发布流程中解放出来了。借此感谢公司的 CM 团队同学。
增强型发布
大家考虑这种场景:使用 React 的 SSR 的话,后端需要访问前端的文件,遇到这种情况怎么办呢?
一种方式,前后端项目在同一个工程里,到是能解决问题,但唯一的不足是前端静态资源在后端域名下,不能充分利用CDN的优势。
另一种方式, 前后端工程还是独立的,借助 gitsubmodules ,以映射文件夹的方式,把前端项目映射到后端,既遵循之前先发布前端,在关联后端开发思路,也能解决前后端文件共享。之前部分项目也是使用的这种方式。看起来完美解决问题。但是,实际操作上,理解 gitsubmodule 概念对于开发者(尤其是新同学)而言,成本还是稍高(主要这个命令不常用)。在开发调式过程中也容易出现了一些常识性的问题。
今年年初,CM 团队同学,为了解决这种问题,增加了一种全新发布方式。前端和后端项目在一个git 工程中,发布的时候,发布系统自动会把前端文件夹发布到CDN上。及解决了文件共享,还利用了 CDN。虽然缺点是前端和后端在一个git工程中,但是对于我们前端开发者而言,还是在前端工作范畴内,也是可以接受的。
性能监控
这个话题,我想应该是大多数人,不用 Node.js 的原因。
我主要从三个角度考虑这个问题
1.
框架层面
稳定性
可靠性
性能是否最优
日志是否健全,是否可扩展
2.
业务层面
每一环节是否增加日志记录
业务处理是否简单,避免复杂逻辑
不做CPU密集性运算
3.
机器层面
CPU 监控
Memory 监控
解决方案
框架层面,业务层面和机器层面,都接入公司的 Watcher 系统,比如:接口调用时长,框架错误次数,CPU 使用率(Node.js 进程), Memory 使用率(Node.js进程)等等。
通过 Watcher 系统,可以整体了解系统的运行情况,同时可以把指标范围加入报警规则,实时主动的提醒我们系统运行情况,能够在第一时间发现问题,使故障影响范围降到到最小。
Wather 系统,虽然在特定的时间点报出问题,但只限数量上的程度,具体什么问题,Watcher 系统就不行了,还需要借助「日志系统」。它可以无缝对接到业务,内置了41 种日志解析策略,做到自助式收集,即开即收,日志的保存时间,也可以自由调节。
在实际的业务中,主要把 accesslog ,应用程序本身错误,和业务中的错误或者信息接入到「日志系统」中。一旦 watcher 报警,就到 「日志系统」,找到相应时间点,具体错误就一目了然了。