async_hooks モジュールは、Node.js バージョン 8.0.0 に正式に追加された実験的な API です。バージョン v8.xx で本番環境にも導入しました。 では、async_hooks とは何でしょうか? async_hooks は、関連付けられたコールバックを持つオブジェクトである非同期リソースを追跡するための API を提供します。 つまり、async_hooks モジュールは非同期コールバックを追跡するために使用できます。では、この追跡機能はどのように使用すればいいのでしょうか。また、使用中にどのような問題が発生する可能性があるのでしょうか。 async_hooksを理解するv8.xx バージョンの async_hooks は主に 2 つの部分で構成されます。1 つはライフサイクルを追跡するための createHook で、もう 1 つは非同期リソースを作成するための AsyncResource です。 const { createHook、AsyncResource、executionAsyncId } = require('async_hooks') constフック = createHook({ init (asyncId、タイプ、トリガーAsyncId、リソース) {}, 前 (asyncId) {}, (asyncId) {} の後、 破棄 (非同期 ID) {} }) フックを有効にする() 関数fn(){ コンソールログ(executionAsyncId()) } const asyncResource = 新しい AsyncResource('demo') asyncResource.run(fn) asyncResource.run(fn) asyncResource.emitDestroy() 上記コードの意味と実行結果は次のとおりです。
一般的に使用される async、await、promise 構文やリクエストなどの非同期操作の背後には、これらのライフサイクル フック関数もトリガーする非同期リソースがあります。 次に、init フック関数で、非同期リソース作成コンテキスト triggerAsyncId (親) から現在の非同期リソース asyncId (子) へのポイント関係を作成し、非同期呼び出しを直列に接続して完全な呼び出しツリーを取得し、コールバック関数 (上記のコードでは fn) の executeAsyncId() を通じて現在のコールバックを実行する非同期リソースの asyncId を取得し、呼び出しチェーンから呼び出し元をトレースします。 同時に、init は非同期リソース作成のフックであり、非同期コールバック関数作成のフックではないことにも注意する必要があります。これは、非同期リソースが作成された場合に 1 回だけ実行されます。実際の使用では、どのような問題が発生するでしょうか。 リクエストの追跡例外のトラブルシューティングとデータ分析を目的として、クライアントから送信されたリクエストのリクエスト ヘッダー内の request-id を、Ada アーキテクチャ Node.js サービスのミッドエンド サービスとバックエンド サービスに送信される各リクエストのリクエスト ヘッダーに自動的に追加したいと考えています。 関数実装のシンプルな設計は次のとおりです。
サンプルコードは次のとおりです。 定数 http = require('http') const { createHook, 実行AsyncId } = require('async_hooks') 定数 fs = require('fs') // 呼び出しチェーンを追跡し、呼び出しチェーン ストレージ オブジェクトを作成します。const cache = {} constフック = createHook({ init (asyncId、タイプ、トリガーAsyncId、リソース) { if (type === 'TickObject') 戻り値 // console.log も Node.js の非同期動作であるため、init フックがトリガーされ、同期メソッドを通じてのみログを記録できます。 fs.appendFileSync('log.out', `init ${type}(${asyncId}: trigger: ${triggerAsyncId})\n`); // 呼び出しチェーンストレージオブジェクトが初期化されているかどうかを判定します if (!cache[triggerAsyncId]) { キャッシュ[triggerAsyncId] = {} } // 親ノードのストレージを現在の非同期リソースと参照で共有します。cache[asyncId] = cache[triggerAsyncId] } }) フックを有効にする() // httpを書き換える 定数 httpRequest = http.request http.request = (オプション、コールバック) => { const client = httpRequest(オプション、コールバック) // 現在のリクエストに対応する非同期リソースに格納されているリクエストIDを取得し、それをヘッダーに書き込みます 定数 requestId = キャッシュ[executionAsyncId()].requestId console.log('キャッシュ', キャッシュ[executionAsyncId()]) client.setHeader('リクエストID', リクエストID) 顧客を返す } 関数タイムアウト(){ 新しい Promise を返します ((resolve, reject) => { setTimeout(resolve, Math.random() * 1000) }) } // サービス http を作成する .createServer(非同期(req, res) => { // 現在のリクエストのリクエスト ID を取得し、ストレージ キャッシュに書き込みます [executionAsyncId()].requestId = req.headers['request-id'] //他の時間のかかる操作をシミュレートする await timeout() // リクエストを送信 http.request('http://www.baidu.com', (res) => {}) res.write('hello\n') res.end() }) .聞く(3000) コードを実行して送信テストを実行すると、リクエスト ID が正しく取得できることがわかります。 トラップ同時に、init は非同期コールバック関数作成のフックではなく、非同期リソース作成のフックであり、非同期リソースが作成された場合に 1 回だけ実行されることにも注意する必要があります。 しかし、上記のコードには問題があります。先ほど紹介した async_hooks モジュールのコードで示したように、非同期リソースは異なる関数を継続的に実行することができ、つまり、非同期リソースは再利用することができます。特に、C/C++ 部分で作成される TCP などの非同期リソースの場合、複数のリクエストが同じ TCP 非同期リソースを使用する可能性があります。その結果、この場合、複数のリクエストがサーバーに到着したときに、初期 init フック関数は 1 回だけ実行され、複数のリクエストの呼び出しチェーン トレースが同じ triggerAsyncId を追跡し、同じストレージを参照することになります。 検証を実行するために、前のコードを次のように変更します。 ストレージ初期化部分では、非同期呼び出しの追跡関係の監視を容易にするために、triggerAsyncId を保存します。 if (!cache[triggerAsyncId]) { キャッシュ[トリガー同期ID] = { id: トリガー非同期Id } } タイムアウト関数は、最初に長時間操作を実行し、次に短時間操作を実行するように変更されます。 関数タイムアウト(){ 新しい Promise を返します ((resolve, reject) => { setTimeout(解決、[1000, 5000].pop()) }) } サービスを再起動した後、postman を使用して (curl は各リクエストの後に接続を閉じるため、再現が不可能になるため、curl は使用しないでください)、2 つの連続したリクエストを送信します。次の出力を確認できます。
ストレージの書き込みと読み取りの間に異なる時間がかかる他の操作との複数の同時操作の場合、最初にサーバーに到着した要求に格納された値が、後でサーバーに到着した要求によって上書きされ、前の要求で間違った値が読み取られることがわかります。もちろん、書き込みと読み取りの間に他の時間のかかる操作が挿入されないようにすることはできますが、複雑なサービスでは、このような精神的なメンテナンス方法は明らかに信頼できません。この時点で、この再利用を避けるために、読み取りと書き込みの前に JS が新しい非同期リソース コンテキストに入るようにする必要があります。つまり、新しい asyncId を取得する必要があります。呼び出しチェーンのストレージ部分に次の変更を加える必要があります。 定数 http = require('http') const { createHook, 実行AsyncId } = require('async_hooks') 定数 fs = require('fs') 定数キャッシュ = {} 定数 httpRequest = http.request http.request = (オプション、コールバック) => { const client = httpRequest(オプション、コールバック) 定数 requestId = キャッシュ[executionAsyncId()].requestId console.log('キャッシュ', キャッシュ[executionAsyncId()]) client.setHeader('リクエストID', リクエストID) リターンクライアント } // ストレージの初期化を独立したメソッドに抽出します。async function cacheInit (callback) { // await 操作を使用して、await 後のコードが新しい非同期コンテキストに入るようにします。await Promise.resolve() キャッシュ[executionAsyncId()] = {} // 後続の操作がこの新しい非同期コンテキストに属するようにコールバック実行を使用します。callback() を返します。 } constフック = createHook({ init (asyncId、タイプ、トリガーAsyncId、リソース) { if (!cache[triggerAsyncId]) { // init フックはもはや初期化しません return fs.appendFileSync('log.out', `cacheInit メソッドを使用して初期化されていません`) } キャッシュ[非同期ID] = キャッシュ[トリガー非同期ID] } }) フックを有効にする() 関数タイムアウト(){ 新しい Promise を返します ((resolve, reject) => { setTimeout(解決、[1000, 5000].pop()) }) } http .createServer(非同期(req, res) => { // 後続の操作をcacheInitへのコールバックとして渡す cacheInitを待機します(非同期関数fn() { cache[executionAsyncId()].requestId = req.headers['リクエストID'] タイムアウトを待つ() http.request('http://www.baidu.com', (res) => {}) res.write('hello\n') res.end() }) }) .聞く(3000) コールバックを使用したこの編成方法は、koajs ミドルウェア モデルと非常に一致していることは注目に値します。 非同期関数ミドルウェア (ctx, next) { Promise.resolve() を待つ キャッシュ[executionAsyncId()] = {} 次を返す() } Node.js v14 のawait Promise.resolve() を使用して新しい非同期コンテキストを作成するこの方法は、常に少し「異端」のように思えます。幸いなことに、NodeJs v9.xx では、非同期コンテキストを作成するための公式実装である asyncResource.runInAsyncScope が提供されています。さらに、NodeJs v14.xx は、非同期呼び出しチェーン データ ストレージの公式実装を直接提供しており、非同期呼び出し関係の追跡、新しい非同期起動ドキュメントの作成、データの管理という 3 つのタスクを直接完了するのに役立ちます。 APIについては詳しくは紹介しません。新しいAPIを直接使用して、以前の実装を変換します。 const { AsyncLocalStorage } = require('async_hooks') // asyncLocalStorage ストレージインスタンスを直接作成します。非同期ライフサイクルフックを管理する必要がなくなります。const asyncLocalStorage = new AsyncLocalStorage() 定数ストレージ = { 有効にする(コールバック){ // 新しいストレージを作成するには run メソッドを使用し、その後の操作は新しい非同期リソース コンテキストを使用するために run メソッドのコールバックとして実行する必要があります asyncLocalStorage.run({}, callback) }, 取得 (キー) { asyncLocalStorage.getStore()[キー]を返す }, (キー、値) を設定する { asyncLocalStorage.getStore()[キー] = 値 } } // httpを書き換える 定数 httpRequest = http.request http.request = (オプション、コールバック) => { const client = httpRequest(オプション、コールバック) // 非同期リソースストレージのリクエストIDを取得し、ヘッダーに書き込みます client.setHeader('リクエストID', storage.get('リクエストId')) リターンクライアント } // http を使用する .createServer((req, res) => { ストレージを有効にする(非同期関数() { // 現在のリクエストのリクエスト ID を取得し、ストレージに書き込みます。storage.set('requestId', req.headers['request-id']) http.request('http://www.baidu.com', (res) => {}) res.write('hello\n') res.end() }) }) .聞く(3000) ご覧のとおり、asyncLocalStorage.run API の公式実装も、第 2 バージョンの実装と構造的に一貫しています。 そのため、Node.js v14.xx では、async_hooks モジュールを使用してリクエスト追跡機能を簡単に実装できます。 ノード リクエスト トラッキングに async_hooks モジュールを使用する方法については、これで終わりです。ノード async_hooks リクエスト トラッキングの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: Nginx ソースコード調査における nginx 電流制限モジュールの詳細な説明
>>: MySQL の一時テーブルと派生テーブルについての簡単な説明
1. DNSサーバーの概念インターネットでの通信には IP アドレスの助けが必要ですが、数字に対する...
目次1 システムスループットの簡単な紹介2 試験方法2.1 クライアントテストツール2.1.1 GE...
目次1. はじめに1. コンポーネントデータ2. コンポーネントページのレイアウト1. ロゴエリアの...
lepus3.7 を使用して MySQL データベースを監視中に、次の問題が発生しました。このブログ...
この記事では、ショッピングサイトの虫眼鏡機能を実現するためのjsの具体的なコードを紹介します。具体的...
以前は、さまざまな理由により、一部のアラームは真剣に受け止められませんでした。最近、休暇中に、すぐに...
1. はじめにElasticsearchは現在非常に人気があり、多くの企業が利用しているため、esを...
マシンに初めて MySQL をインストールします。オペレーティングシステムはwin7ですmysqlの...
1. mycatとはエンタープライズアプリケーション開発のための完全にオープンソースの大規模データベ...
序文私自身の個人ブログを入力しているときに、ブログの詳細ページでさまざまなコンテンツをコピーするさま...
この記事の例では、参考のために航空機戦争ゲームを実装するためのJSの具体的なコードを共有しています。...
1. 分離マーカーを追加します。 ip netns add fd 2. 指定されたネットワーク カ...
序文この記事は主に、MySQL で浮動小数点型を文字型に変換するときに発生する問題を紹介します。これ...
同僚から、一時テーブルを使用して変数データを挿入して表示する方法を教わったことがありますが、この方法...
yum か rpm か? yum によるインストール方法は非常に便利ですが、公式サイトから MySQ...