这是我参与「第五届青训营 」笔记创作活动的第7天
Node.js的应用场景
前端工程化
打包工具:webpack,vite,esbuild,parcel
压缩:uglifyjs
转义:babeljs,typescript
其他语言的加入:esbuild,parcel,prisma
web服务端应用
特点:
- 学习曲线平缓,开发效率较高
- 运行效率接近常见的编译语言
- 社区生态丰富及工具链成熟(npm,V8 inspector)
- 与前端结合的场景会有优势(SSR)
- 现状:竞争激烈,Node.js有自己独特的优势
Electron跨端桌面应用
- 商业应用:vscode,slack,discord,zoom
- 大型公司内的效率工具
- 现状:大部分场景在选型,都值得考虑
Node.js运行时结构
- 用户安装的依赖以及代码都是存放在
用户代码 node-inspect诊断调试工具N-API是用于某个场景或需求使用js效率较低,其中会更原生的语言来实现功能libuv封装了各种操作系统的APInghtt2封装关于http2相关的模块和node核心eventloop(事件循环)llhttp做http的解析zlib做一些常见的压缩和解压缩的算法c-ares用于做dns查询的库OpenSSL网络层面的加密解密
举例 node-fetch发起请求的调用
安装
通过npm安装node-fetch,那么对应的代码就会存储在用户代码中
发起请求
我们在代码中调用node-fetch,那么会到v8中执行,该模块在底层中调用的是Node.js Core(javascript),然后调用更底层的Node.js Core(C++),在其中又使用到llhttp 序列化与反序列化,然后通过libuv创建tcp链接,把数据发送给远端
接收数据
拿到数据之后,经过llhttp解析,经过Node.js Core(javascript)代码,再到用户的代码
特点
- 异步 I/O
2. 单线程
只有JavaScript是单线程
实际上:JS线程 + uv线程池 + V8任务线程池 + V8 Inspector线程
优点:不用考虑多线程状态同步问题,也就不需要锁;同时还能比较高效地利用系统资源
缺点:阻塞会产生更多负面影响
解决方法:多进程或多线程
3. 跨平台
编写HTTP server
Http Server
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数据
const http = require('http')
const server = http.createServer((req,res) => {
const bufs = []
// 接收body数据
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(error) {
//
}
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)
})
发送请求
const http = require('http')
const body = JSON.stringify({
msg:"hello from 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)
静态文件
const http = require("http");
const fs = require("fs");
const path = require("path");
const url = require("url");
const folderPath = path.resolve(__dirname, "../static");
const server = http.createServer((req, res) => {
// expected http://127.0.0.1:3000/index.html?abc=10
const info = url.parse(req.url);
console.log("info", info);
// static/index.html
const filePath = path.resolve(folderPath, "./" + info.pathname);
console.log("filePath", filePath);
const filestream = fs.createReadStream(filePath);
filestream.pipe(res);
});
const port = 3000;
server.listen(port, () => {
console.log("listening on", port);
});
运用stream的好处:
把所有数据都存储在stream中,按需返回给客户端,内存使用率较高
与高性能、可靠的服务相比,还差什么:
- CDN:缓存+加速
- 分布式存储,容灾
React SSR
SSR特点:
- 相比传统HTML模型引擎:避免传统编写代码
- 相比SPA:首屏渲染更快,SEO友好
缺点:通常qps较低,前端代码编写时需要考虑服务端渲染情况
Debug
V8 Inspector:开箱即用、特性丰富强大、与前端开发一致、跨平台
场景:
- 查看console.log内容
- breakpoint断点
- 高CPU、高循环:cpuprofile
- 高内存占用
- 性能分析
比如启动的文件名称是ssr_example.js,那么可输入命令行
node --inspect ssr_example.js
通过一个链接,可能会出现以下的情况,它期望连接websocket
我们可以进入http://127.0.0.1:9229/json,跳转到红色框内的url
console:查看打印的信息
sources:调试断点
memory:调试内存问题
profilter:可以查看不同时间段的函数执行,可以用来查找高循环等问题
部署
部署需要解决的问题:
- 守护进程:当进程退出时,重新拉起
- 多进程:cluster便捷地利用多进程
- 记录进程状态,用于诊断
容器环境:通常有健康检查的手段,只需考虑多核cpu利用率即可