概述
node(14.2) 官网API
进阶API
进阶模块主要包括 性能分析模块和实验模块
assert
assert 模块提供了一组断言函数,用于验证不变量。
-
断言
断言是对程序某些假设的检查,用来精确捕获程序逻辑的错误,能够做一些类似预处理的事情
-
断言与异常
断言注重程序逻辑的错误,异常注重语法,环境等不可控因素导致的错误
断言以及单元测试库
断言经常被应用在单元测试中,下面列举常用的node断言和测试框架
-
断言库
- assert node自带断言库,提供简单的断言处理
- shouldjs should拥有丰富的断言类型,提供符合语义描述的语法
- expectjs expectjs 是fb开源的断言库,现已合并到jest框架中
- chai chai提供了should、expect和assert三种风格的断言方式
-
单元测试框架
- jest fb开源的测试框架,只支持expect
- mocha 生态丰富的测试框架,不限制断言库,常配合chai使用
async_hooks / domain
async_hooks 和 domain 提供了对异步资源追踪和处理的能力,domain模块已被官方废弃,async_hooks作为domian的补充,但当前仍是实验性模块
-
异常和监控
try...catch...
可以捕获大部分的异常,但不能捕获异步错误,所以对于异步错误的处理更多是通过监听异常事件process.on('uncaughtException', function (err) {});
来完成,这导致node的异常处理和监控变得很棘手,所以相继推出了 domain 和 async_hooks 来完成对node异步的监控 -
async_hooks 生成traceId
async_hooks 对每一个函数(不论异步还是同步)提供了一个 async scope,我们可以通过调用 async_hooks.executionAsyncId() 来获取函数当前的 async scope 的 id(称为 asyncId),通过调用 async_hooks.triggerAsyncId() 来获取当前函数调用者的 asyncId(即triggerAsyncId)。
async_hooks.createHook 可以注册 4 个方法来跟踪所有异步资源的初始化(init)、回调之前(before)、回调之后(after)、销毁后(destroy)事件,并通过调用 .enable() 启用,调用 .disable() 关闭。
异步资源在创建时触发 init 事件函数,init 函数中的第 1 个参数代表该异步资源的 asyncId,type 表示异步资源的类型(例如 TCPWRAP、PROMISE、Timeout、Immediate、TickObject 等等),triggerAsyncId 表示该异步资源的调用者的 asyncId。异步资源在销毁时触发 destroy 事件函数,该函数只接收一个参数,即该异步资源的 asyncId。
在以上特性的支持下,就可以根据triggerAsyncId和asyncId的关系定位到函数之间的调用链关系,随即可以生成traceId完成链路调用的日志记录,以下是构造全链路traceId的代码
'use strict' const asyncHooks = require('async_hooks'); const fs = require('fs') const uuid = require('uuid').v4 const writStream = fs.createWriteStream('trace.log') // 存储上下文 const logCtxs = new Map() // 通过asyncHooks的init 和 destroy 维护和追踪上下文关系 asyncHooks.createHook({ init, destroy }).enable(); // 维护异步调用关系 function init(asyncId, type, triggerAsyncId) { const ctx = getCtx(triggerAsyncId) if (ctx) { logCtxs.set(asyncId, ctx) } else { logCtxs.set(asyncId, triggerAsyncId) } } // 删除失效对象 function destroy(asyncId) { logCtxs.delete(asyncId) } function getCtx(tid) { if (!tid) { return } if (typeof tid === 'object') { return tid } return getCtx(logCtxs.get(tid)) } function log(args){ const [trigger, asyncId] = [asyncHooks.triggerAsyncId(), asyncHooks.executionAsyncId()] const ctx = getCtx(asyncId) fs.writeSync(1, `{asyncId: ${asyncId}, trigger: ${trigger}, ctx: ${JSON.stringify(ctx)} }: ${args}nn`) } exports.log = log exports.logger = function logger() { return async (ctx, next) => { let reqContent = {main:ctx, traceId: null} reqContent.traceId = ctx.request.header.requestId || uuid() logCtxs.set(asyncHooks.executionAsyncId(),reqContent) log('start') await next() log(`end ${ctx.body}`) } }
-
延申阅读
错误栈定位
CLS
zone.js
string_decoder
string_decoder 模块提供了一个 API,用一种能保护已编码的多字节 UTF-8 和 UTF-16 字符的方式将 Buffer 对象解码为字符串
-
应用场景
使用string_decoder时,当传入的buffer不完整(比如三个字节的字符,只传入了两个),内部会维护一个internal buffer将不完整的字节cache住,等到使用者再次调用stringDecoder.write(buffer)传入剩余的字节,来拼成完整的字符。这样可以有效避免buffer不完整带来的错误,试用于比如网络请求中的包体解析等。
const StringDecoder = require('string_decoder').StringDecoder; const decoder = new StringDecoder('utf8'); // Buffer.from('你好') => <Buffer e4 bd a0 e5 a5 bd> let str = decoder.write(Buffer.from([0xe4, 0xbd, 0xa0, 0xe5, 0xa5])); console.log(str); // 你 str = decoder.write(Buffer.from([0xbd])); console.log(str); // 好
cluster
单个 Node.js 实例运行在单个线程中。 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务。cluster 模块可以创建共享服务器端口的子进程。
-
工作原理
cluster 模块使用 child_process.fork() 生成工作进程, 之前在子进程模块中提到过fork的特殊性(fork方法是spawn的一个特例,专门用于衍生新的 Node.js 进程,返回的 ChildProcess 将会内置一个额外的通信通道,允许消息在父进程和子进程之间来回传递),由fork创建的子进程可以和父进程之间通过IPC通信,cluster借助了这一特性,将连接分发给worker进程,从而实现集群化
连接分发支持两种方式:
- 主进程监听端口,接受新连接后将连接循环发给工作进程
- 主进程创建监听socket 后发送给感兴趣的工作进程,由工作进程负责直接接受连接
-
实践
const cluster = require('cluster') const cpuNums = require('os').cpus().length const http = require('http') if (cluster.isMaster) { // 启动 worker for (let i = 0; i < cpuNums; i++) { const worker = cluster.fork(); console.log('fork a worker', worker.process.pid) } // 异常退出处理 cluster.on('exit', (worker, code, signal) => { if (code !== 0) { console.error(`worker:${worker.process.pid} 异常退出(${signal || code}),1s后尝试重启...`); setTimeout(() => { const new_worker = cluster.fork(); console.log(`worker:${new_worker.process.pid} 正在运行...`); }, 1000); } else { console.log(`worker:${worker.process.pid} 正常退出!`); } }); } else { // 工作进程有一个 http 服务器。 http.Server((req, res) => { res.writeHead(200); res.end('你好n'); }).listen(8000); }
-
基于cluster模块衍生的进程管理工具
-
PM2
pm2 进程管理工具,以守护进程的方式管理托管在其下的进程,支持fork 和 cluster 两种模式,fork模式可以支持其他语言如python, golang等,cluster模式只支持node。
pm2主要由以下组件合作完成工作-
client
client(执行启动进程的进程),如果我们是用控制台来启动,那么这个Client时候可以理解为当前执行命令的这个进程。一般这个Client执行完就被销毁了。client与daemon之间通过rpc的方式通信。 -
daemon
daemon是一个后台常驻进程,相当于一个管理员的角色,pm2 start这类启动命令里都会去检查daemon是否存在,不存在会启动。
-
God
daemon的核心是God。God上定义了诸多方法,God上还挂载了一个clusters_db,记录着进程列表和状态,供各处查询使用。God进程可以通过child_process.spawn() 或者 cluster.fork() 生成子进程,God和子进程之间通过消息进行通信
-
-
egg-scripts
egg-scripts是egg框架自带的进程管理工具,其构建了三个进程角色,master,agent,worker。主要通过三者间的协作完成作业
-
master
master进程负责进程管理,进程间消息转发(woker和agent之间不直接通信,通过master转发,master)
-
worker
Worker 进程负责处理真正的用户请求和定时任务的处理
-
agent
agent 是一个附加进程,负责处理一些只需要在单个进程上处理的任务,比如日志切割, 定时任务等
-
-
dns
dns 模块用于启用名称解析,尽管以域名系统(DNS)命名,但它并不总是使用 DNS 协议进行查找。
-
dns 资源记录
dns资源包含以下信息:
-
domain 域名
-
ttl 生存周期
-
class 网络/协议类型
-
type 资源记录类型 A 、AAAA、NS等
-
rdata 资源记录数据
示例如下:
;; QUESTION SECTION: ;baidu.com. IN A ;; ANSWER SECTION: baidu.com. 280 IN A 220.181.38.148 baidu.com. 280 IN A 39.156.69.79 ;; Query time: 0 msec ;; SERVER: 10.143.22.118#53(10.143.22.118) ;; WHEN: Thu Jul 16 13:36:32 CST 2020 ;; MSG SIZE rcvd: 59
-
-
常用方法
-
lookup 域名查询
lookup 使用操作系统功能来执行名称解析,是node中网络API默认调用的域名解析方法,lookup通过调用底层的getaddrinfo完成解析,由于getaddrinfo被同步阻塞调用,所以lookup 解析域名可能会造成事件循环的阻塞,从而产生负面影响
-
resolve DNS 查询
resolve函数簇也可以用来解析域名,通过异步的方式连接DNS服务解析,不适用线程池资源,但其不能解析本地hosts文件中配置的域名映射
-
reverse 反向 DNS 查询
reverse可进行反向域名解析,同resolve一样,以异步的方式解析
-
-
延伸阅读
node dns 的坑
zlib
zlib 模块提供通过 Gzip、Deflate/Inflate、和 Brotli 实现的压缩功能。可用来压缩文件,数据流
-
压缩原理
数据压缩,是指按照规范,通过一定的算法,将数据编码,并通过重复标记等策略,减少数据体积的技术。一个文件会被压缩成一个数据集,如果要对多文件(文件夹)进行压缩,需要先将多文件归档,经常是归档为一个tar文件,然后对tar文件进行压缩(可类比tar 命令的操作)
-
延伸阅读
压缩
node 压缩
-
node 压缩文件夹
const compressing = require('compressing'); compressing.tar.compressDir('node_modules', 'nodejs-compressing-demo.tar') .then(() => { return compressing.gzip.compressFile('nodejs-compressing-demo.tar', 'nodejs-compressing-demo.tgz'); }).then(() => { console.log('success'); }) .catch(err => { console.error(err); });
inspector
node提供的的和v8检查器交互的API,该模块属于实验性阶段,通过该模块可以对应用程序进行debug和性能分析
debugger
Node.js 包含了一个进程外的调试实用程序, 可通过 V8 检查器(同inspect交互)或内置的调试客户端访问
-
使用
把
debugger;
语句插入到脚本的源代码,则将会在代码中的该位置启用一个断点。通过inspect参数打开调试器node inspect scriptsj.js
,单步调试的命令如下cont, c: 继续执行。 next, n: 单步执行下一行。 step, s: 单步进入。 out, o: 单步退出。 pause: 暂停运行中的代码(类似于开发者工具中的暂停按钮)
perf_hooks
perf_hooks 是node提供的对资源调用进行性能分析的模块,类似浏览器的PerformanceObserver,二者皆采用W3C的performance timeline规范
-
性能分析
-
统计包加载时常
'use strict'; const { performance, PerformanceObserver } = require('perf_hooks'); const mod = require('module'); // Monkey patch the require function mod.Module.prototype.require = performance.timerify(mod.Module.prototype.require); require = performance.timerify(require); // Activate the observer const obs = new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach((entry) => { console.log(`require('${entry[0]}')`, entry.duration); }); obs.disconnect(); }); obs.observe({ entryTypes: ['function'], buffered: true }); require('some-module');
-
路由状态
fastify-routes-stats
-
-
异步资源监控
结合 async_hooks 对异步资源追踪,分析
'use strict'; const async_hooks = require('async_hooks'); const { performance, PerformanceObserver } = require('perf_hooks'); const set = new Set(); const hook = async_hooks.createHook({ init(id, type) { if (type === 'Timeout') { performance.mark(`Timeout-${id}-Init`); set.add(id); } }, destroy(id) { if (set.has(id)) { set.delete(id); performance.mark(`Timeout-${id}-Destroy`); performance.measure(`Timeout-${id}`, `Timeout-${id}-Init`, `Timeout-${id}-Destroy`); } } }); hook.enable(); const obs = new PerformanceObserver((list, observer) => { console.log(list.getEntries()[0]); performance.clearMarks(); observer.disconnect(); }); obs.observe({ entryTypes: ['measure','gc'], buffered: true }); setTimeout(() => {}, 1000);
readline
readline 是基于event 提供的一个用于行读可读流中数据的API;
-
交互式终端模拟
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '请输入> ' }); rl.prompt(); rl.on('line', (line) => { switch (line.trim()) { case 'hello': console.log('world!'); break; default: console.log(`你输入的是:'${line.trim()}'`); break; } rl.prompt(); }).on('close', () => { console.log('再见!'); process.exit(0); });
-
行读文件
const fs = require('fs'); const readline = require('readline'); const rl = readline.createInterface({ input: fs.createReadStream('sample.txt'), crlfDelay: Infinity }); rl.on('line', (line) => { console.log(`文件中的每一行: ${line}`); });
repl
repl 是一个交互式循环解释API,可以接收用户的输入,然后输出结果,可以提供简单方便的入口执行node代码,学习语法,新特性等;也可以基于repl
魔改一个定制的node 终端解释器
tls
tls是传输层安全协议,它的前身是安全套接层(Secure Sockets Layer,缩写作SSL), 是一个被应用程序用来在网络中安全通信的 protocol (通讯协议),防止电子邮件、网页、消息以及其他协议被篡改或是窃听。
-
延申阅读
SSl详解
ssl/tls详解
trace_events
trace_events模块提供了一种集中化由V8,Node.js核心模块和用户空间代码生成的跟踪信息的机制。该模块为实验模块,可通过node 参数开启, 如node --trace-events-enabled
默认开启 node, node.async_hooks v8 三种事件
-
分析工具
开启trace_event后,收集到的是json格式的数据日志,可以通过以下工具可视化查看
-
chrome
-
perfetto
如下图通过chrome查看
-
-
trace event 数据格式解读
强大的可视化利器 Chrome Trace Viewer 使用详解
v8
v8 模块暴露了特定于内置到 Node.js 二进制文件中的 V8 版本的 API。通过该模块可以读取v8堆快照数据。
-
v8堆内存和GC
-
堆内存结构
-
v8 GC
-
-
延申阅读
Node.js内存管理和V8垃圾回收机制
vm
vm 模块提供了在v8虚拟机的上下文中编译和执行代码的能力,在一些需要执行动态脚本的场景中(如fass,频繁变更规则的脚本),可以通过vm便捷的实现功能
-
特性
-
Script
创建一个新的 vm.Script 对象只编译 code 但不会执行它
-
runInContext
在给定对象的上下文中编译执行指定的代码,不能访问本地作用域
-
runInThisContext
在当前global对象的上下文中编译执行指定代码,运行中的代码无法获取本地作用域,但可以获取当前的 global 对象
-
runInNewContext
在给定对象的上下文中编译执行指定的代码,不能访问本地作用域。如果给定的对象为undefined,则新建一个对象。
-
-
vm,eval,new Function比较
vm,eval,new Function 都可以用来执行动态脚本,但是eval , new Function存在明显的安全问题,没有沙箱环境,可以轻易访问到其他作用域。而vm 为指定的动态脚本提供了一个独立的沙箱环境,在一定程度上保证了安全性。
-
vm2
vm模块虽然提供了独立的沙箱环境,但是在特殊场景下,可能会出现沙箱逃逸的问题。vm2就是为解决这一问题而出现,它基于vm模块,使用代理来防止沙箱逃逸。
-
业务适用场景
-
第三方脚本处理
例如运营活动处理,执行定时数据处理脚本等
-
构建fass模型
- vm
- safeify
-
worker_threads
worker_threads提供了并行执行js的线程,可以用来处理cpu密集型的任务,之前说过cluster模式也可以通过多进程的方式来处理cpu密集型任务,worker_threads相比之下可以共享内存资源。
-
工作原理
-
实战
-
延申阅读
- 深入理解worker_threads
- 多线程指南
wasi
wasi API提供了WebAssembly系统接口规范的实现。该模块当前为实验模块。
WebAssembly 是一个可移植、体积小、加载快并且兼容 Web 的二进制格式,WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下。
-
延申阅读
- webAssembly介绍
- c++项目转wasm
- WebAssembly现状与实战
tty
正如官方所说,tty模块是一个几乎不会直接使用的模块,暂时没有找到相应应用场景,或许可以做一些个性化终端的应用
最后
以上就是文静乌冬面为你收集整理的保护我方输出nodejs(三)node(14.2) 官网API的全部内容,希望文章能够帮你解决保护我方输出nodejs(三)node(14.2) 官网API所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复