Node8의 AsyncHooks 비동기식 라이프 사이클

Async Hooks는 Node8의 새로운 기능으로 일부 API를 제공하여 NodeJs의 비동기 자원의 생명주기를 추적하는 데 사용되며 NodeJs 내장 모듈에 속하여 직접 인용할 수 있다.

const async_hooks = require('async_hooks');
이것은 매우 적게 사용하는 모듈인데, 왜 이 모듈이 있습니까?
우리는 모두 자바스크립트가 디자인 초기에 단일 루틴 언어였다는 것을 알고 있다. 이것은 그의 디자인 취지와 관련이 있다. 최초의 자바스크립트는 단지 페이지의 폼 검사를 하는 데 사용되었을 뿐이고 낮은 네트워크 속도 시대에 사용자가 서버의 응답을 기다리는 시간 비용을 낮춘다.웹 전단 기술의 발전에 따라 전단 기능이 점점 강해지고 중시되고 있지만 단선도 해결할 수 없는 문제가 없는 것 같다. 비교해 보면 다선도는 더욱 복잡한 것 같아서 단선도는 여전히 지금까지 사용되고 있다.
JavaScript는 단일 라인이지만 일상적인 개발에서 항상 시간이 많이 걸리는 작업이 있습니다. 예를 들어 타이머, 그리고 현재 표준화된 Ajax, JavaScript는 이런 문제를 해결하기 위해 자신을 BOM,DOM,ECMAScript로 나눈다. BOM은 우리가 이런 시간이 소모되는 임무를 해결하는 것을 비동기적인 임무라고 합니다.
브라우저의 BOM이 비동기화 작업을 처리해 주었기 때문에 대부분의 프로그래머들은 비동기화 작업에 대해 거의 아무것도 모른다. 예를 들어 대기열에 몇 개의 비동기화 작업이 있는지?비동기적인 정체 여부 등은 우리가 직접 관련 정보를 얻을 수 없는 것이다. 많은 경우 밑바닥에서도 우리가 관련 정보를 주목할 필요가 없다. 그러나 만약에 우리가 어떤 상황에서 관련 정보를 원할 때 NodeJS는 우리가 사용할 수 있는 Experimental API를 제공했다. 즉, async_hooks.왜 NodeJS일까요? Node에서 타이머, http 같은 비동기 모듈만 개발자가 제어할 수 있기 때문에 브라우저의 BOM은 개발자가 제어하지 않습니다. 브라우저가 대응하는 API를 제공하지 않는 한.

async_hooks 규칙


async_hooks는 모든 함수에 상하문을 제공할 것을 약속합니다. 우리는 asyncscope라고 합니다. 모든 asyncscope에는 asyncId가 있습니다. 이것은 현재 asyncscope의 표지입니다. 같은 asyncscope에서asyncId는 반드시 같습니다.
이것은 여러 개의 비동기 임무가 병행될 때, asyncId는 우리로 하여금 감청하고자 하는 것이 어떤 비동기 임무인지 잘 구분할 수 있게 한다.
asyncId는 자동으로 증가하는 중복되지 않는 정수이며, 프로그램의 첫 번째 asyncId는 반드시 1이다.
syncscope는 통속적으로 말하면 중단할 수 없는 동기화 작업입니다. 중단할 수 없는 경우, 아무리 긴 코드를 사용해도 asyncId를 사용합니다. 그러나 중간에 중단할 수 있습니다. 예를 들어 리셋, 예를 들어 중간에 await가 있으면 새로운 비동기적인 위아래 문장을 만들고 새로운 asyncId를 만들 수 있습니다.
모든 asyncscope에는 triggerAsyncId가 있습니다. 현재 함수는 그 asyncscope가 촉발하여 생성된 것입니다.
asyncId와triggerAsyncId를 통해 우리는 전체 비동기적인 호출 관계와 체인을 편리하게 추적할 수 있다.
async_hooks.executionAsyncId()는 asyncId를 가져오는 데 사용되며, 전역의 asyncId는 1입니다.
async_hooks.triggerAsyncId()는 triggerAsyncId를 가져오는 데 사용되며, 현재 값은 0입니다.

const async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // triggerAsyncId: 0
우리는 여기에서 fs를 사용한다.open 파일을 열면 fs를 발견할 수 있습니다.open의 asyncId는 7이고 fs.open의 trigger AsyncId가 1이 된 이유는 fs 때문입니다.open은 전역 호출로 촉발되며, 전역의 asyncId는 1입니다.

