🎉🎉🎉NestJS全栈进阶路线指南来了~

5,184 阅读16分钟

大家好,我是元兮!今天跟大家聊一聊体系学习这个话题。

如果问我接触一门新的事物,最重要的一点是什么?我认为是方向

这个答案并不是空穴来风,它存在一定的科学依据的,作为人与生俱来的特点,在遇到陌生的领域首先第一反应的恐惧,恐惧来源于前面道路可能出现的风险或产生问题的未知,说白了就是没有方向!

既然这样,那怎么样才能有方向呢?当然是有一张地图,地图上描绘了到达目的的行进路线,同时标注了过程中会遇到哪些障碍需要我们逐个突破。例如下面这张图:

image.png

这跟我们玩王者荣耀是一样的,工程师们设计了有三条路可以直达对方高地,但每条路都会遇到敌人或野怪,逐个击破才能使我们更加强大,能力越强越容易对付大BOSS。

即使作为一个久经前端沙场的大头兵,在缺乏对服务端技术体系的情况下,顶多对MySQL、Redis、MQ这些中间件有初步了解,涉及到运维知识或许也知道有Nginx、Docker、K8s等这些技术,即便有了这些基础认知实际上我们还是会存在各种疑惑:

  1. 想学全栈开发,但却不知道从哪里下手,哪些是基础知识必须先掌握的,哪些是可以晚一点再学,项目初期暂时不会遇到的?
  2. 学会了MySQL、Redis的基本数据存储使用,接下来进阶部分应该学什么呢,才能满足企业要求?
  3. 在实际的业务中有经验的同学是怎么应用Redis、MQ这些中间件的,它们分别处于哪一层,可以做哪些事情?你们公司技术架构组成是怎么样的,用到了哪些技术?我想很多前端、初入后端的同学也是一知半解。
  4. 当线上出现bug时,如何定位具体问题?是运维层面出了问题还是后端服务出了问题?更具象化一些,是不是哪个配置漏了?服务部署顺序错了?CPU、I/O操作、服务内存占满了?

有以上的问题都很正常,我们认识一件事物是从认知、了解、熟悉的过程,下面详细梳理一下这个过程。

PS: 2024.9月,笔者发布了新书《NestJS全栈开发解析:快速上手与实践》并开源了书中的实战项目代码,感兴趣的朋友可以点击了解详情。

第一阶段

一上来,通常我们会有一个切入点,即选择后端语言或框架,例如Node(Nest)、Go或Java等等,进而可以了解到后端体系中各种设计思想,著名鼎鼎的是MVC体系、OOP | AOP思想、DDD领域设计

以NestJS为例,了解完设计思想后我们基本上有了一定的概念,清楚MVC体系中每一层的职责范围,其中Model层指的是ORM层的数据模型,通过@Entity定义,与服务层Service或DB进行数据交互。

View是视图层,可以是EJS、Pug模板直接渲染出来的HTML,但这种做法不常见,在前后端分离的体系中更多指的是返回JSON给前端框架进行处理。

Controller是控制器,在Nest中它的职责很明确,用于分发路由而不实现具体的业务逻辑,通过调用Service方法对数据库进行交互、与第三方API配合处理业务细节。

接着,你可以根据图中的这部分内容归纳,对Nest的主要基础部分进行知识扫盲,作为后续进阶实践的前提条件。

image.png

有了以上基础 & 核心概念的理论支撑,此时我们大脑中已经产生了很大一部分游离的神经元,这些神经元保存着各种不同的新知识点,但它们之间的关联能力很弱甚至没有关联,经不住时间的推敲。

此时我们可以通过Nest提供的强大CLI工具创建项目、模块、控制器、服务进行刻意练习,使用Curl命令行脚本进行请求测试,以巩固各个神经元之间的联系。

image.png

第二阶段

为了能够让我们快速上手项目开发,接下来就需要学习后端体系中最常见的用户权限系统、储存 & 缓存中间件的使用,让系统能够活起来

image.png

权限系统中,最为普遍的是基于Token、Session的身份验证(JWT属于特殊的Token),大名鼎鼎的是基于RBAC的权限管理机制,除此之外还有ALC、ABAC等权限机制。

后端的信息绝大部分都是需要持久化的,进一步我们需要了解到各种类型的数据库,关系型数据库MySQL、postgreSQL、Oracle等,非关系型数据库(NoSQL)代表为MongoDB、Redis等等。

以MySQL为例,如果所有的信息都进行落库处理,通常落库需要进行磁盘I/O,而这个操作又是非常昂贵的,为了提升系统性能,我们会使用内存缓存来帮助MySQL抗压,对频繁访问的数据进行缓存。内存缓存有CacheManage、Redis这类中间件,甚至我们可以用它们实现多级缓存效果。

另外,我们在实际开发中操作MySQL可以使用命令行或可视化界面操作数据,但业务中一般都不会直接使用命令,这未免显得有些低效,而是用ORM这类框架来实现对数据库操作,它将操作数据库这一行为映射为操作对象的人性化过程,主流代表框架有TypeOrm、Prisma

image.png

