服务开发
Node.js适用场景
通用场景
- 通用的http/rpc接口
- 通用的定时任务/队列消费任务
- 能够胜任的大部分的业务逻辑
优势场景
- BFF前端“胶水层”
- SSR服务端页面渲染
- 同构的Web应用
- 实时通讯服务(WebSocket)
Node.js能够胜任大部分与前端密切相关的任务,而且优势场景也是非常多,现在社交和直播的websocket底层就是利用Node.js提供的io库
那么我们怎么在复杂的业务中保障稳定性和性能呢?
稳定性与性能
稳定性的统计指标:SLA,指标分以下三种
- 3个9:99.9% = 8760 * 0.1% = 8.72小时
- 4个9:99.99% = 8760 * 0.01% = 0.872小时 = 52.6分钟
- 5个9:99.999% = 8760 * 0.001% = 0.0872小时 = 5.26分钟
就是说一年的时间内,服务器宕机的时间有以上三个标准,一年宕机8.27个小时和52.6分钟和5.26分钟。我们知道,宕机就是抛锚,什么业务都无法展开,所以宕机时间越短越好,5个9的标准就是最高标准
性能的统计指标
- 响应时间RTT(发起请求到接收到请求的时间)
- 单位时间处理能力QRS/TPS(当前服务每秒能处理的最大请求或者最大事务的数量)
- 并发数Coucurrency(当前服务同时能处理多少请求)
- 错误率Error Rate(判断status code非200的数量,或者统计catch的数量)
资源的统计指标
- CPU Load (cpu占用率)
- Memory Usage(内存的使用量)
- FD Count(文件描述符的用量)
- Disk Read/Write(磁盘io读写)
- Network Send/Recv(网络io的吞吐)
稳定性保障
在开发阶段包装服务稳定性
异常捕获
如果我的代码中有代码的粗心或者其他原因,引入第三方库没有禁止,可以使用异常捕获,拿到错误的原因和日志,帮助我们解决问题
process
.on('unhandleRejection',(reson)=>{
//send error logs
//...
})
.on('uncaughException',err=>{
//send error logs
//...
process.exit(1)
})
复制代码
自动重启
基于class的模块下,在子进程退出捕获后,由父进程自动fork重启这个子进程,可以保证在多进程的架构下,单进程挂掉可以迅速重启一个,避免宕机
// in cluster mode
cluster.on('exit',function(worker,code,signal){
if(worker.suicide){
//send error logs
cluster.fork()
}
})
复制代码
健康检查
在代码中实现健康检查,隔一段时间通过soket链接一下服务是否还是可用的,及时发现进程在运行中的异常
//check whether the port is available
function connectToport(
port:number,
host?:string,
timeout?:number
):Promise<void>{
return new Promise((resolve,reject)=>{
const socket = new net.Socket()
const onError = (message:string)=>{
socket.destroy(),
reject(message)
}
})
socket.setTimeout(timeout ?? 1000)
socket.once("error",(e)=> onError(e.message))
socket.once("timeout",()=> onError("TIMEOUT"))
socket.connect({ port, host },()=>{
socket.end();
resolve()
})
}
复制代码
性能保障
Node.js指标统计工具
利用Node.js API获取指标
CPU Usage
- user cpu time (用户CPU时间)
- system cpu time(系统CPU时间)
Memory Usage
- heapTotal/heapUsed(V8heap占用的内存数)
- external(V8堆外内存占用数)
- array buffers
- rss
IO Usage
- fsRead/fsWrite
- ipcSent/ipcReceived
const {
cpuUsage,
memoryUsage,
resourceUsage,
} = require('process')
console.log(cpuUsage())
//{ user:38579,system:6986}
console.log(memoryUsage())
//Prints:{
// rss:4935680,
// heapTotal:1826816,
// heapUsed:650472,
// external:48979,
// arrayBuffers:9386
//}
复制代码
通过系统指令获取服务容器指标
Linux的任务管理器,当前容器的一系列指标
可以看到当前某个进程打开的所有的文件描述符
数据收集&&可视化
在服务端或容器镜像内封装通用的实时上报指标Metrics,收集数据并通过可视化看板展示,有助于我们时刻掌握当前服务的状态
Grafana埋点看板示例
发布部署
早期部署方案
-
购买/租用服务器/公网IP域名
-
安装操作系统、搭建内网环境以及一系列基础设施工具
-
通过FTP/RSYNC等手段上传生产环境代码包
-
在对应路径下执行启动命令
-
早期通过nohub实现以daemon方式运行
nohub `NODE_ENV = production node test.js` 2>&1& 复制代码
-
通过pm2/框架相关启动脚本运行
pm2 start app.js -i max 复制代码
-
-
购买域名,配置DNS,以及反向代理到服务下
所有的一切都是手工运行,对开发人员非常不友好
IaaS/Paas/FaaS的发展路线
IaaS:云计算的初级阶段
基础设置即服务(英语:Infrastructure as a Service,简称IssS)是云服务厂商提供消费者处理、储存、网络以及各种基础运算资源,以部署与执行操作系统或应用程序等各种软件
IaaS是云服务的最底层,主要提供一些基础资源。用户无须购买服务器、软件等网络设备,即可任意部署和运行处理、存储、网络和其他基本的计算资源,不能控管或控制底层的基础设施,但是可以控制操作系统、存储装置、已部署的应用程序
虚拟主机/VPS代表产品
- AWS EC2
- Aliyun ECS
- 腾讯云 云服务器
技术实现
- 虚拟机KVM/OpenVZ/Hyper-V
- OpenStack
- Docker
PaaS:主流的应用托管发布形态
平台即服务(Platform as a Service)是一种云计算服务,提供运算平台与解决方案
PaaS提供软件部署平台(runtime),抽象掉了硬件和操作系统的细节,可以无缝的扩展(scaling)。开发者只需要关注自己的业务逻辑,不需要关注底层
PaaS代表产品
- Google AppEngine
- Heroku
- AWS Elastic Beanstalk
- Vercel
技术实现
-
Docker容器部署应用/Docker Swarm
-
Kubernetes
- 服务编排
- 弹性扩缩容
- ……
基于PaaS的发布流程
大多数PaaS平台都提供了Node.js服务的运行和支持
我们根据PaaS平台提供的Node.js Runtime规范给应用编写
构建脚本(npm install)、启动脚本(npm start)以及应用配置脚本(app.yml)
以Vercel为例,我们可以绑定Git Repository后直接发布
基础Runtime不满足应用要求时,PaaS平台也支持通过Dockerfile定制特殊的功能以及启动命令
以Heroku为例,支持通过CLI的方式发布一个Container
PaaS与DevOps
DevOps(Development和Operations的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。通过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更快的快捷、频繁和可靠
现代PaaS平台提供了基础的DevOps流程,通过绑定Git Branch实现自动化集成发布到Perview环境,极大地简化了发布测试上线的流程
PaaS与自动扩缩容
得益于Kubernetes的能力,以及通用的PaaS应用Runtime,现代PaaS服务支持定义实例的性能,并支持在请求激增、CPU/内存吃紧时快速扩容实例,以及在请求量小、资源充裕是缩容,从而减少运维成本和服务费用
当然,快速扩缩容的前提是在Runtime侧已经实现了性能指标监控与数据上报的能力
serverless理念和产品
“Serverless computing is a cloud computing execution model in which the cloud provider allocates machine resources on demand, taking care of the servers on behalf of the customers”
Serverless最佳形态:FaaS + BaaS
-
FaaS(Lambda):“函数即服务”,是一种在无状态容器中运行的事件驱动型计算执行模型,这些函数将利用服务来管理服务器端逻辑和转态。它允许开发人员以功能来构建、运行和管理这些应用包,无须维护自己的基础架构。
- AWS Lambda
- Google Cloud Function
- Aliyun函数计算FC
- 腾讯云函数
-
BaaS:后端即服务,使开发人员可以专注于应用程序的前端,无需构建或维护后端服务即可利用
- Google Firebase
FaaS实现(Lambda)的局限性
FaaS按量计费的原则在传统容器部署的技术方案下难实现
- 容器冷启动+服务启动需要秒级的事件
- 常驻实例+待命模式可以保证首次访问的效率,则按量计费要求无法满足
Node.js基于VM模块实现“高密度部署”
- 函数之间的隔离性
- 死循环的恢复
const vm = require('vm')
const runFunction = async (code)=>{
const result = await new Promise((resolve,reject)=>{
const sandbox = { require,console }
try {
timer = setTimeout(()=>{
reject(new Error('Execute function time out'))
},1000)
vm.createContext(sandbox)
const data = vm.runInNewContext(code,sandbox)
resolve(data)
}catch (error){
reject(error)
}
}).catch((err)=>{
return err instanceof Error ? err : New Error(err.stack)
})
if(timet){
clearTimeout(timer)
timer = null
}
return result
}
复制代码
另辟蹊径:WASM/V8 Worker替代Node.js
- Deno Deploy
- Cloudflare Workers
- Wasm Edge
FaaS vs PaaS
FaaS 与PaaS在开发体验上的对比
- 函数模型过于简单
- 编写多个云函数对工程化不友好
- 应用开发者更希望编写/发布一个完成的Node.js WebApp
Jamstack模式与Vercel的探索:将PaaS应用构建成若干FaaS函数后发布
监控运维
日志埋点与监控报警
-
日志
- process.stdout / process.stderr
- send through udp socket
-
埋点报警
- Metrics
- Span
- Trace
线上排查问题
这段生产服务前,请务必拉出集群,以免影响外部用户
Node.js Inspector
Node.js提供了Inspector模块,支持对运行中的服务开启调试
const inspector = require('inspector')
inspector.open()
console.log(inspector.url())
复制代码
Inspector还支持在运行时打HeapSnapshot/CPUProfile,排查CPU/内存问题
const inspector = require('inspector')
const fs = require('fs')
const session = new inspector.Session()
const fd = fs.openSync('profile.heapsnapshot','w')
session.connect()
session.on('HeapProfiler.addHeapSnapshotChunk',(m)=>{
fs.writeSync(fs,m.params.chunk)
})
session.post('HeapProfiler.takeHeapSnapshot',null,(err,r)=>{
console.log('HeapProfiler.takeHeapSnapshot done:',err,r)
session.disconnect()
fs.closeSync(fd)
})
复制代码
strce与tcpdump:更通用的系统诊断工具
- tcpdump可以捕获实际网络传输中的数据,对于Web Server的开发场景来说十分有用
- strace则可以明确输出applications和kernel之间的每一个syscall的参数及返回结果,是了解系统调用的万用工具
strace:查看syscall的利器
编写一个http server,启动后通过strace -p查看并分析系统调用
const http = require('http')
//输出pid方便后面strace使用
console.log(process.pid)
const server = http.createServer((req,res)=>{
res.end('hello')
})
server.listen(()=>{
const {port} = server.address()
setInterval(()=>{
const req = http.get(`http://127.0.0.1:${port}`,(res)=>{
res.on('data',()=>{})
})
req.end('hello')
},1000)
})
复制代码
tcpdump:通用抓包工具
tcpdump作为跨平台抓包工具,可以让我们看到在网络设备中传输的每一个请求包。并且在Windows/Mac/Linux上使用
常用筛选命令
- host/net用于指定请求host/ip
- port用于指定请求的端口
- dst和src用于指定规则是用于指定一个packet的来源还是去向
- and和or逻辑上的与、或关系,用户组合起多组筛选规则
tcpdump:使用Wireshark查看抓包结果
tcpdump支持将抓包的报文写入文件,并通过Wireshark查看结果