const async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // triggerAsyncId: 0
const fs = require('fs');
fs.open('./test.js', 'r', (err, fd) => {
    console.log('fs.open.asyncId:', async_hooks.executionAsyncId()); // 7
    console.log('fs.open.triggerAsyncId:', async_hooks.triggerAsyncId()); // 1
});

비동기 함수의 생명주기


물론 실제 응용 중인 async_hooks는 이렇게 사용하지 않습니다. 그의 정확한 사용법은 모든 비동기적인 작업이 생성되고 실행되기 전, 실행 후, 삭제된 후에 리셋을 터치하는 것입니다. 모든 리셋은 asyncId로 전송됩니다.
우리는 async_를 사용할 수 있다hooks.createHook은 비동기 자원의 갈고리를 만듭니다. 이 갈고리는 대상을 매개 변수로 받아들여 비동기 자원의 생명 주기에 발생할 수 있는 이벤트에 대한 리셋 함수를 등록합니다.비동기식 자원이 생성/실행/제거될 때마다 이 갈고리 함수는 촉발됩니다.

const async_hooks = require('async_hooks');

const asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) { },
  destroy(asyncId) { }
})

현재createHook 함수는 다음과 같은 5가지 종류의 Hook Callbacks를 사용할 수 있습니다.
1.init(asyncId, type, triggerAsyncId, resource)
  • init 리셋 함수는 일반적으로 비동기 자원이 초기화될 때 촉발된다.
  • asyncId: 모든 비동기 자원은 유일한 표지판을 생성한다
  • type: 비동기 자원의 유형은 일반적으로 자원의 구조 함수의 이름이다.
  • FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE,
    HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP,
    SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP,
    TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
    RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject
  • triggerAsyncId: 현재 비동기 자원이 생성된 대응하는 asyncscope를 촉발하는 asyncId
  • resource: 초기화된 비동기 자원 대상
  • 을 나타낸다
    우리는 async_를 통해hooks.createHook 함수는 모든 비동기 자원이 생명주기에 발생하는 init/before/after/destory/promiseResolve 등 관련 이벤트에 대한 감청 함수를 등록합니다.
    같은 asyncscope는 여러 번 호출되고 실행될 수 있습니다. 아무리 실행해도 asyncId는 반드시 같습니다. 감청 함수를 통해 우리는 그 실행 횟수와 시간 및 상위권 관계를 추적하기 쉽습니다.
    2.before(asyncId)
    before 함수는 일반적으로 asyncId에 대응하는 비동기 자원 작업이 끝난 후에 리셋을 실행하기 전에 호출됩니다. before 리셋 함수는 여러 번 실행될 수 있습니다. 리셋된 횟수에 따라 결정됩니다. 사용할 때 주의해야 합니다.
    3.after(asyncId)
    after 리셋 함수는 일반적으로 비동기 자원에서 리셋 함수를 실행한 후 바로 호출됩니다. 리셋 함수를 실행하는 과정에서 포착되지 않은 이상이 발생하면 after 이벤트는'uncaught Exception'이벤트를 터치한 후에 호출됩니다.
    4.destroy(asyncId)
    asyncId에 대응하는 비동기 자원이 소각될 때 호출됩니다. 일부 비동기 자원의 소각은 쓰레기 회수 메커니즘에 의존해야 하기 때문에 일부 상황에서 메모리 유출로 인해destory 사건은 영원히 촉발되지 않을 수 있습니다.
    5.promiseResolve(asyncId)
    Promise 구조기의resovle 함수가 실행되면promiseResolve 이벤트가 촉발됩니다.어떤 경우, 어떤 resolve 함수는 은밀하게 실행된다. 예를 들어.then 함수는 새 Promise를 되돌려줍니다. 이때 호출됩니다.
    
    const async_hooks = require('async_hooks');
    
    //   asyncId
    const eid = async_hooks.executionAsyncId();
    
    //   asyncId
    const tid = async_hooks.triggerAsyncId();
    
    //  AsyncHook 。 
    const asyncHook =
        async_hooks.createHook({ init, before, after, destroy, promiseResolve });
    
    //    
    asyncHook.enable();
    
    //  。
    asyncHook.disable();
    
    function init(asyncId, type, triggerAsyncId, resource) { }
    
    function before(asyncId) { }
    
    function after(asyncId) { }
    
    function destroy(asyncId) { }
    
    function promiseResolve(asyncId) { }
    
    

    Promise


    promise는 비교적 특수한 상황입니다. init 방법의 type에 충분하면 PROMISE가 없다는 것을 발견할 수 있습니다.ah만 사용한다면.executionAsyncId()로 Promise의 asyncId를 가져오면 정확한 ID를 얻을 수 없습니다. 실제 훅을 추가한 후에만 async_hooks는 Promise의 리셋에 asyncId를 생성합니다.
    다시 말하면 V8은 asyncId를 얻는 데 집행 비용이 비교적 높기 때문에 기본적으로 Promise에 새로운 asyncId를 분배하지 않습니다.
    즉, 기본적으로 우리가promises나async/await를 사용할 때 현재 상하문에서 정확한 asyncId와triggerId를 얻을 수 없습니다.하지만 괜찮아, 우리는 async_를 실행할 수 있어.hooks.createHook(callbacks).enable() 함수는 Promise에 asyncId를 할당하도록 강제로 설정합니다.
    
    const async_hooks = require('async_hooks');
    
    const asyncHook = async_hooks.createHook({
      init(asyncId, type, triggerAsyncId, resource) { },
      destroy(asyncId) { }
    })
    
    
    asyncHook.enable();
    
    Promise.resolve(123).then(() => {
      console.log(`asyncId ${async_hooks.executionAsyncId()} triggerId ${async_hooks.triggerAsyncId()}`);
    });
    
    
    또한 Promise는 init와 promise Resolve 갈고리 이벤트 함수만 터치하고, before와 after 이벤트의 갈고리 함수는 Promise의 체인 호출 때만 터치합니다. 즉, 있습니다.then/.catch 함수에서 생성된 Promise는 트리거됩니다.
    
    new Promise(resolve => {
        resolve(123);
    }).then(data => {
        console.log(data);
    })
    
    위의 두 개의 Promise가 존재하는 것을 발견할 수 있습니다. 첫 번째는 new 실례화로 만든 것이고, 두 번째는 then에서 만든 것입니다. (알 수 없는 것은 이전의 Promise 원본 문장을 볼 수 있습니다.)
    이 순서는 new Promise를 실행할 때 자신의 init 함수를 호출하고, Resolve를 실행할 때promise Resolve 함수를 호출하는 것입니다.이어서 then 방법에서 두 번째 Promise의 init 함수를 실행하고 두 번째 Promise의 before,promise Resovle,after 함수를 실행합니다.

    이상 처리


    등록된 async-hook 리셋 함수에 이상이 발생하면 서비스는 오류 로그를 출력하고 종료합니다. 모든 de 감청기가 제거되고'exit'이벤트 종료 프로그램을 터치합니다.
    프로세스가 바로 종료되는 이유는 만약에 이 async-hook 함수가 불안정하게 운행된다면 다음 같은 사건이 촉발될 때 이상이 발생할 수 있기 때문이다. 이 함수들은 주로 비동기적인 사건을 감청하기 위해서이다. 불안정하면 제때에 발견하고 수정해야 한다.

    비동기 갈고리 리셋에서 로그 인쇄


    콘솔 때문에.log 함수도 비동기 호출입니다. 만약에 우리가 async-hook 함수에서 콘솔을 다시 호출한다면.log는 상응하는 훅 이벤트를 다시 터치하여 사순환 호출을 일으킬 수 있습니다. 따라서 async-hook 함수에서 동기화 인쇄 로그 방식으로 추적해야 합니다. fs를 사용할 수 있습니다.writeSync 함수:
    
    const fs = require('fs');
    const util = require('util');
    
    function debug(...args) {
      fs.writeFileSync('log.out', `${util.format(...args)}
    `, { flag: 'a' }); }
    [참고 문헌-Asynchooks](https://nodejs.org/dist/latest-v15.x/docs/api/async_hooks.html
    Node8에서 Async Hooks의 비동기 생명주기에 관한 이 글은 여기까지 소개되었습니다. 더 많은 Node Async Hooks의 비동기 생명주기 관련 내용은 저희 이전의 글을 검색하거나 아래의 관련 글을 계속 훑어보시기 바랍니다. 앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기