OK,到此为止前面介绍的都与本地数据库相关的,我们还会使用到云数据库,即通过云存储的方式实现数据持久化。这类数据库的使用场景一般是将图片、视频等资源上传到一个特定的平台统一管理,如OSS、S3,然后返回一个可以访问资源的链接(下载链接),根据使用场景可以将链接设置为定期有效或长期有效。

image.png

好,前面介绍了操作数据库可以使用缓存来加速,常用在大数据量查询和连表查询的场景。如果是遇到写操作的场景呢,短时间内大量请求进行入库操作,此时可以借助消息队列进行抗压,如RabbitMQ、Kafka这类中间件,将请求投递到异步队列中,由后台消费者异步写入,这就是常说的流量削峰image.png

在研发阶段,我们通常需要定义各种业务接口,它们遵循一定的接口规范,比如RESTful或GraphQL。NestJS中集成了Swagger插件,能够自动生成接口文档说明,并支持导入到Yapi中来管理。

除此之外,我们可以随时使用第三方插件对接口进行快速调试,VsCode插件如REST Client、REST API等,当然你热衷于第三方工具Post Man、Apifox也无妨。

image.png

第三阶段

这个阶段我认为是进一步扩大开发者的视角,具备更全面的后端思维和知识体系 - 架构及及运维。 有人可能认为,这是运维人员做的事情呀。没错!但我们不需要做到这么专业,毕竟这岗位牵涉到的面太广了,软件、硬件、网络层面都需要了解,围绕项目部署、运行、监控这几个环节就行了。

image.png

说到部署,绕不开的话题是Docker与K8s,那如何在两者之间选择适合当前业务的运维方案呢?开始这个话题之前先来了解单体集群分布式架构的概念。

架构演变史

在互联网早期时,前后端分离的概念尚未普及,代码都在一个项目下进行开发和部署,将整套代码上传到服务器即可,用户交互产生的所有资源请求和接口请求都打到一台服务器上,这是早期的单体架构

工程师们很快发现服务器资源不足以支持业务增长,于是将前端资源上传到指定的资源服务器,采用CDN方式单独维护,缓解了部分压力。但每次页面请求都需要返回整个HTML结构,而且仅仅更新页面部分数据也要刷新整个页面,无意中在增加非必要请求。于是,Ajax诞生解决了局部刷新问题,也为前后端分离奠定了基础,加上SPA模式的普及更为服务器缓解了非必要的页面请求,然而这依旧属于单体架构。

随着移动互联网的繁荣发展,各种高并发的场景层出不穷,单体架构处理能力有限,一台服务器硬件资源无法支撑日益增长的请求,那怎么办呢?增加多几台服务器啊!于是,集群架构由此而来。

集群中每台服务器就属于这个集群中的“节点”,所有“节点”构成了一个集群。每个节点提供相同的服务,做同样的事情,整个系统的处理能力就提升了n倍(n个节点)。

用户请求过来时,如何确定当前请求由哪个节点进行处理呢?分配请求需要尽可能均匀,不能出现A节点负载高时,还不断分配请求给它,而是找到一堆节点中负载压力最小的那个节点。承担这项任务的角色属于调度者,每个请求都会经过它进行处理后均衡到每个节点上,所以起了个响当当的名字给它 - 负载均衡服务器

当业务增长到服务器抗不住时,通过扩展节点来满足业务发展,但是,当业务发展到一定体量,不管如何扩展节点,系统的处理能力提升并不明显,例如每台节点的处理某个任务需要1小时,无法通过扩展节点来缩减这个时间,此时,分布式架构应运而生!

分布式系统中最具代表的是微服务架构,把一个庞大的系统拆分为一个个小的独立服务进行单独开发和部署,每个子系统之间通过RPC进行通信,共同协作完成用户请求闭环。

这样有什么好处呢?当构成分布式系统的某一子系统面临高并发请求时,如双十一节日的订单服务,可以对该服务进行单独扩展节点或硬件资源,其余服务保持原有的配置。同时可以对订单服务单独开发,测试和部署,相对于单体架构而言降低系统偶尔度。

一句话总结:集群是一堆人做同样的事情,分布式是一堆人各自负责不同的事情。

我们再用一个通俗例子说明单体、集群和分布式架构之间的关系。

工程师A负责公司前端工作,每天朝九晚六,别提有多滋润,可有一天老板把后端和运维的工作都给到A负责,不得不变成996扛住这波压力。

随着业务增长老板担心有一天A突然宕机(跑路或cusi),又招了B和C工程师,负责与A一样的工作内容,此时他们关系就演变成了集群架构。

好景不长,B和C的前端代码写的很好,但后端、运维能力太弱了,每次需要花费大量时间,影响了需求的迭代。为了职责更明确,于是老板招了专门的后端开发D和运维人员F,此时他们之间的关系是分布式,当发现运维人员不足了,又招了一个运维G,F和G的关系又是集群

就老系统而言,想要把集群升级为分布式架构,意味着对各个模块进行解藕,势必大刀阔斧,需要架构师深思熟虑、权衡ROI。

