这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
一、Node.js 的应用场景(why)
1. 应用场景
-
前端工程化
- Bundle:webpack, vite, esbuild, parcel
- Uglify:uglifyjs
- Transpile: bablejs, typescript
- 其他语言加入竞争:esbuild, parcel, prisma
- 现状:难以替代
-
Web服务端应用
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编译语言
- 社区生态丰富及工具链成熟(npm, V8 inspector)
- 与前端结合的场景会有优势(SSR)
- 现状:竞争激烈,Node.js有自己独特的优势
-
Electron跨端桌面应用
- 商业应用:vscode, slack, discord, zoom
- 大型公司内的效率工具
- 现状:大部分场景在选型时,都值得考虑
2. Node.js 在字节
-
BFF应用、SSR应用,举例:Modern.js
-
服务端应用,举例:头条搜索,西瓜视频,懂车帝
-
Electron应用:飞连,飞书
-
每年新增1000+ Node.js应用
二、Node.js 运行时结构(what)
1. 示意图
2. V8,libuv
- V8:JavaScript Runtime,诊断调试工具(inspector)
- libuv: eventloop(事件循环),syscall(系统调用)
- 举例:用 node-fetch 发起请求时...
3. 特点
-
异步 I/O
- 当Node.js执行VO操作时,会在响应返回后恢复操作,而不是阻塞线程并占用额外内存等待
-
单线程(worker_thread可以起独立线程,但每个线程的模型没有太大变化)
-
JS单线程
- 实际:JS 线程 + uv线程池+V8任务线程池 + V8 Inspector线程
-
优点:不用考虑多线程状态同步问题,也就不需要锁;同时还能比较高效地利用系统资源;
-
缺点:阻塞会产生更多负面影响
- 解决办法:多进程或多线程
-
-
跨平台
-
跨平台(大部分功能、api)
-
Node.js跨平台 + JS无需编译环境 (+ Web跨平台 + 诊断工具跨平台)
- = 开发成本低(大部分场景无需担心跨平台问题),整体学习成本低
-
三、编写Http Sever(how)
1. 安装 Node.js
-
Mac,Linux 推荐使用 nvm ,多版本管理
-
Windows 推荐 nvm4w 或是官方安装包
-
安装慢,安装失败的情况,设置安装源
设置镜像源: ORG MIRROR=https://npmmirror.com/mirrors/node nvm install 16
2. 编写 Http Server
- Hello World
/* Hello World */
const http = require('http')
const server = http.createServer((req,res) => {
res.end('hello')
})
const port = 3000
server.listen(port, () => {
console.log('listening on',port)
})
- Json
/* JSON */
const http = require('http')
const server = http.createServer((req,res) => {
const bufs = []
req.on('data', (buf) => {
bufs.push(buf)
})
req.on('end', () => {
const buf = Buffer.concat(bufs).toString('utf-8')
let msg = 'Hello'
try{
const ret = JSON.parse(buf)
msg = ret.msg
} catch(err) {
res.end('invalid json')
}
const responseJson = {
msg: `receive: ${msg}`
}
res.setHeader('Content-Type','application/json')
res.end(JSON.stringify(responseJson))
})
})
const port = 3000
server.listen(port, () => {
console.log('listening on',port)
})
- Client
/* Client */
const http = require('http')
const body = JSON.stringify({
msg: 'Hello from my own client'
})
const req = http.request('http://127.0.0.1:3000', {
method:'POST',
headers: {
'Content-Type': 'application/json'
}
}, (res) => {
const bufs = []
res.on('data', (buf) => {
bufs.push(buf)
})
res.on('end', () => {
const buf = Buffer.concat(bufs)
const json = JSON.parse(buf)
console.log('json.msg is:',json.msg)
})
})
req.end(body)
3. Promisify
-
用 Promise + async await 重写这两个例子(why)
-
技巧:将 callback 转换为 promise
function wait(t){ return new Promise(( resolve, reject)=>{ setTimeout(() =>{ resolve() }, t) }) } wait(1000).then(()=> { console.log( 'get called ')})
4. 静态文件
// 编写一个简单的静态文件服务
const http =require("http")
const fs =require('fs')
const path = require('path')
const url = require('url')
const port = 3000
const server = http.createServer( (req,res) => {
const info = url.parse(req.url)
const file = fs.createReadStream(path. resolve(__dirname,'.' + info.pathname))
file.pipe(res)
})
server.listen(port,()=> {
console.log( `server listens on: ${port}`)
})
-
与高性能、可靠的服务相比,还差什么?
- CDN:缓存+加速
- 分布式储存,容灾
-
外部服务:cloudflare,七牛云,阿里云,火山云...
5. React SSR
-
SSR (server side rendering)有什么特点?
-
相比传统HTML模版引擎:避免重复编写代码
-
相比SPA (single page application):首屏渲染更快,SEO友好
-
缺点:
- 通常qps 较低,前端代码编写时需要考虑服务端渲染情况
-
-
HTML例子
const http =require( ' http') const port = 3000 const server = http.createserver( ( req, res) =>{ res.setHeader('content-Type' , 'text/html') res.end(` <D0CTYPE html> <html> <head> <title>My App</title> </head> <body> <h1>My App</h1> </body> /html> `) }) server.listen(port,()=> { console.log(`server listens on: ${iport}`) }) -
替换成React
const http = require( 'http ') const React =require(' react ') const ReactDOMServer = require( "react-dom/server") function App(){ return React.createElement( "h1",{ children: "Hello", }) } const port = 3000 const server = http.createServer((req, res) =>{ res.setHeader( 'Content-Type' , "text/html") res.end(` <DOCTYPE html> <html> <head> <titleMy App</title> </head> <body> <div id="main"> ${ReactDoMServer.renderToString( React.createElement(App) )} <div> </body </html> `) }) server.listen(port,()=>{ console.log(`server listens on: ${iport}`) })
-
SSR难点
-
需要处理打包代码
-
需要思考前端代码在服务端运行时的逻辑
-
移除对服务端无意义的副作用,或重置环境
-
6. Debug
-
V8 Inspector:开箱即用、特性丰富强大、与前端开发一致、跨平台
- node --inspect
- open http://localhost:9229/json
-
场景:
- 查看console.log内容breakpoint
- 高CPU、死循环:cpuorofile
- 高内存占用:heapsnapshot
- 性能分析
7. 部署
-
部署要解决的问题
- 守护进程:当进程退出时,重新拉起
- 多进程:cluster便捷地利用多进程
- 记录进程状态,用于诊断
-
容器环境
- 通常有健康检查的手段,只需考虑多核cpu利用率即可
四、延伸话题
1. Node.js 贡献代码
-
快速了解 Node.js代码
- Node.js Core贡献入门
-
好处:
- 从使用者的角色逐步理解底层细节,可以解决更复杂的问题
- 自我证明,有助于职业发展;
- 解决社区问题,促进社区发展;
-
难点:
- 花时间
2. 编译Node.js
-
为什么要学习编译Node.js
- 认知:黑盒到白盒,发生问题时能有迹可循
- 贡献代码的第一步
-
如何编译
- 参考:Maintaining the build files
- ./configure && make install
- 演示:给net模块添加自定义属性
3. 诊断 / 追踪
-
诊断是一个低频、重要同时也相当有挑战的方向。是企业衡量自己能否依赖一门语言的重要参考。
-
技术咨询行业中的热门角色。
-
难点:
- 需要了解 Node.js底层,需要了解操作系统以及各种工具
- 需要经验
4. WASM,NAPI
- Node.js(因为V8)是执行WASM代码的天然容器,和浏览器WASM是同一运行时,同时Node.js支持WASI。
- NAPI执行C接口的代码(C/C++/Rust...),同时能保留原生代码的性能。
- 不同编程语言间通信的一种方案。