NodeバインディンググローバルTraceIDの実現方法


問題の説明
Node.jsのために 単スレッドモデルの制限は、グローバルトラッキングをセットすることができません。 出力ログと要求の結合を実現します。ログと要求のバインディングが実現されないと、ログ出力と対応するユーザ要求との対応関係を判断するのは難しいです。 オンライン問題の調査は困難をもたらした。
例えば、ユーザにアクセスする retrieveOne APIの場合、その呼び出しがあります。 retrieveOne Sub関数は、もし私達が retrieveOne Sub関数から現在の要求に対応する学生情報を出力するのは、煩雑です。course-seの既存の実現の下で、私達の針はこの問題に対する解決方法は:
  • 案1:呼び出し中 retrieveOne Sub関数の親関数、すなわち retrieveOne内で、はい。 paramDataで行います 学生に関する情報を出力するが、この案は ログを細かくして粒度を出力できませんでした。
  • 案2:修正 retrieveOne Sub関数署名、受信 paramDataはパラメータです。 ログ出力粒度を確保できますが、 チェーンが深い場合は、関数ごとに署名を変更して、それを受信する必要があります。 paramDataさん、仕事量が多くて、あまりできません。
  • 
    /**
     *            
     * @param {ParamData}  paramData
     * @param {Context}   ctx
     * @param {string}   id
     */
    export async function retrieveOne(paramData, ctx, id) {
     const { subModel } = paramData.ce;
     const sub_asgn_id = Number(id);
    
     //    paramData.user    user     ,  user_id ,
     //            ,     retrieveOneSub    ,
     //    paramData     。
     const { user_id } = paramData.user;
     console.log(`${user_id} is trying to retreive one submission.`);
     //     retrieveOneSub   。
     const sub = await retrieveOneSub(sub_asgn_id, subModel);
     const submission = sub;
     assign(sub, { sub_asgn_id });
     assign(paramData, { submission, sub });
     return sub;
    }
    
    /**
     *           
     * @param {number}   sub_asgn_id
     * @param {SubModel}   model
     */
    async function retrieveOneSub(sub_asgn_id, model) {
     const [sub] = await model.findById(sub_asgn_id);
     if (!sub) {
      throw new ME.SoftError(ME.NOT_FOUND, '      ');
     }
     return sub;
    }
    
    
    Aync Hook s
    実は、以上の問題に対して、NodeのAsync Hook s実験的なAPIから入手できます。Node.js v 8.xの後、公式提供しました。 非同期行為を傍受するAsync Hook(非同期フック)APIのサポート。
    Async Scope
    Async Hooksは、それぞれの(同期または非同期)関数に対してAsync Scopeを提供しています。呼び出し可能です。 execution AsyncIdメソッドは、現在の関数のAsync IDを取得し、呼び出します。 trigger AyncIdは、現在の関数の使用者のAync IDを取得する。
    
    const asyncHooks = require("async_hooks");
    const { executionAsyncId, triggerAsyncId } = asyncHooks;
    
    console.log(`top level: ${executionAsyncId()} ${triggerAsyncId()}`);
    
    const f = () => {
     console.log(`f: ${executionAsyncId()} ${triggerAsyncId()}`);
    };
    
    f();
    
    const g = () => {
     console.log(`setTimeout: ${executionAsyncId()} ${triggerAsyncId()}`);
     setTimeout(() => {
      console.log(`inner setTimeout: ${executionAsyncId()} ${triggerAsyncId()}`);
     }, 0);
    };
    
    setTimeout(g, 0);
    setTimeout(g, 0);
    
    
    上記のコードの中で使用します。 set Timeoutは非同期呼出しプロセスをシミュレートし、非同期プロセスではhandler同期関数を呼び出して、それぞれの関数で対応するAsync IDとTrigger Aync IDを出力します。上記のコードを実行したら、その運転結果は以下の通りです。
    
    top level: 1 0
    f: 1 0
    setTimeout: 7 1    
    setTimeout: 9 1    
    inner setTimeout: 11 7
    inner setTimeout: 13 9
    上記ログ出力により、以下の情報が得られます。
  • は、関数f内のAsync IDとその使用者のAync IDと同じように、同期関数を呼び出します。
  • の同じ関数は、異なるタイミングで非同期呼出しされ、上記のコードのg関数のような異なるAsync IDに割り当てられます。
  • 非同期のリソースを追跡
    前に述べたように、Aync Hookは非同期リソースを追跡するために使用できます。この目的を達成するためには、Async Hooksの関連APIを理解し、以下のコードのコメントを参照して具体的に説明する必要があります。
    
    const asyncHooks = require("async_hooks");
    
    //      AsyncHooks   。
    const hooks = asyncHooks.createHook({
     //          init   。
     init: function(asyncId, type, triggerId, resource) {},
     //           before   。
     before: function(asyncId) {},
     //           after   。
     after: function(asyncId) {},
     //           destroy   。
     destroy: function(asyncId) {}
    });
    
    //               hooks 。
    hooks.enable();
    
    //           。
    hooks.disable();
    
    
    私たちは呼び出し中です createHookの場合は、注入できます。 init、 before、 アフターと destroy関数は、 非同期資源の異なるライフサイクルを追跡する。
    新たな解決策
    Async Hooks APIに基づいて、以下の解決策を設計することができます。ログと記録を要求するバインディング、すなわちTrace IDのグローバルバインディングを実現します。
    
    const asyncHooks = require("async_hooks");
    const { executionAsyncId } = asyncHooks;
    
    //           。
    const contexts = {};
    
    const hooks = asyncHooks.createHook({
     //          init   。
     init: function(asyncId, type, triggerId, resource) {
      // triggerId             asyncId 。
      if (contexts[triggerId]) {
       //                         。
       contexts[asyncId] = contexts[triggerId];
      }
     },
     //           destroy   。
     destroy: function(asyncId) {
      if (!contexts[asyncId]) return;
      //          。
      delete contexts[asyncId];
     }
    });
    
    //   !              hooks 。
    hooks.enable();
    
    //         。
    function handler(params) {
     //    context ,           (  Logger Middleware)。
     contexts[executionAsyncId()] = params;
     
     //        。
     console.log(`handler ${JSON.stringify(params)}`);
     f();
    }
    
    function f() {
     setTimeout(() => {
      //           params 。
      console.log(`setTimeout ${JSON.stringify(contexts[executionAsyncId()])}`);
     });
    }
    
    //         (    )。
    setTimeout(handler, 0, { id: 0 });
    setTimeout(handler, 0, { id: 1 });
    
    上記のコードの中で、先に声明しました。 contextsは、各非同期プロセスにおけるコンテキストデータ(例えば、Trace ID)を保存するために使用され、その後、Aync Hooksのインスタンスを作成した。非同期リソースの初期化時には、現在のAync IDに対応するコンテキストデータを設定し、そのデータを使用者のコンテキストデータとします。非同期リソースが破壊された時、対応するコンテキストデータを削除します。
    このようにして、コンテキストデータを最初に設定するだけで、コンテキストデータを取得し、問題を解決します。
    上記のコードを実行し、その結果は以下の通りです。出力ログによると、私たちの解決策は実行可能です。
    
    handler {"id":0}
    handler {"id":1}
    setTimeout {"id":0}
    setTimeout {"id":1}
    
    ただし、注意が必要なのはAync Hooksです。 実験API、 一定の性能損失がありますが、Nodeの公式は生産が可能になるよう努力しています。したがって、 マシンリソースが十分な場合、このソリューションを使用して、性能の一部を犠牲にして、開発体験と交換します。
    以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。