回到Docker与K8s选型话题上,如果我们的项目属于单体架构,使用Docker可以轻便的管理各个服务的配置和服务之间的先后顺序。当团队使用Docker出现部署效率瓶颈时,考虑再上K8s,借助Docker + k8s集群可以实现容器弹性伸缩、请求负载均衡和服务发现等功能。

除此之外,有公司也会在测试环境使用Docker部署,而正式环境用K8s。

那不是还有前端项目部署么?没错!前端项目使用Nginx托管即可,允许在Nginx层进行反向代理、跨域配置、请求分发-负载均衡、资源压缩、IP黑白名单等操作。这是基于前后端分离的架构基础上,单体架构几乎不存在单独部署的问题,顶多对资源文件执行压缩命令。如今,使用NestJS的几乎也都是前后端分离架构。

如今,每一个研发环节都有对应提效工具,要在部署环节进行提效,不得不提到Devops流程,目前有不错的第三方服务提供支持,如蓝盾、阿里云云效,通过GitHub ActionGitlab CI实现CI/CD功能,完成一键部署和回滚。

服务运行过程中,如果需要更改项目配置,比如第三方服务域名升级、黑白名单更新,我们通常会使用第三方配置中心统一管理各种服务配置信息,如Apollo、Etcd。更新完后发布新的配置文件即可生效,不需要手动重启服务。

除此之外,后端各种服务都会实现监控系统和日志上报,实时感知系统健康状态,Nest中可以通过内置Logger进行日志统计,结合Sentry监控系统实现自动上报和错误源码溯源。也可以使用第三方日志框架Wiston自定义各种格式的日志记录,上传到日志服务机器。

当然,我们也可以使用集成度更高的Grafana数据可视化平台,它与K8s系统无缝集成,实现数据分析、性能监控、机器指标监控和日志查询。

一致性问题

分布式领域中,一致性问题是绕不开的话题,数据库、缓存、业务逻辑、消息中间件、文件存储...各类场景的分布式系统中都有它的影子。

image.png 要想深入分布式领域,首先要了解基础理论(啃骨头),如ACID、BASE、CPA

前面我们了解了集群的概念,从业务性质维度上可分为业务逻辑集群数据存储集群业务逻辑集群 必然涉及到客户端请求身份验证、流量管理、负载均衡、监控日志以及请求分发等问题,每个细分下来都能写成一本书,以K8s Ingress、Nginx、ApiSix等API网关和请求分发器作为切入点进行深入了解。

为了能够优雅处理一致性问题,衍生出各种架构模式,数据存储集群 中根据架构模式又可以分为主从架构和分片架构,主从架构涉及级联复制、读写分离、故障转移、多主热备等多种方式,分片架构涉及分片式集群,根据不同维度可以分为多种分片方式,例如根据控制与协调方式可分为中心化分片去中心化分片集群,根据范围维度可以分为单表分片多表分片集群,根据存储引擎维度可以分为非关系型数据库关系系数据库分片集群

再深入到分布式数据库,又会接触到分布式事务分布式ID等机制来保证数据一致性。如今,实现分布式事务也有多种成熟方案:

  • 基于Best Efforts 1PC模式解决分布式事务问题。
  • 基于XA协议的2PC、3PC模式做全局事务控制。
  • 基于TTC方案做事务补偿。
  • 基于MQ实现事务的最终一致性。

分成分布式ID也有常用的几种算法:

  • 雪花算法
  • ID生成器
  • UUID
  • 号段模式

好了,细心的朋友可能会发现,越聊到后面跟语言、框架的相关性就越小,事实就是如此,在清晰全栈知识体系的前提下,我们能够从一门语言、框架类比到其他同类型的语言中,做到举一反三,唯一区别是不同语言为了满足特定化场景(需求)或者解决某一类问题而刻意设计出独属自己的语言特性,就像我们每个人都有自己的个性和脾气。

回归前面提到的几个问题,这里出个笔者个人思路可供参考。

image.png

  1. 语言、框架作为切入点,熟悉其基本特性,延伸到图中每一环节的技术细节,先打小怪升级。
  2. 满足企业要求其实不难,先掌握常用的Redis、MySQL基本技巧,了解其基本的原理;再深入系统学习性能优化、分布式领域相关技术,如索引、SQL调优、分表分库、数据一致性问题等。
  3. 熟悉公司的技术架构,我想这个文章已经给了方向,接下来是逐步接触公司相关领域,从实践中学习,这里有个捷径:问公司相关负责人
  4. 熟悉公司基本的技术架构后进行链路追踪,看日志,Web服务器日志、服务器指标(CPU、内存)、MySQL服务指标、Redis服务指标、MQ服务消费日志等等。

总结

至此,本文已经超过4800多字,但提及分布式领域的话题仍旧是冰山一角,无法用简短的语言来表达清楚所有的学习过程,初期唯一能做的是认清楚方向。在全栈的旅途中依然存在很多坑坑洼洼、绊脚石等着我们去填踩和沉淀~

最后,总结NestJS全栈进阶三段曲

  • 第一阶段:夯实基础,了解语言、框架特性,打小怪升级。
  • 第二阶段:了解各种常用中间件,继续叠buff。
  • 第三阶段:了解系统架构及运维,终身学习...