小姐姐暴富 發表於 2021-9-29 16:35:00

好未来数据中台 Node.js BFF实践(一):基础篇

<blockquote>
<p>好未来数据中台 Node.js BFF实践系列文章列表:</p>
<ul>
<li><strong>基础篇</strong></li>
<li>实战篇(TODO)</li>
<li>进阶篇(TODO)</li>
</ul>
</blockquote>
<p>好未来数据中台的Node.js中间层从7月份开始讨论可行性,截止到9月已经支持了4个平台,其中3个平台生产环境稳定,另1个在测试阶段近期上线。</p>
<p>我4月份刚加入数据中台,原本的想法是半年内不做大刀阔斧的改变,优先完善团队现有的基建设施,比如组件库、charts库、工具、规范等。Node.js中间层的立项完全是一个意外。</p>
<p>某次中台周例会上讨论到前后端协作效率问题,我一时嘴贱提到Node.js中间层的想法,将应用平台的一些<strong>与DB无关的逻辑</strong>放到Node层让前端伙伴们负责,让后端伙伴集中精力做底层服务建设。</p>
<p>本来就随口一提,结果老板当真了,当场拍板:搞!我当时的表情大概是...</p>
<p><img src="https://img2020.cnblogs.com/blog/595796/202109/595796-20210929163257572-1761556753.png" alt="" loading="lazy"></p>
<p>没办法,自己画的饼,哭着也要吃完。</p>
<blockquote>
<p>咦?怎么有点往日重现的感觉?曾经在腾讯云,刚给客户吹完牛逼就想抽自己大嘴巴~</p>
</blockquote>
<h2 id="nodejs-的定位">Node.js 的定位</h2>
<p>数据中台 Node.js 中间层的定位类似一层 <em>API Gateway</em>,承载<strong>接口代理、聚合以及与DB 无关的部分业务逻辑</strong>。</p>
<p>在现阶段数据中台的服务体系中有两类服务:常规 Java 后端和 T-Service 。后者是数据中台将 <em>OneService</em> 方法论落地的统一数据服务,即服务于各个前台事业部,也为数据中台内部的各个应用平台提供数据服务。</p>
<p>使用 T-Service 的协作流程简单描述就是<strong>数仓</strong>伙伴建表后将<strong>数据源接入 T-Service</strong>,然后 Java <strong>后端</strong>伙伴<strong>配置取数 SQL</strong>,最后<strong>前端</strong>从统一的 <strong>query 接口查数展示</strong>。</p>
<p>T-Service 不直接对接前端,旧架构体系下需要在前端与 T-Service 之间搭建一层 Java 服务,说白了就是一堆 <em>Controller</em>,从 T-Service 取数后做一些很简单的二次加工给到前端。Node.js 中间层的目标之一就是将这些复用性很差的 <em>Controller</em> 拿过来,好处有<strong>两点</strong>:</p>
<ol>
<li>
<p>旧架构体系下完成一个数据查询功能需要牵涉数仓、后端和前端三方,新架构下只需要前端和数仓;</p>
<blockquote>
<p>前提是前端伙伴需要掌握 SQL,前期由后端同事辅助,过渡后需要前端伙伴自己写 SQL。</p>
</blockquote>
</li>
<li>
<p>基于第一条, Java 后端伙伴的生产力被解放,集中精力做底层建设或通用性更强的接口。</p>
</li>
</ol>
<p>除了以上两条以外还有另一个隐藏优势,前端的能力边界扩宽后技术视野也会更宽阔,能够<strong>从纵向角度上全面思考业务</strong>。</p>
<p><img src="https://img2020.cnblogs.com/blog/595796/202109/595796-20210929163311170-1457526637.png" alt="" loading="lazy"></p>
<p>具体到职责分配上,Node.js 作为直接与客户端交互的服务层,登录认证是最基本的功能之一,Java 后端服务只需要关注 Node.js 传递的用户 ID 即可。</p>
<p>数据中台有一个统一的用户管理中心提供登录/登出服务,客户端登录后会接收管理中心下发的 JWT,后续业务接口的请求会验证 JWT 的有效性。接入 Node.js 中间层以后,<strong>JWT 的验证逻辑就上浮到了 Node 层</strong>。</p>
<p>由于 Node 层只需要验证(解密) JWT,不需要 JWT 加密算子,所以<strong>非对称加密</strong>是相对较优的方案,比如 RS256。用户中心提供一个获取<strong>公钥</strong>的 API,Node.js 拿到公钥后进行解密即可。</p>
<p><img src="https://img2020.cnblogs.com/blog/595796/202109/595796-20210929163320383-985013619.png" alt="" loading="lazy"></p>
<h2 id="技术选型">技术选型</h2>
<p>技术选型方面并没有纠结太久,其实用什么框架都行,虽然每个框架都有各自的优缺点,但由于 Node.js 中间层的逻辑不会很重,复杂度不高,框架层面的问题几乎不会成为瓶颈。所以一句话总结技术选型的核心出发点就是:用着舒服就行了。</p>
<p>最终用的 NestJS v7,当然也并不是完全没有量化的因素。首先跟 express 和 koa 相比,<strong>NestJS 的模块抽象层次更高</strong>,将中间件进一步抽象为 guards 、 filter 、 interceptor 等等,能够满足大多数场景,几乎不需要感知中间件这个概念。虽然有一定的理解门槛,但熟悉之后写代码能够将各模块的功能划分更加清晰容易维护。其次,NestJS 与 Express 完全兼容,<strong>生态足够丰富</strong>。最后,<strong>完美支持 TypeScript</strong>,搭配 DI 、IoC 等机制,代码的结构和模块体系非常清晰。</p>
<p>之所以选了 v7 而没有用最新的 v8 版本,原因之一是 <strong>NestJS 的 v8 版本依赖 RxJS v7</strong>。RxJS v7 废弃了很多 v6 版本的操作符,用惯了 v6 一时之间切换过来很不习惯。</p>
<p>不过 NestJS 也并不是没有<strong>缺点</strong>。举个例子,Node.js 通常使用 <em>async hooks</em> 进行异步资源跟踪,比如日志。NestJS 的依赖注入机制提供了一种 Request 作用域的 Provider,表面上看完全可以解决请求上下文的资源共享,但实际上并不好用,因为 NestJS 对 Request 作用域的 Provider 有一条额外的限制:<strong>依赖 Request 作用域 Provider 的 Provider 也必须是 Request 作用域的</strong>(很拗口吧)。由于日志模块是通用模块,被很多模块依赖,所以在这条限制下,从 app scope 到 module scope,几乎每个 Provider 都会被牵涉。所以最后还是用了常规方案:cls-hooked。</p>
<blockquote>
<p>也有可能是我学艺不精,还没掌握NestJS 的精髓,欢迎指正。</p>
</blockquote>
<h2 id="服务治理">服务治理</h2>
<p>Node.js 服务部署在公司的未来云 k8s,上层没有接网关,所以 <strong>https 的支持是在由 ingress 这一层提供</strong>,目前的方案比较原始,没有自动化工具,需要手动修改 ingress 配置。除此之外,在服务治理方面需要重点关注两个方面:<strong>守护进程</strong>和<strong>日志管理</strong>。</p>
<h4 id="守护进程">守护进程</h4>
<p>在 k8s 普及之前,Node.js 的进程守护需要借助一些第三方工具,比较知名的比如 forever 和 pm2。使用这些工具会占用一些额外的机器资源(cpu、内存),借助 k8s 探针完全可以取代它们。</p>
<p>未来云提供了<strong>两种存活检查的探针</strong>:Http 和 TCP。</p>
<p><strong>Http 探针</strong>本质上是向某个接口发起 Get 请求,响应成功状态码代表服务健康,否则判定为坏死重启 pod。对于 Node.js 来说就相当于一次请求,所以需要 Node.js 提供一个专用的接口比如<code>/health</code>,需要额外工作,并且这个接口不应该记录日志。</p>
<p>TCP 探针本质上是尝试与容器建立 socket 连接,成功代表服务存活,否则判定为坏死重启 pod,对 Node.js 服务本身没有依赖和影响。</p>
<p>Http 探针由于发起的是一个真实请求,所以通用性更强,但是需要额外的工作。</p>
<p><strong>如果 Node.js 上层有额外的一层反向代理比如Nginx,那么一定不要使用 TCP 探针</strong>。因为 Nginx 本身就能够建立 TCP 连接,所以如果用 TCP 探针的话检测结果永远是健康的。</p>
<p>数据中台的 Node.js 服务每个 pod 都是单核,没有起多进程,也就没有使用反向代理的必要性,所以最终使用 TCP 探针做存活检测。</p>
<p><img src="https://img2020.cnblogs.com/blog/595796/202109/595796-20210929163332365-74977747.png" alt="" loading="lazy"></p>
<h4 id="日志管理">日志管理</h4>
<p>与 Java 服务的日志一样,Node.js 服务的日志也是 ELK 一把梭。需要注意两点:</p>
<ul>
<li>
<p><strong>与 Java 后端的日志串联</strong>。<br>
Node.js 与 Java 后端约定一个日志串联的规范,Node.js 向 Java 发起的请求头中携带一个额外字段 <code>x-trace-id</code>,值为 Node.js 生成的 requestId。</p>
</li>
<li>
<p><strong>过期日志文件及时清理</strong>。<br>
Node.js 的日志文件以天为单位分割文件,每天都会创建几个单独的文件(errors/warnsing/infos/expcetions),如果不及时清理的话会把磁盘打爆进而造成服务重启,所以需要添加一个定时任务清理过期文件。</p>
</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/595796/202109/595796-20210929163341260-35596365.png" alt="" loading="lazy"></p>
<h2 id="总结">总结</h2>
<p>现阶段的 Node.js 中间层刚起步,还比较轻量,以后还会遇到更多挑战。本文简单记录了一些搭建过程中的经验,有正面的可能也有反面的,欢迎指导。</p>
<p>下一篇会写一下目前接入的几个项目中 Node.js 中间层扮演的角色和具体做的事情,敬请期待。</p><br><br>
来源:https://www.cnblogs.com/ihardcoder/p/15353489.html
頁: [1]
查看完整版本: 好未来数据中台 Node.js BFF实践(一):基础篇