当前位置: 首页 > 科技观察

浅谈Nodejs中间层

时间:2023-03-19 11:17:50 科技观察

前言nodejs的出现给前端行业带来了无限可能,很多原本只负责客户端开发的同学也逐渐开始接触和使用服务端技术。虽然nodejs带来了很多好处,但是它也有自己的局限性。与那些传统的老牌编程语言如JAVA相比,PHP.nodejs无法成为它们的替代品,在可预见的未来,这些老牌编程语言的地位也难以撼动。目前nodejs主要有以下几种应用场景。前端工程化,如rollup,webpack在工程化nodejs中层客户端集成的方向探索nodejs,如electron市面上一些不太复杂的应用选择nodejs作为后端编程语言本文主要讲一讲关于nodejs作为中间层的一些做法,见下图。在传统的开发模式中,浏览器直接与服务器层通信。增加中间层意味着在浏览器层和服务器层之间增加了一个额外的层。原始客户端客户端直接向服务器发送请求,服务器层接收请求,计算处理后返回结果给浏览器。现在浏览器向节点层发送请求,节点层经过一轮处理后向服务器层发起请求。服务器层处理完成后,将响应结果返回给节点层,节点层最终将数据返回给浏览器。因为node层的出现,server层可以只关注业务本身,而不管前端对字段的特殊要求。节点层可以从服务器层获取数据,然后通过计算和集成将数据转换成满足前端UI要求的数据格式。另外,如果整个应用采用微服务架构,那么服务器层会有很多服务器管理各个业务模块。该层很好地适应了微服务架构。可以向多个服务器发起请求,获取不同模块的数据,然后进行整合转换,发送给前端。下面将重点介绍nodejs作为中间层的一些实践。代理转发agentforwarding在实践中有很多广泛的应用。浏览器首先向节点服务器发送请求。节点服务器收到请求后,可以对请求做一些处理,比如改变原来的路径,改变请求头中的信息,然后修改最终请求发送到远程真实服务器。远程服务器计算响应结果返回给节点服务器。节点服务器仍然可以选择性地处理响应,然后将其返回给浏览器。代理转发可以解决前端日常开发中经常遇到的问题。此外,它还屏蔽了远程真实服务器的细节,使浏览器只与节点服务器通信。下面是一个简单的练习。constexpress=require('express');const{createProxyMiddleware}=require('http-proxy-middleware');constapp=express();//创建应用app.use("/api",createProxyMiddleware(//设置代理转发{target:'http://www.xxx.com',//随便写一个地址的例子changeOrigin:true,pathRewrite:function(path){returnpath.replace('/api','/server/api');}}));app.use("*",(req,res)=>{//所有不以'/api'开头的路由返回"helloworld"res.send("helloworld");})app.listen(3000);http-proxy-middleware是第三方依赖包,设置代理转发非常方便,需要通过npm安装。如果当前访问路径以/api开头,那么请求就会被http-proxy-middleware拦截。观察http-proxy-middleware中配置的参数。target表示远程真实服务器的地址。changeOrigin设置为true,表示请求将被转发到目标地址。pathRewrite是对请求路径进行处理,将/api转换为/server/api。上述案例的意义显而易见。如果当前浏览器访问http://localhost:3000/api/list。因为这个路径以/api开头,所以会被拦截,从而触发pathRewrite函数修改访问路径。最后,访问路径变成http://www.xxx.com/server/api/list,然后会向这个路径发起请求,响应会返回给浏览器。上面介绍的接口聚合中的接口转发在实践中非常有用。它很少单独使用。如果只是为了转发数据,还不如直接用nginx配置,转发就搞定了。如果同时需要接口聚合和接口转发,那么还是优先从代码层面解决。接口聚合是什么意思?假设公司有两个销售系统,一个一是线上电商平台销售,二是线下实体店。它们由不同的团队运营,维护着不同的数据系统。如果当前请求只是查询电商平台某商品的信息,则只需将接口转发给电商平台系统即可。同样,如果只想查询某一天线下实体店的销售业绩,可以直接将请求转发到线下数据系统进行查询,然后返回响应数据。上面介绍的http-proxy-middleware插件支持配置多个代理路径,具体可以查询文档。现在有这样一个需求,目标是查询某商品本周线上线下销售数据对比。那么此时,节点层需要向两个远程服务器发送请求,分别获取线上销售数据和线下销售数据,然后将这两部分数据聚合返回给前端。简单的做法如下。constexpress=require('express');const{createProxyMiddleware}=require('http-proxy-middleware');constapp=express();//创建应用//伪代码app.get("/getSaleInfo",async(req,res)=>{constonline_data=awaitgetOnline();//获取在线数据constoffline_data=awaitgetOffline();//获取离线数据res.send(dataHanlder(online_data,offline_data));//返回数据到前端processing})proxyHanlder(app);//伪代码,proxy转发逻辑封装app.use("*",(req,res)=>{res.send("helloworld");})app.listen(3000);/getSaleInfo代表两个数据自聚合定义路由,如果对聚合数据的需求比较多,这个逻辑应该封装到路由模块中单独管理,写在代理转发的前面。这样可以保证需要转发的接口交给转发逻辑。个性化数据处理的接口是单独编写路由操作数据。数据缓存在提高系统性能和减轻数据库压力方面起着微不足道的作用。常用的缓存软件是redis,可以理解为数据存储在内存中的数据库。因为数据存放在内存中,所以读写速度非常快,可以做到极快响应用户请求。在节点层部署redis来管理缓存数据,可以提升整体应用性能。但是并不是所有的数据都推荐存储在redis中,只有那些不经常变化的数据才应该设置为缓存。比如商品信息数据,浏览服务器发起商品请求,想查看商品详情。请求第一次到达node层,此时redis是空的。然后节点开始请求server层获取响应结果,然后将响应结果返回给浏览器之前,以请求的访问路径作为key值,将响应结果作为value存入redis.这样,后面再发送同样的请求时,先检查redis是否缓存了请求的数据。如果有缓存,直接把数据存回,如果没有缓存,再去server层,再走一遍上面的流程。Redis还可以设置过期时间和清除缓存数据,可以根据具体业务进行操作。简单的做法如下。constexpress=require('快递');constapp=express();//创建应用程序//伪代码app.use("*",(req,res,next)=>{constpath=req.originalUrl;//获取访问路径if(redisClient.getItem(path)){//检查redis中是否有缓存该接口的数据res.send(redisClient.getItem(path));//返回缓存数据}else{next();//不做任何操作,直接release}})aggregate(app);//伪代码,封装接口聚合的逻辑proxyHanlder(app);//伪代码,封装代理转发的逻辑app.use("*",(req,res)=>{res.send("helloworld");})app.listen(3000);接口限流节点作为中间层,可以限制前端的无限制访问。比如一些恶意脚本在界面中循环,一秒内访问服务器的负载增加了几十倍。Redis可以帮助我们实现这个功能。用户第一次访问,解析出本次请求的ip地址,将ip作为key值,并将value设置为0保存到redis中。用户第二次访问,取出ip在redis中找到对应的值,然后加1,如果同一个人重复大量访问,值会在短时间内增长到一个很大的数,我们每次都能得到这个判断次数是否超过设定的预期标准,超过则拒绝请求。简单的做法如下。constexpress=require('express');constapp=express();//创建应用程序//伪代码app.use("*",(req,res,next)=>{constip=req.ip;letnum=0;if(redisClient.getItem(ip)){//当前ip字段是否缓存num=redisClient.incr(ip);//每访问一次,计数加1}else{redisClient.setItem(ip,0);redisClient.setExpireTime(5);//设置过期时间为5秒,5秒后ip为空}if(num>20){res.send("非法访问");}else{next();//release}})cacheData(app)//伪代码。Cacheinterfacedataaggregate(app);//伪代码,封装接口聚合的逻辑proxyHanlder(app);//伪代码,封装proxy转发的逻辑app.use("*",(req,res)=>{res.send("helloworld");})app.listen(3000);在应用程序前面设置一层限流中间件,在每次访问前判断终端是否已经缓存。第一次访问肯定是没有缓存的,所以把当前ip对应的值设置为0,加上5秒的过期时间。下次同一用户再次访问时,该值会加1。最终效果是,如果5秒内调用该接口的次数超过20次,则拒绝访问。日志操作系统没有日志就相当于没有眼睛的人。日志可以帮助我们发现和分析线上系统的错误。此外,还可以通过日志数据进行统计计算,得出一定的结论和趋势。节点层可以承担管理日志的功能,以接口访问日志为例。在系统中创建一个新的日志文件夹。每次有接入请求,首先分析请求的路径、当前接入时间、携带的参数和终端数据信息。然后在log文件夹下创建一个txt文件存放当天的日志情况,将上面的数据和请求的响应结果组合成一条记录插入到txt文件中。下次访问继续按照上述流程将访问日志添加到txt文件中。和上面介绍的代理转发一样,插件http-proxy-middleware支持配置如何返回响应结果,那么在对应的事件函数hook中就可以同时获取到请求和响应,并且有了这两条数据,就可以存入日志了。许多配置策略可以在这里制定。可以选择每天一篇日志文本,如果访问量很大,也可以选择每小时一篇日志文本,根据实际情况而定。另外,随着时间的推移,日志文件夹的内容会越来越多。这就需要为linux操作系统写一个定时任务来迁移备份这些日志数据。log操作的简单做法如下//伪代码app.use("/getList",async(req,res)=>{constlist=awaitgetProductList();//获取商品数据const{access时间,accesspath,parameter}=req;logger.log('info',`${accesstime}-${accesspathandparameter}:${list}`);//将数据存入日志文件res.send(list);//将结果返回给客户端})最后,中间层还可以做很多其他的事情,比如监控、认证和服务端渲染(SSR)。这部分内容较多,可单独成章。网上也有很多实用的文章,可以搜索一下,参考学习。事实上,上面提到的所有功能都可以通过其他编程语言来实现,这也成为了很多人质疑是否有必要在架构中额外增加一层的关注点。加入nodejs中间层对于前端同学来说绝对是个福音。因为它可以让前端承担更多的工作任务,增加前端业务的比重。另外,后端以后只需要专注于自己的业务,前端继续做自己擅长的事情,可以从整体上提高开发效率。但从宏观上看,在架构上多加一层,势必会造成整个应用性能的损失。此外,还会增加部署和测试层面的运维成本。目前End-side分离已经成为主流的开发模式。很多类型的应用都需要seo的支持和首屏的加载速度,所以服务端渲染是必不可少的。目前大多数前端项目都是使用React或Vue框架开发的。如果用nodejs来承接服务端渲染任务的话,可以保证一套代码既可以做客户端渲染也可以做服务端渲染,而这些任务可以由前端独立完成程序员。服务端渲染技术非常重要,后面会单独讲解。综上所述,nodejs作为中间层最有价值的功能就是服务端渲染和接口数据聚合。如果企业应用数量少,业务简单,规模不大,不建议加中间层,会把简单的事情搞复杂。