接近快两个月没有更新博客了,总结有以下几种原因
- 6月房子到期了,要找新地方租房啦加上基友出差,每周末等他回来一起去看房,真累,并且和上个房东还就押金的事有很多冲突生活不简单呀;
- 部门 php 资源有点不够,上司同意并建议引入 nodeJs,我也终于等到了这一天,于是开始查看 egg 文档,发现跟 koa 一样,上手难度基本没有,也不愧是阿里巴巴的企业级开源框架,从半自然路由方式到 MVC 的分离已经做的极好了,在日志系统与定时任务上也已经做的相当完善,并且直接引入 cluster 模块,使用 TCP 建立 master 进程去 fork 复制 cpu 数量的子进程去执行业务代码,还在 master 中独立引入了 agent 层去处理一些公共配置逻辑,可以说在集群的稳定性方面下了一定的功夫了,在 《NodeJs 深入浅出》 第九章的总结内,我会总结这个集群的创建原理;
- 既然已经写到了这篇博客,当然就代表我已经将 egg 搭建并已经成功上线了 2 个活动模块啦,其中的基础设施为了方便 php 的同事以及老大去 review,我基本上就是复刻了部门 php 架构的基础设施,现在暂时是将目前要用的都引入了,后面用到哪个就再继续重构哪一部分就好,php 嘛虽然不怎么会写,但是看懂完全没有问题~遇到不知道的函数 百度 谷歌就好(对了,我们老大是 php 出身,所以写 node 的时候我尽量是复刻了 php 内的所有逻辑,只是将一些注入方式改为了适合 egg 的方式);
- 最后一个原因,当然是懒啦= =。。。其实要写的话上两周肯定是有一定的时间去写的。
接下来说说在基础设施的搭建以及上线 2 个活动模块的一些总结吧。
关于配置文件的动态环境注入与改造
- 关于本地开发启动 egg-bin 时的配置注入,我没有进行任何改动,仍然是
extend(default, local)
,即 local 配置去合并 default,不一样的是,我们将 local 配置进行了抽离,每个开发同事的 local 配置都是个人的,在 git 提交代码时,是不提交 local 的,所以我们将通用的中间件配置(如 cors、session、bodyParse 等)写在了 default 中,而将业务配置(如 mysql的多个连接配置、redis配置、业务模块配置等)写在了 local 中,原因就是为了每个同事去开发时只用关注自己模块的配置,而不需要关注其他业务的配置(因为每个活动模块都是独立的),这就是本地配置的抽离; - 关于测试环境配置的抽离,我们遵循了 php 架构的分离方式,我将 test 配置由单元测试的命令改成了环境命令,即运行 start 时更改
process.env.NODE_ENV
与EGG_SERVER_ENV
为 “test” 并启动 egg-script,而 test 的配置我们在本地的 git 也是处理 .ignore 列表中的,不允许上传,需要更改 npm run start 的配置时需要登录到测试服务器中直接更改 config.test.js 文件,杜绝了所有人都能随意更改的情况,由此可以看出测试环境与本地配置的不同,本地 local 配置,我们每个人是各自为战的,到测试环境后,会存在每个人的业务配置(因为是测试环境嘛哈哈,这就不解释了),有同学会吐槽了,恩?你们的单元测试呢?哎呀。。。暂时我们不会写单元测试,所以嘛,我先把它干掉了,因为我们多为活动模块,开发时间短,活动周期短,讲究敏捷开发,实际上我们没有时间写单元测试,如果以后需要再添加回来改一个命令就好,不用担心~ - 关于正式环境配置的分离,经过老大的沟通,prod 配置应该只能由运维人员管理,我们只能将业务配置从 prod 中抽离为
events.js
模块,这名字就随便你怎么定啦,反正在 prod 中引入就好,这也是为了安全性着想,毕竟正式环境的 sql 连接的信息和 redis 的配置信息,我们都是没权限知道的[哭哭囖],所以所有我们可以更改的业务配置我们都要抽离出来~
最后的结构就是这样啦,很简单,灰色就是被 git ignore 的,这个大家都懂。
日志文件是独立抽取到了一个公共的日志目录中的,服务器下并未存放在项目下,这也是为了保证各个语言及项目日志路径的统一。
关于 controller 层的模块分离与 Model 层的公共抽取
这个没什么好说的,按照 egg 文档来就行了,controller 目录下,建立多个目录进行级联操作就可以实现,service 层,主要是实现了一些第三方 api 获取数据的调用以及公共业务的数据获取(如常见的登陆机制、报名机制、用户信息、抽奖信息等),这个就大家各自根据业务抽象即可,最后在 router.js 中完成映射即可,至于中间件的公用以及私用这个就不提了,使用过 express 或者 koa 的都懂,即使没使用过,egg 文档内也写的十分清楚了,过~。
关于静态目录的挂载方式
- 本地使用
static
中间件实现即可,每个单页前端项目,直接访问具体的静态活动目录的index.html
即可,第一次 connection 监听成功后,由于是单页应用,路由交由前端控制,后端只提供业务接口即可,然后这存在一个不好的问题,就是每次访问必须是具体的静态文件,即要 url 地址要输出文件的全名,很明显用户体验这样是极差的,所以正式与测试环境我们是使用 nginx 的 - 生产与测试环境,我们使用 nginx 直接挂载整个 public 目录即可,当然不用 nginx 的话,我们也可以用 node 自己实现,实现方式十分简单,就是去 get 目录地址,正则匹配到是 public 目录时,访问具体目录直接返回目录下的 index.html,权衡之后,明显 nginx 方便,而且 nginx 还有做负载均衡咧 = =,别这么麻烦自己做啦~nginx 配置的话,vue-router 文档内就有 H5 路由 History 模式下的后端配置
关于定时任务的注意点
定时任务是必须写的,主要用于更新一些表数据的定时更新,比如我司由于用户量比较大,所以本可能做到每个活动的用户数据都实时更新,因为有大量的分表与数据量,所以如排行榜之类的数据一般是一段时间跑一次脚本更新,egg 文档中 定时任务 schedule 是有两种方式编写的,第一种是 class 的方式,第二种是直接 module.exports 的方式,建议使用第二种,因为第二种是可以传入参数 app 的,可以获取到 app 上挂载的配置信息,用于对定时任务做一些配置注入限定。
- 定时任务中的
disable
配置并不是实时读取的,在以 class 类的方式书写时,你会发现配置是一个静态方法,而直接 exports 方式写的时候则并非是一个异步方法,其实这种方式就明显告诉了我们,定时任务的配置是先读后跑的,什么意思呢?简单来说,配置信息的函数就相当于是一个闭包,在启动服务时已经先注入了配置,不会在每次执行定时任务时再次读取配置(不然怎么知道你定的时间呢,时间不可能让你每次都动态,如果时间每次都动态,等于要为用户创建一个队列去记录每个定时任务的动态时间,这是没有必要的),但是我们仍然可在 disable 中写动态的时间配置,因为每次部署重新读取配置后就可以停止啦~(有点废话了。。。毕竟一次写后,不用以后手动停止了,其实这才是我想说的)当然,我们在 task 里加入动态判断的条件return 0
就可以了,如下图 - 只是更新表数据而不是在当前服务进程下挂载计算后的变量时,
type: worker
即可,如果我们是更新一个公共的第三方的东西(比如表数据),只要其中一个子进程运行后,其他子进程再次读取第三方时,第三方已经是更新后的状态了,但是如果我们是要更新某个挂载在当前进程服务上的变量时,就必须type: all
了,原因就是每个子进程其实都是独立 http 服务,egg 每个子进程会启动 http 自己进程的服务但不监听端口并运行业务代码,master 进程本质上只负责启动 TCP 监听指定端口并向子进程发送 tcp 句柄,这样就不会有端口冲突了错误了,并且使用上了多进程充分榨干多 cpu 的性能(想了解更多进程的玩法,大家可以看文档中提到的 cluster 实现原理,原理是很简单的,如果想更深入的了解,可以关注我《Node 深入简出》第九章的总结。
关于 macOs 启动 egg-bin 进行文件监听 fs.watch 时可能会报错前端 node_modules 中模块不存在问题(解决电脑配置过低时因 cpu 计算能力与内存分配不足导致无法启动 egg-bin 的问题)
在实际开发上线的过程中,macOs 出现过一个 windows 不会出现的问题,就是启动 egg-bin 时,我们通过源码知道默认监听的目录是 process.cwd
,即整个当前进程启动的根目录,而同时当先启动前端服务时(即通过 webpack 的 dev-server 快速创建一个可访问的应用),再启动后端的 node 服务时,egg-bin 会不停报错 /app/public/activity/XXX/node_modules
下所有模块为 null
。
windows 未出现此问题,我们暂时没找到原因,所以选择先忽略 watch public 目录或 node_modules 目录来解决,但是我通过 egg-bin 的源码发现 egg-bin 是直接 watch 整个指定目录的,而不是递归遍历 watch 指定目录下的每个子目录,这就导致了我们无法直接传入参数去忽略某个目录。
而配置较低的电脑可能会出现 cpu 运转率达到 100%,但内存分配仍然不足的情况,导致 node 进程失去了计算能力而没办法继续 io 项目中的文件进内存,这同时也是因为 egg-bin 自身是 fs.watch
的当前项目整个目录,也就是说连同 /node_modules
目录也 watch 了,这显然会导致整个项目占用的硬盘空间是多少,初始启动 egg-bin 的内存分配就需要多少,而 egg-bin 由于是直接 watch pwd 的,而不是级联目录遍历的,所以它没办法去排除指定目录。
最后综上原因的解决方式是查询官方 issue 发现天猪推荐的 egg-watcher-chokidar 模块引入,传入 ignore: /public/*
(静态文件目录随意你是否忽略) 与 /node_modules/***(这个目录请一定要忽略watch)** 即可,官方文档推荐是使用参数
usePolling: true,但是我们发现使用这个配置后会时 cpu 飞速暴涨到经常 100%,所以将其设置为了
false,这是 [chokidar](https://github.com/paulmillr/chokidar#api) 官方文档给出的 cpu 占用过高方案,经测试并未影响到文件监听更改,可放心使用,后来看了下,为
true` 时监听普通文件的 interval 默认时间是 0.1s,监听二进制文件的是 0.3s ,并且连同会监听网络请求(惊了。。。黑人脸,能不cpu过高???我不需要监听网络也关了你啦)。
// /config/config.local.js
exports.watcherChokidar = {
ignore: '/node_modules/*',
usePolling: false,
alwaysStat: true
}
// /config/plugin.js
if (process.env.NODE_ENV === 'development') {
exports.watcherChokidar = {
enable: true,
package: 'egg-watcher-chokidar'
}
}
搭建与上线过程中最重点的是什么?
- 调试问题:本地的调试与线上调试是有区别的,要善于利用日志打印,比如在我取出 cookie 时其实是 encode 的状态,但是一开始开发的时候我是不知道的,后来打印日志在服务器下测试才发现,或者是最近出现过一个 mysql 返回数据序列不一致的问题,在本地调试返回数据序列是固定的,直接执行 sql 也是固定的,但是到服务器上返回却乱序了,这些问题,我们只有通过日志来调试,所以在可能出现问题的地方,一定要善于利用日志,不论是 业务日志文件 还是 定时器日志文件。
- 关注性能:当前由于代码量与配置数比较少,业务也不太复杂,所以体验还比较良好,但是一旦项目臃肿后,各种问题就会来了,所以现在可以不在意,但是一定要学会末雨绸缪,去学习一些相关的方法,比如尽量降低中间件的数量与运用缓存来降低 qps 等方法,多学习多了解即可。
- 部署问题:其实在部署方面,我没有做什么,90%以上都是老大来做的,比如 jenkins 部署脚本的书写、日志文件的迁移与定时删除、与运维人员协商相关配置与文档,这些其实是很重要的一个环节,是会直接影响开发效率与质量的,这是一点需要慢慢了解的地方。
写了 Node 就算是后端了吗?
其实不是的,你可以说自己跨入大前端行列了,可以说自己是会crud的页面仔,但是我个人的观点来讲,你是不算是一个全栈或后端的,因为随便几个问题就可以打破你是后端的这个概念了。
- nginx 你很了解了吗?
- 微服务你知道是什么概念吗?
- docker你了解过吗?
- 你知道 Node 擅长处理密集型 IO(高并发),但是高并发下应该注意什么,没什么经验的你知道吗?即使是理论?
从自己瞎玩到企业级运用其实是有很大差距的,如果你也能用 koa 抽象出像 egg 这样的企业级框架,那就确实是很厉害了,你在使用框架时,你觉得你很厉害,但是其实服务集群的稳定性、健壮性都是框架帮你实现的而已,去除框架,自己本身剩下的东西才是最重要的,而 node 也只是帮我们打开了一扇新世界的大门,让你从前端的繁杂过渡到了深度的世界而已,so~
emmmm,其实我看书是算比较慢的,因为我经常一章自己可能需要看 2-3 遍,书中的代码我必然是看了后再自己写的,有时候觉得书中太复杂,可能会自己按照书中的思想去改造,结果改到后来发现还是书中的设计正确~~~然而写一篇总结博客,每章的内容可能需要看3-4遍才能保证全面且大部分正确,所以可能更新较慢~
世界很大,我们很小,保持谦虚,稳步前行才是关键
来源:CSDN
作者:yolo0927
链接:https://blog.csdn.net/yolo0927/article/details/81098567