Node.js 与前端开发实战 | 青训营笔记

170 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第13天。

一、本堂课的重点内容

  • Nodejs应用场景
  • Nodejs运行时结构
  • 编写HTTP Server,Nodejs使用最多的场景

二、详细知识点介绍

Nodejs应用场景

前端工程化

image.png

早期的web库Ajax、JQuery,是浏览器直接加载,不太需要后端的工程化的事情。

babel、Webpack等,需要一些Nodejs的服务端能力,调用操作系统api

  • Bundle:Webpack,Vite,esbuild,parcel
  • Uglify:uglifyjs,压缩,转换
  • Transpile:babeljs,typescript,语法转换
  • 其他语言加入前端工程化:esbuild基于go实现,parcel基于rust实现,prisma前端数据库的ORM

Web服务端应用

Vercel公司开发的Nextjs,与前端结合的web服务端应用

  • 学习曲线平缓,开发效率高,js实现
  • 运行效率接近常见编译语言,在解释型语言中效率较高
  • 社区丰富,工具链成熟(npm,V8 inspector),有的包的成熟度可能不如其他语言,nodejs调试器与devtools相同
  • 与前端结合的场景有优势(SSR),服务端渲染,自动生成HTML

Electron跨端桌面应用

VSCode,slack,discord,zoom等

大型公司内部的效率工具,跨端桌面应用,开发效率高,运行稳定,资源占用多。

字节跳动内部的Nodejs应用:

  • BFF应用(业务逻辑由后端实现,接口拼接裁剪由端构建)、SSR应用,Modern.js
  • 服务端应用,头条搜索,西瓜视频,懂车帝等的PC站点,最上层的后端应用由Nodejs实现
  • Electron应用:飞连,飞书

Nodejs运行时结构

image.png

  • nodejs内部也会使用社区代码,如acron,node-inspect。

  • 用户手动安装的包在用户代码内

  • N-API提供更高性能的Native API的代码,实现由于js效率太低无法实现的代码

  • v8是nodejs运行时,诊断调试工具(inspector)

  • libuv提供操作系统api和event loop(事件循环),syscall(系统调用)

  • nghttp2提供http2相关api

  • zlib 压缩和解压缩

  • c-ares用于DNS查询

  • llhttp提供http相关api

  • OpenSSL用于网络的加解密

Nodejs特点

  • 异步IO

    • setTimeout,readFile等

    • 无需阻塞线程执行,无需等待异步操作,而等待异步返回时线程内存不会释放

    • 节省时间,提高效率,无需频繁申请和释放内存 image.png

  • 单线程

    • nodejs是单线程,不适合CPU密集型操作,从nodejs12开始的worker_thread可以起独立线程,但每个线程的模型没有太大变化

    • 只有JS主线程是单线程,实际上有JS线程+libuv线程池+V8任务线程池+V8 inspector线程

    • 异步读文件的过程由libuv的线程池实现,主线程先做其他的事,或者高CPU的操作如加解密等,也会进入libuv的线程池去做,避免主线程的阻塞

    • 优点:不用考虑多线程状态同步问题,也就不需要锁;同时还能比较高效地利用系统资源;

    • 缺点:阻塞会产生更多负面影响;解决办法:多进程或多线程,nodejs更多场景会使用多进程

  • 跨平台

    • 同样的代码,如socket的,大部分API是相同的,不太用考虑跨平台的问题

    • 有些API也不会跨平台,如有的API是特定操作系统专用的

    • 无需编译环境,开发成本低,整体学习成本低

三、实践练习例子

node-fetch模块发起请求

npm安装了node-fetch,用户代码中发起了node-fetch请求,在v8内执行,调用js内的http模块,进而调用C++内的http api,调用llhttp执行序列化和反序列化,调用libuv创建tcp连接。

接收到数据后,在libuv的Event loop中处理,交给llhttp解析,再层层向上返回。

编写HTTP Server

安装nodejs,*nix下使用nvm,方便多版本管理

要对Nodejs原生的模块有一定了解,以实现多样性的需求,不要完全依赖模块

使用promisify化的函数解决回调地狱的问题。

stream风格的api:const fileStream=fs.createReadStream(path);res.pipe(fileStream),可占用尽可能少的内存空间,用fs.readFile会把整个文件读到内存,需要分配足够的内存空间。而使用fs.createReadStream会按需将数据返回给client,stream发送速率由client的消费速率而定。

HTTP Server高性能、可靠的需求:CDN,缓存,加速、分布式储存,容灾

外部服务:cloudflare、七牛云、阿里云、火山云

服务端渲染 Server Side Rendering, SSR

  • 相比传统HTML模板引擎,避免重复编写代码,服务端渲染引擎不再需要HTML模板引擎,均写到js里
  • 相比SPA(single page application):首屏渲染更快,SEO(搜索引擎优化)友好
  • 缺点:通常qps(每秒查询率)较低,前端代码编写时需要考虑服务端渲染情况

Debug

开启调试 node --inspect

访问127.0.0.1:9229/json打开websocket调试

场景:

  • 查看console.log内容
  • 断点,可以加入logpoint,防止阻塞,相当于插桩
  • 高CPU,死循环:cpuprofile
  • 高内存占用,memory面板,通过snapshot查看是否存在内存泄露问题

部署

要解决的问题:

  • 守护进程:进程退出时,重新拉起
  • 多进程:cluster便捷利用多进程
  • 记录进程状态用于诊断

可通过容器环境部署,只需考虑多核CPU利用率即可

四、课后个人总结

本节课学习了nodejs的相关知识,对nodejs概况有了一定的了解,学习了nodejs应用场景、架构和一些编程实例。通过本节课的学习,在之前已有的知识储备上,增加了Nodejs架构的了解,学习了Nodejs相关底层库的功能,以及了解了nodejs运行时结构。

五、参考链接

talks/contributing-to-node-core.pdf at master · joyeecheung/talks (github.com)