Node.js 笔记:EventLoop、Buffer、Stream、网络与多线程
Node.js 是工程化、服务端渲染、BFF 和全栈能力的关键补充。本文整理运行机制、网络服务、并发与稳定性。
运行机制
EventLoop
Node 的事件循环由 libuv 驱动,包含 timers、pending callbacks、idle/prepare、poll、check、close callbacks 等阶段。setTimeout 在 timers 阶段,setImmediate 在 check 阶段,Promise 微任务会在当前阶段结束后尽快清空。
setTimeout 和 setImmediate 谁先执行不一定。顶层代码里受定时器精度和事件循环进入时机影响;如果在 I/O 回调里,setImmediate 通常先于 setTimeout。
Buffer
Buffer 用来处理二进制数据。Node 在网络、文件、加密场景中经常需要直接操作字节,字符串只是按编码解释后的结果。
Buffer 是字节序列,字符串是字符序列。一个字符可能对应多个字节,比如 UTF-8 中文通常占 3 个字节。
Stream
Stream 用分块方式处理数据,避免一次性把大文件读入内存。常见类型有 Readable、Writable、Duplex、Transform。背压机制能避免写入方过快导致内存膨胀。
大文件上传下载用 Stream 的原因:可以边读边写,降低内存占用,并通过背压协调读写速度,提升稳定性。
网络服务
HTTP Server
Node 内置 http 模块可以创建服务。框架如 Express、Koa、NestJS 封装了路由、中间件、异常处理和生态能力。
Koa 洋葱模型:中间件按顺序进入,遇到 await next() 后进入下一个中间件,后续中间件完成后再回到上一层继续执行,形成类似洋葱的前后置处理。
WebSocket、GraphQL 与 gRPC
WebSocket 适合实时双向通信。GraphQL 让客户端声明需要的数据结构,适合复杂聚合查询。gRPC 基于 HTTP/2 和 Protobuf,适合服务间高性能通信。
WebSocket 和 HTTP 轮询的区别:轮询是客户端定时请求,实时性和资源利用都较差;WebSocket 建立长连接后可双向推送,适合聊天、协作、实时通知。
并发与稳定性
Cluster
Node 单进程单主线程,CPU 密集任务会阻塞事件循环。Cluster 可以启动多个进程利用多核 CPU,通常由主进程管理 worker。
Node 不适合在主线程直接跑 CPU 密集任务。可以用 Worker Threads、子进程、任务队列,或者交给专门服务处理。
Worker Threads
Worker Threads 提供线程级并行能力,适合 CPU 密集计算。它和主线程之间通过消息通信,也可以使用 SharedArrayBuffer 共享内存。
Cluster 是多进程,适合扩展服务吞吐;Worker Threads 是多线程,适合分担 CPU 密集计算。
错误处理与日志
服务端要处理同步错误、异步 rejection、接口异常、超时、重试和日志追踪。生产环境不能只靠 console.log,需要结构化日志、请求 ID、监控告警。
Node 服务防崩溃:业务层做好 try/catch 和统一错误处理中间件,处理未捕获异常并上报,进程异常后由 PM2、容器或守护进程拉起,同时保留日志和告警。
代码示例
Node 事件循环阶段细节
Node 的 process.nextTick 不属于普通微任务队列,它优先级高于 Promise 微任务。滥用 nextTick 递归会饿死 I/O。
1 | setTimeout(() => console.log('timer'), 0) |
常见输出:
1 | sync |
不要大量递归调用 process.nextTick,它会在事件循环进入下一阶段前持续清空,可能导致 timers、I/O、check 阶段迟迟无法执行。
Stream 背压代码示例
write() 返回 false 表示写入缓冲区已满,读取方应该暂停,等 drain 再继续。
1 | const fs = require('fs') |
更推荐使用 pipeline,它会处理背压和错误传播:
1 | const { pipeline } = require('stream/promises') |
CPU 密集任务放到 Worker
CPU 密集任务会阻塞主线程,让所有请求变慢。Worker Threads 可以把计算移出主线程。
1 | // main.js |
1 | // worker.js |
Node 高并发靠事件驱动和非阻塞 I/O 处理大量 I/O 并发,不是靠单线程执行 CPU 任务很快。CPU 密集计算仍要拆出去。
