1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Node.js 進(jìn)程、線程調(diào)試和診斷的設(shè)計(jì)和實(shí)現(xiàn)

        共 19046字,需瀏覽 39分鐘

         ·

        2021-12-09 17:22

        大廠技術(shù)  高級(jí)前端  Node進(jìn)階

        點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

        回復(fù)1,加入高級(jí)Node交流群

        前言:本文介紹 Node.js 中,關(guān)于進(jìn)程、線程調(diào)試和診斷的相關(guān)內(nèi)容。進(jìn)程和線程的方案類似,但是也有一些不一樣的地方,本文將會(huì)分開介紹,另外本文介紹的是對(duì)業(yè)務(wù)代碼無(wú)侵入的方案,通過命令行開啟 Inspector 端口或者在代碼里通過 Inspector 模塊打開端口在很多場(chǎng)景下并不適用,我們需要的是一種動(dòng)態(tài)控制的能力。

        1. 背景

        隨著前端的快速發(fā)展,Node.js 在業(yè)務(wù)中的使用場(chǎng)景也越來越多,如何保證 Node.js 服務(wù)的穩(wěn)定也逐漸成為一個(gè)非常重要事情,傳統(tǒng)的服務(wù)器架構(gòu)大多數(shù)基于多進(jìn)程、多線程的,任務(wù)的執(zhí)行是隔離的,一個(gè)任務(wù)出現(xiàn)問題通常不會(huì)影響其他任務(wù),比如在一個(gè)請(qǐng)求中執(zhí)行一個(gè)死循環(huán),服務(wù)器還能處理其他的請(qǐng)求。

        但是 Node.js 不一樣,從整體來看,Node.js 是單線程的,單個(gè)任務(wù)出現(xiàn)問題有可能會(huì)影響其他任務(wù),比如在一個(gè)請(qǐng)求中執(zhí)行了死循環(huán),那么整個(gè)服務(wù)就沒法繼續(xù)工作了。所以在 Node.js 中,我們更加需要方便的調(diào)試和診斷工具,以便遇到問題時(shí)可以快速找到問題,解決問題,另外,工具不僅可以幫我們排查問題,還可以找出我們服務(wù)中的性能瓶頸,方便我們進(jìn)行性能優(yōu)化。

        2. 目標(biāo)

        我們基于 Node.js 本身提供的調(diào)試和診斷能力,提供一個(gè)調(diào)試和診斷平臺(tái),使用方只需要引入 SDK,然后通過調(diào)試和診斷平臺(tái)就可以對(duì)服務(wù)的進(jìn)程和線程進(jìn)行調(diào)試和診斷。

        3. 實(shí)現(xiàn)

        目前支持了多進(jìn)程和多線程的調(diào)試和診斷,下面按照進(jìn)程和線程兩個(gè)方面介紹一下原理和具體實(shí)現(xiàn)。

        3.1. 單進(jìn)程

        3.1.1 調(diào)試和診斷基礎(chǔ)

        在 Node.js 中,可以通過以下方式收集進(jìn)程的數(shù)據(jù)。

        const inspector = require('inspector');
        const session = new inspector.Session();
        session.connect();
        // 發(fā)送命令
        session.post('Profiler.enable', () => {});

        使用方式很簡(jiǎn)單,通過新建一個(gè)和 V8 Inspector 通信的 Session 就可以對(duì)進(jìn)程進(jìn)行數(shù)據(jù)的收集,比如抓取進(jìn)程的堆快照和 Profile 數(shù)據(jù)。有了這個(gè)基礎(chǔ)后,我們就可以封裝這個(gè)能力。

        const http = require('http');
        const inspector = require('inspector');
        const fs = require('fs');

        // 打開一個(gè)和 V8 Inspector 的會(huì)話
        const session = new inspector.Session();
        session.connect();

        function getCpuprofile(req, res{
          // 向V8 Inspector 提交命令,開啟 CPU Profile 并收集數(shù)據(jù)
          session.post('Profiler.enable', () = >{
            session.post('Profiler.start', () = >{
              // 收集一段時(shí)間后提交停止收集命令
              setTimeout(() = >{
                session.post('Profiler.stop', (err, { profile }) = >{
                  // 把數(shù)據(jù)寫入文件
                  if (!err && profile) {
                    fs.writeFileSync('./profile.cpuprofile'JSON.stringify(profile));
                  }
                  // 回復(fù)客戶端
                  res.end('ok');
                });
              },
              3000);
            })
          });
        }

        http.createServer((req, res) = >{
          if (req.url == '/debug/getCpuprofile') {
            getCpuprofile(req, res);
          } else {
            res.end('ok');
          }
        }).listen(80);

        但是這種方式不能調(diào)試進(jìn)程,調(diào)試進(jìn)程需要使用另外的 API,可以通過以下方式啟動(dòng)調(diào)試進(jìn)程的服務(wù)。

        const inspector = require('inspector');
        inspector.open();
        console.log(inspector.url());

        這時(shí)候 Node.js 進(jìn)程中就會(huì)啟動(dòng)一個(gè) WebSocket Server,我們可以通過 Chrome Dev Tools 連上這個(gè) Server 進(jìn)行調(diào)試,我們看看如何封裝。

        const inspector = require('inspector');
        const http = require('http');
        let isOpend = false;

        function getHTML({
          return `<html>
            <meta charset="utf-8" />
            <body>
              復(fù)制到新 Tab 打開該 URL 開始調(diào)試 devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=${inspector.url().replace("ws://"'')}
            </body>
          </html>`
        ;
        }

        http.createServer((req, res) = >{
          if (req.url == '/debug/open') {
            // 還沒開啟則開啟
            if (!isOpend) {
              isOpend = true;
              // 打開調(diào)試器
              inspector.open();
            }
            // 返回給前端的內(nèi)容
            const html = getHTML();
            res.end(html);
          } else if (req.url == '/debug/close') {
            // 如果開啟了則關(guān)閉
            if (isOpend) {
              inspector.close();
              isOpend = false;
            }
            res.end('ok');
          } else {
            res.end('ok');
          }

        }).listen(80);

        我們以 API 的方式對(duì)外提供動(dòng)態(tài)控制進(jìn)程調(diào)試和診斷的能力,具體的實(shí)現(xiàn)可以根據(jù)場(chǎng)景去修改,比如給前端返回一個(gè)不帶 Inspector 端口的 URL,前端再通過 URL 訪問服務(wù),服務(wù)代理請(qǐng)求 Websocket 請(qǐng)求到 Inspector 對(duì)應(yīng)的 WebSocket 服務(wù)。比如把收集的數(shù)據(jù)上傳到云上,給前端返回一個(gè) URL。

        3.1.2 具體實(shí)現(xiàn)

        我們通過 API 的方式提供功能,設(shè)計(jì)上采用插件化的思想,主框架負(fù)責(zé)接收請(qǐng)求和路由處理,具體的邏輯交給具體的插件去做,結(jié)構(gòu)如下所示。

        數(shù)據(jù)收集的實(shí)現(xiàn)和上面的例子中類似,收到請(qǐng)求路由到對(duì)應(yīng)的插件,插件通過 Session 和 V8 Inspector 通信完成數(shù)據(jù)的收集。調(diào)試的實(shí)現(xiàn)就稍微復(fù)雜些,主要的原因是我們不能把端口返回給前端,讓前端直接連接該端口。這個(gè)不是因?yàn)榘踩珕栴},因?yàn)檎{(diào)試的 URL 是一個(gè)帶有一個(gè)復(fù)雜隨機(jī)值的字符串,就算端口暴露了,攻擊者也很難猜對(duì)隨機(jī)值,相比來說,通過提供 API 的方式更加不安全,因?yàn)橹灰婪?wù)的地址,就可以通過 API 去調(diào)試進(jìn)程了,所以嚴(yán)格來說,這里還需要加一些校驗(yàn)機(jī)制。言歸正傳,不暴露端口的原因是通常前端無(wú)法直接連接到這個(gè)端口,原因可能有很多,比如我們的服務(wù)運(yùn)行在容器中,容器只對(duì)外暴露有限的端口,我們不能期待在進(jìn)程中隨便起一個(gè)端口,在前端就可以直接訪問,但是有一個(gè)可以肯定的是,服務(wù)至少會(huì)對(duì)外提供一個(gè)端口,那就意味著我們可以通過某個(gè)對(duì)外的端口把非業(yè)務(wù)相關(guān)的請(qǐng)求傳遞到進(jìn)程內(nèi),基于上面的情況,當(dāng)我們打開 Inspector 端口時(shí),我們只會(huì)告訴前端打開成功或者失敗,當(dāng)前端通過調(diào)試 API 訪問服務(wù)器時(shí),我們會(huì)判斷端口是否已經(jīng)打開,是的話代理請(qǐng)求到 WebSocket Server。結(jié)構(gòu)設(shè)計(jì)如下:

        大致實(shí)現(xiàn)如下:

        const client = connect(WebSocket Server地址);
        client.on('connect', () = >{
          // 轉(zhuǎn)發(fā)協(xié)議升級(jí)的 HTTP 請(qǐng)求給 WebSocket Server
          client.write(`GET ${req.path} HTTP/1.1\r\n` + buildHeaders(req.headers) + '\r\n');
          // 透?jìng)?/span>
          socket.pipe(client);
          client.pipe(socket);
        });

        收到客戶的的請(qǐng)求后,首先連接到 WebSocket Server,然后透?jìng)骺蛻舳说恼?qǐng)求,接著通過管道讓 WebSocket Server 和客戶端通信就行。

        3.2 多進(jìn)程

        為了利用多核,Node.js 服務(wù)通常會(huì)啟動(dòng)多個(gè)進(jìn)程,所以支持多進(jìn)程的調(diào)試和診斷也是非常必要的。但是單進(jìn)程的調(diào)試診斷方案無(wú)法通過橫行拓展來支持多進(jìn)程的場(chǎng)景。

        3.2.1 單進(jìn)程方案的限制

        前面提到的方式看起來工作得不錯(cuò),但是如果服務(wù)是單實(shí)例上多進(jìn)程部署,就會(huì)存在一些限制。我們來看看這時(shí)候的結(jié)構(gòu):

        假如我們只有一個(gè)對(duì)外端口:

        1. 基于 Node.js Cluster 模塊的多進(jìn)程管理機(jī)制,多個(gè)進(jìn)程監(jiān)聽同一個(gè)端口是沒問題的,但是請(qǐng)求的分發(fā)上會(huì)存在問題,比如請(qǐng)求 1 被分發(fā)到進(jìn)程 1,打開了進(jìn)程 1 的 Inspector 端口,接著請(qǐng)求 2 想關(guān)閉這個(gè)端口,但是請(qǐng)求被分發(fā)到了進(jìn)程 2,但是進(jìn)程 2 并沒有打開 Inspector 端口。
        2. 基于 child_process 的 fork 創(chuàng)建多進(jìn)程,則在重復(fù)監(jiān)聽端口時(shí)會(huì)報(bào)錯(cuò),導(dǎo)致只有一個(gè)進(jìn)程可以使用提供的功能。

        3.2.1 Agent 進(jìn)程

        一種解決方案是每個(gè)進(jìn)程監(jiān)聽不同的端口,這樣又回到了前面討論到問題,但是這種方案也不是完全不可行,只需要基于這個(gè)方案做一下改進(jìn),那就是引入 Agent 進(jìn)程,這時(shí)候結(jié)構(gòu)如下:

        Agent 進(jìn)程負(fù)責(zé)收集和管理工作進(jìn)程的信息(如 pid、監(jiān)聽地址),并接管所有調(diào)試和診斷相關(guān)的請(qǐng)求,收到請(qǐng)求后根據(jù)參數(shù)進(jìn)行請(qǐng)求分發(fā)。具體流程如下:

        1. Agent 啟動(dòng)一個(gè)服務(wù)器。
        2. 子進(jìn)程啟動(dòng)后,把自己的 pid 和監(jiān)聽的隨機(jī)服務(wù)地址注冊(cè)到 Agent。
        3. 客戶端通過 Agent 獲取進(jìn)程的 pid 列表,并選擇需要操作的進(jìn)程。
        4. Agent 收到客戶的請(qǐng)求,根據(jù)入?yún)⒅械?pid 把請(qǐng)求發(fā)送給對(duì)應(yīng)的子進(jìn)程。
        5. 子進(jìn)程處理完畢后返回給 Agent,Agent返回給客戶端。

        3.2.2 如何創(chuàng)建 Agent 進(jìn)程

        確定了 Agent 進(jìn)程的方案后,如何創(chuàng)建 Agent 進(jìn)程成為一個(gè)需要解決的問題。在 Node.js 里啟動(dòng)多個(gè)服務(wù)器的方式是通過 Cluster 或者直接通過 child_process 模塊 fork 出多個(gè)子進(jìn)程,Node.js 框架/工具通常都會(huì)封裝這些邏輯,但是框架不一定會(huì)提供創(chuàng)建 Agent 進(jìn)程的方式。為了通用,我們不能假設(shè)運(yùn)行在某種框架/工具中,所以我們只能尋找一種獨(dú)立于框架/工具的方案。我們?cè)诿總€(gè) Worker 進(jìn)程里都創(chuàng)建一個(gè) Agent 進(jìn)程,然后多個(gè) Agent 進(jìn)程競(jìng)爭(zhēng)監(jiān)聽一個(gè)端口,監(jiān)聽成功的進(jìn)程繼續(xù)運(yùn)行,監(jiān)聽失敗的退出,最終剩下一個(gè) Agent 進(jìn)程。

        3.3 多線程

        線程和進(jìn)程的調(diào)試、診斷類似,下面主要講一下不一樣的地方。

        3.3.1 調(diào)試和診斷基礎(chǔ)

        可以通過以下方式收集線程的數(shù)據(jù)。

        const { Worker, workerData } = require('worker_threads');
        const { Session } = require('inspector');

        const session = new Session();
        session.connect();
        let id = 1;

        // 給子線程發(fā)送消息
        function post(sessionId, method, params, callback{
          session.post('NodeWorker.sendMessageToWorker', {
            sessionId,
            messageJSON.stringify({
              id: id++,
              method,
              params
            })
          },
          callback);
        }

        // 子線程連接上 V8 Inspector 后觸發(fā)
        session.on('NodeWorker.attachedToWorker', (data) = >{
          post(data.params.sessionId, 'Profiler.enable');
          post(data.params.sessionId, 'Profiler.start');
          // 收集一段時(shí)間后提交停止收集命令
          setTimeout(() = >{
            post(data.params.sessionId, 'Profiler.stop');
          },
          10000)
        });

        // 收到子線程消息時(shí)觸發(fā)
        session.on('NodeWorker.receivedMessageFromWorker', ({ params: { message } }) = >{});

        const worker = new Worker('./httpServer.js', { workerData: { port80 } });

        worker.on('online', () = >{
          session.post("NodeWorker.enable", { waitForDebuggerOnStartfalse }, (err) = >{
            console.log(err, "NodeWorker.enable");
          });
        });

        setInterval(() = >{},100000);

        類似通過 Agent 進(jìn)程管理多個(gè) Worker 進(jìn)程一樣,因?yàn)橐粋€(gè)進(jìn)程中可能存在多個(gè)線程,所以需要對(duì)多個(gè)線程進(jìn)行管理。首先通過 NodeWorker.enable 命令開啟子線程的 Inspector 能力,然后通過 NodeWorker.attachedToWorker 事件拿到線程對(duì)應(yīng)的 sessionId,后續(xù)通過 sessionId 和線程進(jìn)行通信。接著看一下調(diào)試的實(shí)現(xiàn):

        const { Worker, workerData } = require('worker_threads');
        const { Session } = require('inspector');

        const session = new Session();
        session.connect();
        let workerSessionId;
        let id = 1;

        function post(method, params{
          session.post('NodeWorker.sendMessageToWorker', {
            sessionId: workerSessionId,
            messageJSON.stringify({
              id: id++,
              method,
              params
            })
          });
        }

        session.on('NodeWorker.receivedMessageFromWorker', ({ params: { message } }) = >{
          const data = JSON.parse(message);
          console.log(data);
        });

        session.on('NodeWorker.attachedToWorker', (data) = >{
          workerSessionId = data.params.sessionId;
          post("Runtime.evaluate", {
            includeCommandLineAPItrue,
            expression`const inspector = process.binding('inspector');
              inspector.open();
              inspector.url();
              `

          });
        });

        const worker = new Worker('./httpServer.js', { workerData: { port80 } });
        worker.on('online', () = >{
          session.post("NodeWorker.enable", { waitForDebuggerOnStartfalse }, (err) = >{
            err && console.log("NodeWorker.enable", err);
          });
        });

        setInterval(() = >{}, 100000);

        線程的調(diào)試主要利用 Runtime.evaluate 在子線程里動(dòng)態(tài)執(zhí)行代碼來打開子線程的 Inspector 端口。了解了基礎(chǔ)使用后,我們看一下具體實(shí)現(xiàn)。

        3.3.2 具體實(shí)現(xiàn)

        首先我們提供一個(gè) API 獲取線程列表,這樣我們后續(xù)就可以選擇操作某個(gè)線程,后續(xù)的每個(gè)請(qǐng)求都需要帶上 線程對(duì)應(yīng)的 id,這里以獲取 Profile 為例講一下處理過程。

        const {
          sessionId,
          interval = INTERVAL,
          duration = DURATION
        } = req.query;
        // 向V8 Inspector 提交命令,開啟 CPU Profile 并收集數(shù)據(jù)
        this.post(sessionId, { method'Profiler.enable' }, (err) = >{
          this.post(sessionId, {
            method'Profiler.setSamplingInterval',
            params: { interval }
          });
          this.post(sessionId, { method'Profiler.start' }, (err) = >{
            // 收集一段時(shí)間后提交停止收集命令
            setTimeout(() = >{
              this.post(sessionId, { method'Profiler.stop' }, (err, { profile }) => {});
            }, duration);
          });
        })

        我們看到每一個(gè)操作都需要 sessionId。通過 sessionId,我們把請(qǐng)求轉(zhuǎn)發(fā)到對(duì)應(yīng)的線程。但是和進(jìn)程不一樣,進(jìn)程發(fā)送一個(gè)請(qǐng)求時(shí)傳入一個(gè)回調(diào),請(qǐng)求成功后就會(huì)執(zhí)行對(duì)應(yīng)的回調(diào),我們不需要保存請(qǐng)求上下文,Node.js 會(huì)幫我們處理,但是線程不一樣,存在一個(gè)嵌套的過程,因?yàn)?Inspector 命令的執(zhí)行模式是一個(gè)請(qǐng)求命令對(duì)應(yīng)一個(gè)回調(diào),但是和線程通信時(shí),是首選通過 NodeWorker.sendMessageToWorker 命令和主線程通信,主線程會(huì)解析出 NodeWorker.sendMessageToWorker 的參數(shù),參數(shù)里包含了給子線程發(fā)送的命令,接著主線程通過 sessionId 把請(qǐng)求轉(zhuǎn)發(fā)到子線程,然后這時(shí)候 NodeWorker.sendMessageToWorker 就會(huì)返回并執(zhí)行對(duì)應(yīng)的回調(diào),這時(shí)候意味著 NodeWorker.sendMessageToWorker 執(zhí)行結(jié)束了,但是我們請(qǐng)求子線程的命令還沒有完成,也就是說我們需要自己維護(hù)請(qǐng)求子線程對(duì)應(yīng)的回調(diào)。我們看看 post 的具體實(shí)現(xiàn):

        post(sessionId, message, callback ? ) {
          // 請(qǐng)求對(duì)應(yīng)的 id
          const requestId = ++this.id;
          this.session.post('NodeWorker.sendMessageToWorker', {
            sessionId,
            message: JSON.stringify({ ...message, id: requestId })
          },
          (err) = >{
            /*
              回調(diào)說明 NodeWorker.sendMessageToWorker 請(qǐng)求完成
              err非空說明請(qǐng)求失敗,直接執(zhí)行回調(diào)
              err為空說明請(qǐng)求成功,記錄 post 調(diào)用方的請(qǐng)求回調(diào),通過 id 關(guān)聯(lián)
            */
            if (typeof callback === 'function') {
              // 發(fā)送失敗則直接執(zhí)行回調(diào),成功則記錄回調(diào)
              if (err) {
                callback(err);
              } else {
                this.sessionMap[sessionId]['requests'][requestId] = callback;
              }
            }
          });
        }

        我們看到在 NodeWorker.sendMessageToWorker 回調(diào)里保存了請(qǐng)求子線程的回調(diào)。接下來我們看一下線程執(zhí)行完命令后的回調(diào)。

        this.session.on('NodeWorker.receivedMessageFromWorker', ({
          params: {
            sessionId,
            message
          }
        }) = >{
          const ctx = this.sessionMap[sessionId];
          try {
            const data = JSON.parse(message);
            /**
            *  data 的內(nèi)容格式如下:
            *  {                            
            *    method: string,         
            *    params: Object             
            *  }             
            *  或者
            *  {
            *    id: number,  
            *    result: { result: Object }
            *  }
            */

            const {
              id,
              method,
              result
            } = data;
            // 有 id 說明是請(qǐng)求對(duì)應(yīng)的響應(yīng),沒有 id 說明是 Inspector 異步觸發(fā)的事件
            if (id) {
              if (typeof ctx.requests[id] === 'function') {
                const fn = ctx.requests[id];
                delete ctx.requests[id];
                fn(null, result);
              }
            } else {
              ctx.emit(method, data);
            }
          } catch(e) {
            console.warn(e);
          }
        });

        通過 NodeWorker.receivedMessageFromWorker 事件可以接收到線程返回的請(qǐng)求結(jié)果,從響應(yīng)的數(shù)據(jù)中我們可以知道這個(gè)響應(yīng)來自的線程和請(qǐng)求 id,根據(jù)這些信息我們就可以從維護(hù)的上下文中找到對(duì)應(yīng)的回調(diào)(某些請(qǐng)求在收到響應(yīng)前會(huì)觸發(fā)一些事件,這種情況下響應(yīng)里是沒有請(qǐng)求 id 的)。

        接著看一下如何調(diào)試子線程,調(diào)試端口默認(rèn)是 9229,因?yàn)榇嬖诙嗑€程,如果我們要同時(shí)調(diào)試多個(gè)線程的話,則會(huì)失敗,所以我們要允許前端來控制打開的端口,接著給子線程發(fā)送一個(gè)命令。

        this.post(query.sessionId, {
          method"Runtime.evaluate",
          params: {
            includeCommandLineAPItrue,
            expression`let inspector;
              try {
                inspector = require('inspector');
                inspector.open(${port}${host});
              } catch(e) {
                inspector = process.binding('inspector');
                inspector.open(${port}${host});
              }
              inspector.url();`

          }
        },
        (err, result) = >{

        });

        我們通過在子線程里動(dòng)態(tài)執(zhí)行代碼來打開 Inspector 端口,這里需要處理一下不同 Node.js 版本的兼容問題,高版本(比如 16)中增加了一個(gè)判斷邏輯,如果存在 session 就無(wú)法動(dòng)態(tài)打開 Inspector 端口了,比如以下代碼在 16 中會(huì)報(bào)錯(cuò)(換一下 connect 和 open 的位置就可以執(zhí)行)。

        const inspector = require('inspector');
        const session = new inspector.Session();
        session.connect();
        inspector.open()

        這里需要繞過 JS 層的判斷,通過 C++ 模塊提供的接口直接打開 Inspector 端口,這樣就可以保證任何時(shí)候我們都可以動(dòng)態(tài)打開 Inspector 端口。最后通過  inspector.url() 讓子線程返回調(diào)試的 URL 并保存到上下文中,和進(jìn)程一樣,前端也是通過 API 的方式連接子線程的 WebSocket Server。最后形成的結(jié)構(gòu)如下。

        4. 使用方式

        目前支持了多個(gè)子進(jìn)程和多個(gè)線程的調(diào)試、獲取 CPU Profile、獲取 Heap Profile、獲取 Heap Snapshot、獲取內(nèi)存信息(RSS、堆外內(nèi)存、ArrayBuffer等信息)能力。使用方首先在業(yè)務(wù)代碼里加載 SDK,部署服務(wù)后,進(jìn)入調(diào)試診斷平臺(tái)頁(yè)面,按照以下步驟操作:

        1. 選擇調(diào)試進(jìn)程還是線程
        2. 輸入服務(wù)地址(Agent 進(jìn)程監(jiān)聽的地址)和選擇對(duì)應(yīng)的操作類型,如果是收集數(shù)據(jù)則還需要輸入收集的持續(xù)時(shí)間。
        3. 獲取進(jìn)程列表,并從中選擇你想操作的進(jìn)程,每個(gè)選項(xiàng) hover 時(shí)會(huì)提示進(jìn)程對(duì)應(yīng)的信息,比如文件路徑。
        4. 如果操作線程的話,在選擇進(jìn)程后,還需要獲取該進(jìn)程下的線程列表,并選擇你想操作的線程。
        5. 點(diǎn)擊執(zhí)行就可以獲得你想收集的數(shù)據(jù)或者在線調(diào)試的 URL。

        進(jìn)程:

        線程:

        5. 總結(jié)

        進(jìn)程、線程的調(diào)試和診斷在 Node.js 中的實(shí)現(xiàn)非常復(fù)雜,了解了 Node.js 的實(shí)現(xiàn)和使用方式后,具體應(yīng)用到業(yè)務(wù)里也不容易,主要是要考慮到不同的業(yè)務(wù)場(chǎng)景,需要設(shè)計(jì)出通用的方案,另外調(diào)試是一個(gè)比較有用但是也比較危險(xiǎn)的操作,在安全方面也需要多多考慮。調(diào)試、診斷和安全一樣,平時(shí)用不上,但是有問題的時(shí)候,能幫助我們更好地解決問題。

        更多內(nèi)容參考:

        1. 深入理解 Node.js 的 Inspector:https://mp.weixin.qq.com/s/GLIlhURSrCYQ-8Bqg7i1kA
        2. Node.js子線程調(diào)試和診斷指南:https://zhuanlan.zhihu.com/p/402855448
        - END -
        Node 社群


        我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


           “分享、點(diǎn)贊、在看” 支持一波??

        瀏覽 83
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            潘金莲一级淫片aaaaaa电影 | 五十路在线视频 | 无码人妻一区二区 | 宝贝好爽夹的太紧了 | 91禁在线观看 | 飞机上一级毛片在线 | 国产主播欧美一区日韩精品 | 久久国产福利国产秒拍 | 黑人肏大逼视频 | 3p啊灬啊灬啊灬快灬深用力视频 |