Featured image of post 如何上线你的Koa2项目

如何上线你的Koa2项目

基于CentOS 8环境

基本的nodejs后端开发知识这里不多介绍,有时间了会单独写一个帖子

一个 Koa2 的后端起手式

就像前端开发一般会使用一个 CLI 生成一个脚手架一样,后端也不例外,但是事实上后端并没有什么特别流行的脚手架,都是按照自己的一个开发习惯和流程起一个空项目。比如我自己搭建的一个基于Koa2TypeScriptTypeORMPM2的起手式:Koa2-TypeScript-Template。这样在之后开新坑的时候直接Use This Template就可以了。大部分的逻辑都是可以复用的。

后端开发的时候要注意不能把敏感信息一起上传到 GitHub 上,例如数据库的用户名、密码,密钥等,所以需要使用本地的环境变量来存储,我这里使用了dotenv来加载.env文件中的环境变量:

1
2
3
// index.ts
import dotenv from "dotenv";
dotenv.config({ path: ".env" }); // 从根目录下的 .env 文件中加载环境变量

从模板生成新项目之后,只需要在项目根目录下新建.env文件,然后在里面写上自己的环境变量即可:

1
2
3
4
5
SECRET=secret
MYSQL_USERNAME=username
MYSQL_PASSWORD=123456
MYSQL_DATABASE=test
HOST=localhost

然后就可以在项目内使用process.env来读取文件中的敏感信息啦

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ormconfig.js
module.exports = {
  type: "mysql",
  host: `${process.env.NODE_ENV === "dev" ? "127.0.0.1" : process.env.HOST}`,
  port: 3306,
  username: process.env.MYSQL_USERNAME,
  password: process.env.MYSQL_PASSWORD,
  database: process.env.MYSQL_DATABASE,
  synchronize: process.env.NODE_ENV === "dev" ? true : false,
  logging: false,
  entities: [
    `${process.env.NODE_ENV === "dev" ? "src" : "dist"}/entity/*{.ts,.js}`,
  ],
};

注意:一定要在.gitignore文件里加入.env,起手式里已经加入

如何使用路径别名

这里涉及到了tsc的路径别名问题。我们在前端开发时,通常会对Webpack或者Vite等打包工具做路径别名的配置,例如:

