前端网关实践

原文已发布在玩物得志技术公众号,地址:https://mp.weixin.qq.com/s/ra4F659RoY8eQcfMUvMPgw

从一个房间走到另一个房间,要经过一扇门。从一个网络向另一个网络发送消息,也需要经过一道“关口”,这道关口就是网关。

1.前言

公司在快速发展,为了更好的服务业务,前端团队在性能体验、工程化建设等方面也有了更高的追求。

NodeJs提供的服务端能力,不仅拓展了前端的能力边界,也为性能体验优化和工程化提供了更丰富的方案。

2.项目介绍

2.1 技术选型

| 框架 | 描述 |
| express | 早期的node框架,功能齐全,开箱即用。但基本停止更新,与koa最大的区别是中间件机制,虽然能实现类似koa的洋葱形调用,但是实际上依旧存在差别。 |
| koa | 没有内置任何多余的中间件,按需引入,简洁。洋葱形中间件机制,灵活。 |
| egg | 基于koa, 高度封装,功能齐全。 |

Java的SpringCloud Gateway、 和基于nginx+lua的Kong都是成熟的网关项目,但是和前端当前的技术栈不匹配。

自研一套小型网关,并根据实际需求去慢慢完善,是我们选择的方案。

总体上,我们希望网关的底层框架尽可能简洁、灵活,同时拥有高扩展性,所以最终选择了koa。

2.2 架构

前端网关内部20210219.png

2.3 执行流程

网关内部流程.png

上图是网关执行时内部的大致流转过程,请求经过多层中间件处理,

可以看到一个中间件的处理逻辑可以在请求和响应阶段发挥作用,这得益于Koa中间件的洋葱形机制 如下图:

image

下方演示了Koa中间件的写法,只需几行代码,实现一个记录访问信息和请求耗时的中间件:

const accessLog = async (ctx, next) => {

const start = Date.now();

await next();

const rt = Date.now() - start;

logger.info(`${ctx.method} ${ctx.host} ${ctx.url} - ${rt}ms`);

};

module.exports = accessLog;

请求经过中间件处理后到达服务层,服务层处理完成后返回结果,再经过中间件处理后响应。

2.4 性能&稳定

image

如上图所示,Node是**单进程 **+ **异步I/O **+ 事件循环模型,这种模型比较适合处理高并发和密集I/O的场景,不过也有一些缺陷,比如不能充分使用cpu,出现异常时整个应用崩溃等。针对这些情况,需要做一些优化:

多进程:Node本身提供了多进程方案,使用Cluster模块,只需要少量修改就能实现实现多进程,示例:

const cluster = require('cluster');

const http = require('http');

const numCPUs = require('os').cpus().length;

// 主进程逻辑

if (cluster.isMaster) {

 // 创建工作进程

 for (let i = 0; i < numCPUs; i++) {

   cluster.fork();

}

 cluster.on('exit', (worker, code, signal) => {

   // 工作进程退出后的逻辑

});

} else {

 http.createServer((req, res) => {

   res.writeHead(200);

   res.end('hello world!');

}).listen(8000);

}

有一个问题:示例里多个子进程都监听了8000端口,程序却不会报错,是因为listen方法内部判断了进程是主进程还是子进程,最终只有主进程监听了端口。最后通过进程间通信和任务调度算法来实现多进程的工作模式。

如果使用PM2来启动Node服务,无需修改任何代码也能实现多进程。 示例:

$ pm2 start app.js -i max

(max表示CPU数,如果服务器的资源可以都用来跑对应的Node服务,那么让进程数=CPU核心数算是最佳实践)

数据缓存:对外部获取的数据进行缓存,减少不必要的请求能明显提升性能。Redis是一个高性能的缓存服务,将数据缓存到Redis,不仅可以减少同进程的请求次数,也可以在多个进程间共享数据。

进程守护:默认情况下,node是没有进程守护功能的为了防止应用崩溃导致整个服务宕机,需要增加进程守护机制。

常用的两种方案:

1. 常驻进程作为守护进程

** 2. ****crontab + shell 定时检测**

进程守护的基本逻辑,都是运行程序检测应用是否正常运行,检测出异常后进行日志记录,发送警报,重启应用等等操作。

如果使用PM2来管理Node服务,无需额外操作,因为PM2自带了守护进程。

错误日志:记录运行中产生的错误日志并分析原因,能帮助我们进一步优化应用的稳定性。

3.应用场景

前端网关20210208.png

3.1 文档请求接管&动态化

区别于常见的API网关,接管来自用户的文档请求也是前端网关的应用场景。

当前前端一些项目已经改造成了微前端的形式,多个项目可以独立开发和部署,同时跨项目访问是在一个SPA应用内的路由切换。但是也带来了一定的性能损失,比如项目初始化时需要多2次串行的请求。

这种情况下,在请求经过网关时,通过微前端服务获取内容,并预注入到文档内,在初始化过程中就不需要额外发起请求,节省了2次网络请求的时间。

如下图所示:

微前端逻辑.png

通过模板服务数据预取服务、微前端服务,注入接口数据、骨架、脚本等内容,使输出的html动态化,可以有效提升首屏的访问体验。

这种方式看上去与 SSR有一些相似。相比而言,服务端预注入数据,渲染由客户端完成的方式具有更强的通用性和更低的接入成本。

主要体现在:

** 1. 业务代码无侵入** 无需大规模修改代码做适配,也不会影响原有的开发习惯。

** 2. 无框架兼容问题**无论开发框架是Vue、React, 或是自研的H5 SPA应用均可使用同一套后端逻辑。

3.2 离线文件实时更新

在App内缓存H5项目的文件,目前常见的方式有:

** 1. App打包时包含h5项目文件 **离线文件更新受发版限制,不够灵活。

** 2. 文件通过接口获取 **接口通常由后端同学提供,难以和前端工程体系打通,导致文件需要定期手动更新。

将前端发布系统与网关打通后通过离线缓存服务实时生成最新的离线文件列表并返回给客户端,实现客户端缓存文件的实时更新。

如下图所示:

离线缓存逻辑.png

4.总结&展望

当前,玩物得志h5商城的流量已全部由前端网关处理。随着业务的不断发展,后续会新增更多的Node服务和网关功能。目前网关的功能还比较简单,内集的一些服务也未做拆分,未引入RPC,我们也将持续完善网关,建设更完整健全的Node服务化体系。

本文主要分享了基于node的网关设计思路和一些实践经验。

因篇幅有限,对文中出现的一些概念未做进一步解释(如异步I/O,事件循环),对于一些值得探讨的细节(如Cluster原理、多进程任务调度、父子进程通信等等)也未做深入,有兴趣的同学请自行查阅相关资料

你可能感兴趣的:(前端网关实践)