本文干货满满,介绍了用BFF层(Back-end For Front-end)中间层提升性能的整体解决方案和思路,涉及前期技术调研,聚合业务分析,聚合方法,验收,最后向同学们普及node、koa基础知识,以及如何通过中间层做优化等一系列的内容,形成 发起问题——解决问题——复盘学习推广 的完整闭环。
业务介绍
业务背景
我们负责的系统,既有内部各细分领域的功能分支,也有大量的功能交集,展示和交互要求极高。
- 后端微服务化,使得数据更加碎片化,请求数量增加,创建更多连接,且数据包大,传输耗费带宽。
- 微前端技术的应用,使前端文件也变得碎片化。
最终导致:
- 页面有效内容的展示发生延迟。
- 并且功能交互产生卡顿。
通过以下截图,可以看到,一个页面的请求数可轻松超过100个,其中接口类的请求有30个。
中间层目标
减少请求数量,以及缩小数据包体积,可以有效提升用户体验。
想要减少请求,可以建设BFF层。
通过建设BFF层,提升用户体验:
- 提升加载性能,更快地呈现核心内容。
- 提升交互的流畅度,更快地响应用户操作。
验收目标:聚合后,页面性能,TTI提升10%
中间层在架构中的位置:(server层—web server层)
我在此次迭代中做的工作:
- node,koa技术调研
- 用koa框架搭建web server中间层
- 系统需要聚合业务和接口调研,以及文档整理
- 出具所有聚合接口方案
- 分配到人进行接口聚合
- 配合后端和运维,域名转发处理
- 性能监控,对比,与测试配合验收
聚合接口注意事项:
接口聚合在开发时要注意的原则:
a. 尽量聚合同一视图层级的请求
b. 在逻辑有相关性,参数相同的接口,比较适合做聚合
c. 不要把过慢的接口和其他正常接口放在一起,会影响整体性能
最终效果,通过我们的努力,BFF层聚合接口以后,请求时间和接口数降低,达到了预期的目标。
原理是,node采用事件驱动、异步编程,为网络服务而设计。Node.js非阻塞模式的IO处理给Node.js带来在相对低系统资源耗用下的高性能与出众的负载能力,非常适合用作依赖其它IO资源的中间层服务。用户,特别是电脑配置特别低的用户,通过这波优化,他们体验会更好(cpu资源降低,请求时长缩小),用服务端资源换用户的时间和空间,最终提升了用户体验。
在此次项目完成以后,进行了前端分享,本次分享的目的:
- 让大家对node不再陌生,了解bff层构成
- 能上手利用node,koa参与一些性能优化,
- 提升个人的技术能力和技术视野
nodeJS介绍
前言
Node.js 是 JavaScript 后端开发语言。从诞生之初就备受关注,到如今说到最火的后端 Web 开发,Node 说自己是第二,没有人敢说他是第一。
前端有必要进行技术赋能,使用node对业务和团队进行提升,包括以下几个方面的提升:
1、 一切为了用户——提升用户体验
2、职责明确——提高开发团队生产力
3、团队赋能——提高前端团队的想象和成长空间
以下内容,是对node介绍。
一、概念
Node.js 是一个开源与跨平台的 JavaScript 运行时环境。
在浏览器外运行 V8 JavaScript 引擎(Google Chrome 的内核),利用事件驱动、非阻塞和异步输入输出模型等技术提高性能。
可以理解为 Node.js 就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript运行环境。
非阻塞异步
Nodejs采用了非阻塞型I/O机制(基本原理结合服务器演进史,详细看思维导图),在做I/O操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作。
例如在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。
事件驱动
事件驱动就是当进来一个新的请求的时,请求将会被压入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数。
比如读取一个文件,文件读取完毕后,就会触发对应的状态,然后通过对应的回调函数来进行处理。
能做什么?
1、提供数据给浏览器展示
2、保存用户提交过来的数据
3、数据统计与分析
服务器Node.js和浏览器js的区别是什么?
1、node.js是平台,JavaScript是编程语言;
2、javascript是客户端编程语言,需要浏览器的javascript解释器进行解释执行;
3、node.js是一个基于Chrome JavaScript运行时建立的平台,它是对Google V8引擎进行了封装的运行环境;
4、node.js就是把浏览器的解释器封装起来作为服务器运行平台,用类似javascript的结构语法进行编程,在node.js上运行。
二、优缺点
优点:
1、采用事件驱动、异步编程,为网络服务而设计。其实Javascript的匿名函数和闭包特性非常适合事件驱动、异步编程。而且JavaScript也简单易学,很多前端设计人员可以很快上手做后端设计。
2、Node.js非阻塞模式的IO处理给Node.js带来在相对低系统资源耗用下的高性能与出众的负载能力,非常适合用作依赖其它IO资源的中间层服务。
3、Node.js轻量高效,可以认为是数据密集型分布式部署环境下的实时应用系统的完美解决方案。Node非常适合如下情况:在响应客户端之前,您预计可能有很高的流量,但所需的服务器端逻辑和处理不一定很多。
4、前后端语言互通。
缺点:
因为Nodejs是单线程,带来的缺点有:
-
不适合CPU密集型应用
-
只支持单核CPU,不能充分利用CPU
-
可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
三、应用场景
借助Nodejs的特点和弊端,其应用场景分类如下:
-
善于I/O,不善于计算。因为Nodejs是一个单线程,如果计算(同步)太多,则会阻塞这个线程
-
大量并发的I/O,应用程序内部并不需要进行非常复杂的处理
-
与 websocket 配合,开发长连接的实时交互应用程序
具体场景可以表现为如下:
-
第一大类:用户表单收集系统、后台管理系统、实时交互系统、考试系统、联网软件、高并发量的web应用程序
-
第二大类:基于web、canvas等多人联网游戏
-
第三大类:基于web的多人实时聊天客户端、聊天室、图文直播
-
第五大类:操作数据库、为前端和移动端提供基于json的API
其实,Nodejs能实现几乎一切的应用,只考虑适不适合使用它。
以上。
四、延伸
以上内容,仅仅是node.js的一点皮毛,下面推荐部分内容,有兴趣的同学可以进行深入了解和探究实践。
KOA介绍
Koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。
Koa 是一种简单好用的 Web 框架,node可以在ssr服务端渲染,bff层,接口聚合,削减api,或处理api数据等方面应用,减小前端代码复杂度,为企业节省成本,让吞吐率更高。
二、KOA代码结构
koa 非常小巧,总共就 4 个文件,每个文件的功能也十分单一,文件名也清楚的反应了文件功能。
├── application.js
├── context.js
├── request.js
└── response.js
复制代码
-
request.js
主要针对 http 的 request 对象提供了改对象的大量的 get 方法,文件主要是用来获取 request 对象属性。
-
response.js
主要针对 http 的 response 对象提供了该对象的大量 set 方法;该文件主要是用来设置 response 对象属性。
-
context.js
koa 引入了上下文对象的概念,即 ctx,这里所谓的上下文对象实际上是 request 和 response 两个对象的并集,request 和 response 分别通过代理的形式,将自己的方法委托给 ctx。那样我们就可以用 ctx 同时操作两个对象,来简化操作。
-
application.js
该文件是整个 koa 的核心,简单来说主要有两大功能: 挂载真实请求到 ctx 下,封装中间件的执行顺序
三、KOA和express的区别
于是二者的使用区别通过表格展示如下:
|
koa(Router = require('koa-router'))
|
express(假设不使用app.get之类的方法)
|
---|---|---|
|
koa(Router = require('koa-router'))
|
express(假设不使用app.get之类的方法)
|
初始化 | const app = new koa() | const app = express() |
实例化路由 | const router = Router() | const router = express.Router() |
app级别的中间件 | app.use | app.use |
路由级别的中间件 | router.get | router.get |
路由中间件挂载 | app.use(router.routes()) | app.use('/', router) |
监听端口 | app.listen(3000) | app.listen(3000) |
上表展示了二者的使用区别,从初始化就看出koa语法都是用的新标准。在挂载路由中间件上也有一定的差异性,这是因为二者内部实现机制的不同。其他都是大同小异的了。
与 express,hapi,eggjs 比起来,koa 真的十分小巧,以至于不能称作一种框架,可以看做一种库,但这并不妨碍 koa 生态的发展。
express 当初也是大而全的框架,慢慢的把各种功能已中间件的形式抽离出来,koa 可以看做这种思想的一种实现。大而全的框架主要存在起初的学习成本高,功能冗余等问题,使用 koa 对于初次使用 nodejs 开发 web 的人员非常友好,对于初学者来说,建议从 koa 入手,使用不同的中间件来实现不同的功能,对于了解 web 开发有很大帮助。
四、中间件
1、什么是中间件?
1.执行任何代码
2.修改请求和和响应对象
3.终结请求-响应循环
4.调用堆栈中的下一个中间件
通过next来实现
在express 中间件(Middleware) 是一个函数,它可以访问请求对象(request object(req)),响应对象(response object()res)和web应用中处理请求-相应循环流程中的中间件,一般被命名为next的变量。在Koa中中间件和express有点类似。
经典的洋葱图概念能很好的解释next的执行,请求从最外层进去,又从最里层出来。
中间件的功能包括:
-
执行任何代码
-
修改请求和响应请求对象
-
终结请求-响应循环
-
调用堆栈中的下一个中间件
如果get、post回调函数中,没有next参数,那么就匹配上第一个路由,就不会往下匹配了。如果想往下匹配的话,那么就需要写next()。
app.use('/',function(){});
Koa应用可以使用如下几种中间件:
-
应用级中间件
-
路由级中间件
-
错误处理中间件
-
第三方中间件
可以写两个参数,第一个是匹配的路径,第二个是回调函数,第一个参数可以省略
五、实践
1、安装
检查node版本
$ node -v v14.15.1
Koa 必须使用 7.6 以上的版本。如果你的版本低于这个要求,就要先升级 Node。
你可以使用自己喜欢的版本管理器快速安装支持的 node 版本:
$ nvm install 7
$ npm i koa
$ node my-koa-app.js
2、架设HTTP服务
const Koa = require('koa'); const app = new Koa(); // 本地服务 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
显示Not Found,因为我们没有给内容,所以显示这个。
3、Context 对象
Koa 提供一个 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)。
demo如下:
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.body = 'Hello 星火组'; }; app.use(main); // 本地服务 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
HTTP Response类型:
const Koa = require('koa'); const app = new Koa(); const fs = require('fs'); // const main = ctx => { // ctx.body = 'Hello 星火组'; // }; //HTTP Response 的类型 // const main = ctx => { // if (ctx.request.accepts('xml')) { // ctx.response.type = 'xml'; // ctx.response.body = '<data>Hello World</data>'; // } else if (ctx.request.accepts('json')) { // ctx.response.type = 'json'; // ctx.response.body = { data: 'Hello World' }; // } else if (ctx.request.accepts('html')) { // ctx.response.type = 'html'; // ctx.response.body = '<p>Hello World</p>'; // } else { // ctx.response.type = 'text'; // ctx.response.body = 'Hello World'; // } // }; const main = ctx => { ctx.response.type = 'html'; ctx.response.body = fs.createReadStream('template.html'); }; app.use(main); // 本地服务 app.listen(3000, ()=>{ console.log('http://localhost:3000') })
node 项目实操查看ctx返回
https://koajs.com/#context。官方文档介绍。
4、router 路由
上面代码中,根路径/
的处理函数是main
,/about
路径的处理函数是about
。
const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const about = ctx => { ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>'; }; const main = ctx => { ctx.response.body = 'Hello World'; }; app.use(route.get('/', main)); app.use(route.get('/about', about)); app.listen(3000, ()=>{ console.log('http://localhost:3000')
})
5、中间件
// 引入Koa模块 const Koa = require('koa'); // 引入Koa-router const Router = require('koa-router'); // 实例化Koa模块 const app = new Koa(); // 实例化路由模块 const router = new Router(); // Koa 中间件 // app.use('/',function(){}); //可以写两个参数,第一个是匹配的路径,第二个是回调函数,第一个参数可以省略 // 匹配任何路由之前打印日期 app.use(async (ctx,next)=>{ console.log(new Date()); await next(); //当前路由匹配完成以后继续向下匹配 }); // 配置路由 // ctx 上下文 context, 包含了request和response等信息 router.get('/',async (ctx)=>{ ctx.body = '网站首页'; //返回数据 相当于:原生里面的res.writeHead() res.end() }); // 路由级中间件 // 匹配带news路由以后继续向下匹配路由 router.get('/news',async (ctx,next)=>{ console.log('这是一个新闻路由'); await next(); }); router.get('/news',async (ctx)=>{ ctx.body = '新闻列表页面'; }); router.get('/login',async (ctx)=>{ ctx.body = '网站登录页面'; }); // 启动路由 app .use(router.routes()) /*启动路由*/ .use(router.allowedMethods()); //作用:当请求出错时处理逻辑 /* * router.allowedMethods()作用: 这是官方文档的推荐用法,我们可以 * 看到 router.allowedMethods()用在了路由匹配 router.routes()之后,所以在当所有 * 路由中间件最后调用.此时根据 ctx.status 设置 response 响应头 * */ // 监听3000端口 app.listen(3000,()=>{ console.log('starting at port 3000'); });
6、获取请求数据
const Koa = require('koa') const app = new Koa() const Router = require('koa-router') const router = new Router() router.get('/data', async (ctx , next)=> { let url = ctx.url // 从ctx的request中拿到我们想要的数据 let data = ctx.request.query let dataQueryString = ctx.request.querystring ctx.body = { url, data, dataQueryString } }) app.use(router.routes()) app.listen(3333, ()=>{ console.log('server is running at http://localhost:3333') })
在浏览器里输入http://localhost:3333/data?user=wuyanzu&id=123456 ,可以看到运行结果:
可以看到区别,.query
返回的结果是对象,而.querystring
返回的是字符串,这个很好理解。(chrome插件显示成json格式)
如果遵从 RESTful 规范,比如请求要以 '/user/:id'的方式发出的话,我们可以用下面的例子来获取到想要的数据。
router.get('/data/:id', async (ctx, next) => { // 也从ctx中拿到我们想要的数据,不过使用的是params对象 let data = ctx.params ctx.body = data })
以上,我们通过上面的代码和描述,已经对koa及node有一个初步的印象和概念。
课后作业:
用koa框架实现一个web页面。
进阶:本地json存储,实现增删改查。
参考:
【完】