我是靠谱客的博主 文静乌冬面,最近开发中收集的这篇文章主要介绍保护我方输出nodejs(三)node(14.2) 官网API,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

node(14.2) 官网API

进阶API

进阶模块主要包括 性能分析模块和实验模块

assert

assert 模块提供了一组断言函数,用于验证不变量。

  • 断言

    断言是对程序某些假设的检查,用来精确捕获程序逻辑的错误,能够做一些类似预处理的事情

  • 断言与异常

    断言注重程序逻辑的错误,异常注重语法,环境等不可控因素导致的错误

断言以及单元测试库

断言经常被应用在单元测试中,下面列举常用的node断言和测试框架

  • 断言库

    1. assert node自带断言库,提供简单的断言处理
    2. shouldjs should拥有丰富的断言类型,提供符合语义描述的语法
    3. expectjs expectjs 是fb开源的断言库,现已合并到jest框架中
    4. chai chai提供了should、expect和assert三种风格的断言方式
  • 单元测试框架

    1. jest fb开源的测试框架,只支持expect
    2. 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进程,从而实现集群化

    连接分发支持两种方式:

    1. 主进程监听端口,接受新连接后将连接循环发给工作进程
    2. 主进程创建监听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主要由以下组件合作完成工作

      1. client
        client(执行启动进程的进程),如果我们是用控制台来启动,那么这个Client时候可以理解为当前执行命令的这个进程。一般这个Client执行完就被销毁了。client与daemon之间通过rpc的方式通信。

      2. daemon

        daemon是一个后台常驻进程,相当于一个管理员的角色,pm2 start这类启动命令里都会去检查daemon是否存在,不存在会启动。

      3. God

        daemon的核心是God。God上定义了诸多方法,God上还挂载了一个clusters_db,记录着进程列表和状态,供各处查询使用。God进程可以通过child_process.spawn() 或者 cluster.fork() 生成子进程,God和子进程之间通过消息进行通信

    • egg-scripts

      egg-scripts是egg框架自带的进程管理工具,其构建了三个进程角色,master,agent,worker。主要通过三者间的协作完成作业

      1. master

        master进程负责进程管理,进程间消息转发(woker和agent之间不直接通信,通过master转发,master)

      2. worker

        Worker 进程负责处理真正的用户请求和定时任务的处理

      3. agent

        agent 是一个附加进程,负责处理一些只需要在单个进程上处理的任务,比如日志切割, 定时任务等

dns

dns 模块用于启用名称解析,尽管以域名系统(DNS)命名,但它并不总是使用 DNS 协议进行查找。

  • dns 资源记录

    dns资源包含以下信息:

    1. domain 域名

    2. ttl 生存周期

    3. class 网络/协议类型

    4. type 资源记录类型 A 、AAAA、NS等

    5. 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格式的数据日志,可以通过以下工具可视化查看

    1. chrome

    2. perfetto

    如下图通过chrome查看
    image

  • 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模型

      1. vm
      2. 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所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(66)

评论列表共有 0 条评论

立即
投稿
返回
顶部