当前位置: 首页 > 后端技术 > Node.js

从零开始搭建Node.js企业Web服务器(十二):远程调用

时间:2023-04-04 01:17:39 Node.js

关于远程调用远程调用(简称RPC)主要是指服务器或集群之间处理的调用。通过远程调用,可以打通不同系统之间的数据和功能,或者提取和建立共同的逻辑,提供统一的服务。远程调用的两端也分别称为远程调用的客户端和服务端。一般是多对多的关系,需要引入注册和发现机制进行治理。下图是最常见的做法:治理机制通常因地制宜,可以基于ZooKeeper构建,本章不再展开。gRPCgPRC是Google开源的跨语言高性能RPC框架。底层使用protobuf进行数据交换。已广泛应用于谷歌、Netflix、思科等企业。下面是gRPC的调用过程。客户端先进行DNS解析,然后根据解析结果直接连接服务器或者从负载均衡/治理服务获取服务器信息再连接:本章节将基于完成的项目licg9999/nodejs-server-examples-11-schedule添加远程调用功能,实现简单消息的输入输出,现在进入项目根目录安装grpc相关模块:$yarnadd@grpc/grpc-js@grpc/proto-loader#本地安装@grpc/grpc-js,@grpc/proto-loader#...info直接依赖├─@grpc/grpc-js@1.1.3└─@grpc/proto-loader@0.5.5#...加上远程calls通过.proto文件定义远程接口,并根据定义编写gRPC客户端和服务端:$mkdirsrc/rpc#新建一个src/rpc来存放远程调用逻辑$mkdirsrc/rpc/echo#创建一个newsrc/rpc/echo$treesrc-L1#显示src目录内容结构src├──config├──controllers├──middlewares├──models├──molds├──rpc├──schedules├──server.js├──services└──utils//src/rpc/echo/def.protosyntax="proto3";serviceEcho{rpcGet(EchoRequest)returns(EchoResponse){}}messageEchoRequest{stringmessage=1;}消息EchoResponse{字符串消息=1;}//src/rpc/echo/client.jsconst{resolve}=require('path');const{promisify}=require('util');constprotoLoader=require('@grpc/proto-loader');constgrpc=require('@grpc/grpc-js');const{rpc}=require('../../config');classEchoClient{grpcClient;asyncinit(){constgrpcObject=grpc.loadPackageDefinition(awaitprotoLoader.load(resolve(__dirname,'def.proto')));this.grpcClient=newgrpcObject.Echo(`${rpc.domain}:${rpc.port}`,grpc.credentials.createInsecure());}get=async({s,logger})=>{const{grpcClient}=this;const{message}=awaitpromisify(grpcClient.get.bind(grpcClient,{message:s}))();logger.info('Echo/GetInvoked');返回{消息};};}letclient;module.exports=async()=>{if(!client){client=newEchoClient();等待客户端.init();}returnclient;};//src/rpc/echo/server.jsconst{resolve}=require('path');const{callbackify}=require('util');constprotoLoader=require('@grpc/proto-loader');constgrpc=require('@grpc/grpc-js');classEchoServer{grpcSer版;asyncinit(){constgrpcObject=grpc.loadPackageDefinition(awaitprotoLoader.load(resolve(__dirname,'def.proto')));this.grpcServer.addService(grpcObject.Echo.service,this);}get=callbackify(async(call)=>{const{message}=call.request;return{message};});}letserver;module.exports=async(grpcServer)=>{if(!server){服务器=新的EchoServer();Object.assign(server,{grpcServer});等待server.init();}returnserver;};//src/config/index.js//...constconfig={//默认配置default:{//...++rpc:{+domain:'localhost',+port:进程.env.PORT_RPC||9001,+},},//...};//...启动gRPC日志输出并开始化:#.envLOG_LEVEL='debug'++GRPC_TRACE='all'+GRPC_VERBOSITY='DEBUG'//src/utils/logger.js//...+constGRPC_LOGGER_REGEXP=/^.+Z\s+\|\s+/;++functiongrpcLogger(logger,level='debug'){+constverbosities=['debug','信息','错误'];++返回{+错误(服务器ity,message){+if(typeofseverity!='number'){+message=severity;+severity=0;+}++if(typeofmessage!='string'){+message=String(message||'');+}++记录器[详细程度[严重性]||级别](+message.replace(GRPC_LOGGER_REGEXP,'')+);+},+};+}+module.exports=logger;-Object.assign(module.exports,{logging});+Object.assign(module.exports,{logging,grpcLogger});//src/rpc/index.jsconst{promisify}=require('util');constgrpc=require('@grpc/grpc-js');const{rpc}=require('../config');constlogger=require('../utils/logger');constechoClient=require('./echo/client');constechoServer=require('./echo/server');const{grpcLogger}=logger;module.exports=asyncfunctioninitRpc(){grpc.setLogger(grpcLogger(logger.child({type:'rpc'}),'debug'));//初始化rpc服务器constgrpcServer=newgrpc.Server();等待回声服务器(grpcServer);等待承诺(grpcServer.bindAsync.bind(grpcServer))(`0.0.0.0:${rpc.port}`,grpc.ServerCredentials.createInsecure());grpcServer.start();//initrpcclientsawaitechoClient();};//src/server.jsconstexpress=require('express');const{resolve}=require('path');const{promisify}=require('util');constinitMiddlewares=require('./middlewares');constinitControllers=require('./controllers');constinitSchedules=require('./schedules');+constinitRpc=require('./rpc');constlogger=require('./utils/logger');constserver=express();constport=parseInt(process.env.PORT||'9000');constpublicDir=resolve('public');constmouldsDir=resolve('src/moulds');asyncfunctionbootstrap(){+awaitinitRpc();server.use(awaitinitMiddlewares());server.use(express.static(publicDir));server.use('/moulds',express.static(mouldsDir));server.use(awaitinitControllers());服务器。使用(错误处理程序);等待初始化计划();等待承诺(服务器.listen.bind(服务器,端口))();logger.info(`>Startedonport${port}`);}//...添加gRPC客户端logger与控制层入口://src/middlewares/trace.jsconst{v4:uuid}=require('uuid');constmorgan=require('morgan');constonFinished=require('on-finished');constlogger=require('../utils/logger');const{logging}=logger;module.exports=functiontraceMiddleware(){return[morgan('common',{skip:()=>true}),(req,res,next)=>{req.uuid=uuid();req.logger=logger.child({uuid:req.uuid});req.loggerSql=req.logger.child({type:'sql'});req.logging=logging(req.loggerSql,'info');+req.loggerRpc=req.logger.child({type:'rpc'});onFinished(res,()=>{//...});下一个();},];};//src/controllers/echo.jsconst{Router}=require('express');constcc=require('../utils/cc');constrpcEchoClient=require('../rpc/echo/client');classEchoController{rpcEchoClient;异步cinit(){this.rpcEchoClient=awaitrpcEchoClient();常量路由器=路由器();router.get('/',this.get);返回路由器;}get=cc(async(req,res)=>{const{s=''}=req.query;constmessage=awaitthis.rpcEchoClient.get({s,logger:req.loggerRpc});res.send({success:true,message});});}module.exports=async()=>{constc=newEchoController();returnawaitc.init();};//src/controllers/index.jsconst{Router}=require('express');constshopController=require('./shop');constchaosController=require('./混沌');consthealthController=require('./health');constloginController=require('./login');constcsrfController=require('./csrf');+constechoController=require('./echo');module.exports=asyncfunctioninitControllers(){constrouter=Router();router.use('/api/shop',awaitshopController());router.use('/api/chaos',awaitchaosController());router.use('/api/health',等待健康hController());router.use('/api/login',awaitloginController());router.use('/api/csrf',awaitcsrfController());+router.use('/api/echo',awaitechoController());returnrouter;};访问http://localhost:9000/api/echo?s=Hello%20RPC即可看到效果:同时在命令执行能够看到充分的gRPC日志:#...08:20:52.320Z调试12-rpc:dns_resolver|为目标dns:0.0.0.0:9001(type=rpc)08:20:52.321ZDEBUG12-rpc:dns_resolver|构造的解析器为目标dns:0.0.0.0:9001(type=rpc)08:20:52.321ZDEBUG12-rpc:dns_resolver|请求的分辨率更新返回目标dns:0.0.0.0:9001(type=rpc)08:20:52.322ZDEBUG12-rpc:server|的IP地址尝试绑定0.0.0.0:9001(type=rpc)08:20:52.324ZDEBUG12-rpc:server|成功绑定0.0.0.0:9001(type=rpc)08:20:52.327ZDEBUG12-rpc:resolving_load_balancer|dns:localhost:9001IDLE->IDLE(type=rpc)08:20:52.327ZDEBUG12-rpc:connectivity_state|dns:localhost:9001IDLE->IDLE(type=rpc)08:20:52.327ZDEBUG12-rpc:dns_解析器|Resolverconstructedfortargetdns:localhost:9001(type=rpc)#...本章的源代码licg9999/nodejs-server-examples-12-rpc阅读更多关于从头构建Node.js企业级Web服务器的信息(零):静态服务从头搭建一个Node.js企业级Web服务器(一):接口与分层从头搭建一个Node.js企业级Web服务器(二):验证搭建一个Node.js企业级Web服务器从零开始(三):中间件从零开始搭建一个Node.js企业Web服务器(四):异常处理从零开始搭建一个Node.js企业Web服务器(五):数据库访问从零搭建一个Node.js企业Web服务器Scratch(六):SessionfromScratch从零搭建Node.js企业Web服务器(七):认证登录从零搭建Node.js企业Web服务器(八):网络安全从零搭建Node.js企业Web服务器(九):从零开始搭建配置项Node.jsEnterpriseWebServer(十):从零开始搭建日志Node.jsEnterpriseWebServer(十一):定时任务从零开始搭建Node.jsEnterpriseWebServer(十二):远程调用BuildNodefromScratch.jsEnterpriseWebServer(十三):断点调试和性能分析从零开始构建Node.jsEnterpriseWebServer(十四):自动化测试从零开始构建Node.jsEnterpriseWebServer(十五):总结与展望