这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
应用场景
前端工程化
- Bundle 打包:webpack, vite, esbuild, parcel
- Uglify 压缩:uglifyjs
- Transpile:bablejs, typescript
- 其他语言加入竞争:esbuild, parcel, prisma
- 现状:难以替代
Web服务端应用
- 学习曲线平缓,开发效率较高(js已经比较熟悉)
- 运行效率接近常见的编译语言(js无需编译环境)
- 社区生态丰富及工具链成熟(npm, V8 inspector)
- 与前端结合的场景有优势(SSR)
- 现状:竞争激烈,Node.js有自己独特的优势
Electron跨端桌面应用
- 商业应用:vscode, slack, discord, zoom
- 大型公司内的效率工具
- 现状:大部分场景在选型时,都值得考虑
Node.js在字节的应用
- BFF应用、SSR应用,如Modern.js
- 服务端应用,如头条搜索,西瓜视频,懂车帝PC站点
- Electron应用,如飞书
运行结构
运行步骤
- V8引擎解析JavaScript脚本。
- 脚本调用Node API。
- libuv库执行Node API。
- V8引擎再将结果返回给用户。
V8 引擎:由 Google 开源高性能 Javascript 和 WebAssembly 引擎,使用 c++ 编写,用于 Chrome 和 Nodejs。
libuv:一个用 C 编写的支持多平台的异步 I/O 库,主要解决 I/O 操作容易引起阻塞的问题。
特点
- 异步I/O:在响应返回后恢复操作,不阻塞线程也不占用额外内存
- 单线程:JS单线程,实际上还是有其它线程的(如uv, v8线程池)
- 优点:不用考虑多线程的同步问题,资源利用效率较高
- 缺点:发生阻塞时,会产生等多负面影响
- 跨平台:Node.js跨平台 + JS无需编译环境 + Web跨平台 + 诊断工具跨平台
- 大部分场景无需考虑跨屏他问题,学习和开发成本低
编写Http Server
安装Node.js
Server 和 Client
简单的Hello World
const http = require('http')
const port = 8848
const server = http.createServer((req, res)=>{
res.end('hello')
})
server.listen(port, ()=>{
console.log(`server listen on ${port}`)
})
服务端Server
定义缓冲区,存储接收到的json数据msg,再发送出来,如果没有接收到,则返回‘hello’
const http = require('http')
const port = 3000
const server = http.createServer((req, res) => {
const bufs = []
req.on('data', buf => {
bufs.push(buf)
})
req.on('end', () => {
// Buffer.concat()将给定数组中的所有缓冲区对象合并为一个缓冲区对象
const buf = Buffer.concat(bufs).toString('utf8')
let msg = 'hello'
try{
const ret = JSON.parse(buf)
msg = ret.msg
} catch(err){
// res.end('invalid json')
}
const resJOSN = {
msg: `receive: ${msg}`
}
res.setHeader('Content-Type','application/json')
res.end(JSON.stringify(resJOSN))
})
})
server.listen(port, () => {
console.log(`server listen on ${port}`)
})
客户端client
发送一个POST请求,内容为json数据,并在后台显示内容
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 resJSON = JSON.parse(buf)
console.log(`json.msg is: ${resJSON.msg}`)
})
})
req.end(body)
优化
- 以上的例子使用了很多的回调函数,回调函数不好维护,嵌套的函数关系不易理解
- 技巧:将callback转换成Promise (只适用于只调用一次的函数) + async await
- 如服务端,将其拆分成接收数据(异步)和回应数据两部分
const http = require('http')
const port = 3000
const server = http.createServer(async (req, res) => {
// receive
const msg = await new Promise((resolve, reject) => {
const bufs = []
req.on('data', buf => {
bufs.push(buf)
})
req.on('error', err =>{
reject(err)
})
req.on('end', () => {
const buf = Buffer.concat(bufs).toString('utf8')
let msg = 'hello'
try {
const ret = JSON.parse(buf)
msg = ret.msg
} catch (err) {
// res.end('invalid json')
}
resolve(msg)
})
})
// resopnse
const resJOSN = {
msg: `receive: ${msg}`
}
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(resJOSN))
})
server.listen(port, () => {
console.log(`server listen on ${port}`)
})
静态文件服务器
写一个服务,接收请求,获取其中的URL,该URL对应磁盘中的文件路径,依照路径找到对应的文件,并将文件的内容返回给请求。
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')
const port = 3000
// find folder
const folderPath = path.resolve(__dirname, './static')
const server = http.createServer((req, res)=>{
// expected http://127.0.0.1:3000/index.html
const info = url.parse(req.url)
// static/index.html
const filepath = path.resolve(folderPath, './' + info.path)
// stream API is better
const filestream = fs.createReadStream(filepath)
filestream.pipe(res)
// type
//...
})
server.listen(port, ()=>{
console.log(`server listen on ${port}`)
})
若想获取高性能的可靠服务器,还需要什么?
- CDN:缓存和加速
- 分布式存储,容灾机制
- 考虑外部服务,如cloudflare,阿里云,七牛云,火山云等
React SSR
SSR(server side rendering) 服务端渲染
- 相比传统的HTML引擎,能避免重复编写代码
- 相比SPA(single page application),不需要等待所有js加载完成,首屏渲染更快
- 缺点:qps(Queries Per Second) 每秒查询率较低
难点
- 需要处理打包代码
- 需要考虑前端代码在服务端运行时的逻辑
- 需要移除对服务端无意义的副作用
调试与部署
V8 Inspector
- 开箱即用,特性丰富强大,跨平台
- 使用场景:console.log,断点,性能分析等
部署要解决的问题
- 守护进程:退出和重启
- 多进程利用
- 记录状态,用于诊断