1
2
3
4
5
6
7
8
9
// vite.config.ts
export default defineConfig({
  plugins: [vue(), WindiCSS()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
});

前端的脚手架已经为我们提供了别名功能,而如果结合了 ts,那么也只需要在tsconfig中配置paths即可,这样就可以在开发时使用@/xxx来缩短引入了。

但是在后端开发时,我们通常不会使用打包工具,而当我们使用了 ts,并且也想像前端那样可以使用路径别名进行开发的话,就有点问题了,因为tsconfig中配置了paths别名后,在tsc打包时并不会对路径进行转译,这就有点离谱了,也是tsc受人诟病的一点,尤其是你使用ts-node跑项目的时候,是可以使用路径别名的,而当你项目都写完了你才发现打包没用……这血压一下子就起来了。

那么这时候有几个解决办法:

  1. 人工重写(显然不现实)
  2. 使用 Webpack 等打包工具的路径别名功能,对项目进行重新打包,对路径进行转译
  3. 使用路径别名转译插件

我在项目中选用了第三种,引入了module-alias这个包,虽然不是很优美,但是解决了问题。

这个包可以在不修改你任何代码的情况下对模块引入进行路径别名配置,只需要在package.json中手动添加路径别名:

1
2
3
4
5
6
7
8
9
"_moduleAliases": {
    "@/Controller": "dist/Controller",
    "@/entity": "dist/entity",
    "@/MiddleWare": "dist/MiddleWare",
    "@/router": "dist/router",
    "@/Services": "dist/Services",
    "@/utils": "dist/utils",
    "@/types": "dist/types"
  }

然后在项目的index.ts的最开头加入这一行

1
if (process.env.NODE_ENV !== "dev") require("module-alias/register");

因为在开发环境下,我们是可以正常使用路径别名的,经过了转译反而会报错,因此加一个判断,只有在生产环境下我们才使用这个包。

这样一来,我们tsc编译后的文件就不会再报Can Not Found Module的错误了。

如何上线

后端项目不同于前端项目,前端项目在用户访问时,需要通过请求获取html页面和js以及css资源,因此需要最大程度地压缩文件体积,而且需要考虑用户环境的不同,需要做不同程度的适配,因此引入了babelwebpack等多种多样的插件。而后端运行在服务器上,生产环境可以与开发环境保持高度一致,比如我可以本地使用Linuxnode 16.8开发,我的服务器上也可以部署相同的环境,很大程度上减少了不兼容性。

而且,后端的代码要求具备足够的健壮性,以应对各种复杂环境。而使用 ts 开发的后端项目,虽然本地可以使用ts-node运行,但是在生产环境,我们肯定不能使用ts-node跑,因为即使ts-node可以直接运行 ts 文件,也会产生一定的性能损失。在nodejs本身就难以应对密集计算场景时,我们更加需要注意每一点性能提升。因此,常规的nodejs+ts项目,都需要将ts编译为js之后再运行。

那么我们的一个解决方案就是,使用tsc对项目进行编译,然后运行dist目录下的index.js入口文件开启服务。

这样的项目就已经可以跑在我们的服务器上了,将整个项目文件上传到服务器,并配置好服务器node环境的环境变量,以让我们可以在项目中使用process.env

然后在服务器上安装依赖:

1
yarn

安装完成之后运行即可:

1
node ./dist/index.js

负载均衡与进程管理

虽然nodejs对于前端开发来说确实比较好上手,但是既然是后端,我们就必须要做一些后端要做的事情,比如进程管理、进程监控、负载均衡等。

之前提到过,js 作为一门解释性的脚本语言,在遇到报错时会直接停止运行,所以一个方法是使用统一的try...catch...模块来统一处理错误,以增强代码的健壮性。但这只是代码层面的,服务端层面则需要考虑更多问题,比如文件变动,进程崩溃等。

在本地调试时,我们会使用nodemon对文件进行监听并自动重启服务,但是当服务结束时还是会被销毁,因此,我们需要使用pm2来对进程进行管理

pm2是基于nodejs的一个生产环境的进程管理工具,它不仅可以保证服务的持续运行以及自动重启,还可以提供负载均衡、进程管理、监控等功能。

pm2的功能远不止如此,它能够提供:

  1. 日志管理;两种日志,pm2 系统日志与管理的进程日志,默认会把进程的控制台输出记录到日志中;输入pm2 logs即可实时查看日志
  2. 负载均衡:PM2 可以通过创建共享同一服务器端口的多个子进程来扩展您的应用程序。这样做还允许以零秒停机时间重新启动应用程序。
  3. 终端监控:可以在终端中监控应用程序并检查应用程序运行状况(CPU 使用率,使用的内存,请求/分钟等)。
  4. SSH 部署:自动部署,避免逐个在所有服务器中进行 ssh。
  5. 静态服务:支持静态服务器功能
  6. 支持开发调试模式,非后台运行,pm2-dev start
  7. ……其他许多功能

目前我也在学习使用,很多功能并没有完全掌握,但是仅是已经用上的功能,也足以体现其强悍之处

根目录下的ecosystem.config.jspm2的配置文件,其中

1
2
3
{
  instances: require('os').cpus().length,
}

这一配置项会根据当前服务运行的主机环境,自动分配进程数量。其他配置项则可以根据自身需求自行更改。

生产环境下使用:

1
yarn pro

开启pm2服务,输入:

1
yarn stop

结束所有服务

服务开启后可以输入

1
pm2 logs

查看服务实时日志,以进行调试。

至此,完成在生产环境下nodejs后端项目的上线

Built with Hugo
主题 StackJimmy 设计