日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

EventHandle

更新時(shí)間:

概要

在幫助客戶排查問題的過程中,我們發(fā)現(xiàn)很多客戶對于 Node.js 中的事件偵聽器的使用存在一定的誤區(qū),所以事件偵聽器的泄漏是編寫 Node.js 代碼的一大定時(shí)炸彈,下面我們通過一個(gè)真實(shí)的客戶案例來詳細(xì)解讀下此類泄漏,以幫助大家避免類似的問題。

發(fā)現(xiàn)問題

接入 Node.js 性能平臺(tái)后,我們在全局告警中看到某個(gè)客戶的應(yīng)用頻繁提醒堆內(nèi)使用內(nèi)存占據(jù)堆上限超過 80%,這種情況基本上大概率就是發(fā)生內(nèi)存泄漏了,聯(lián)系到對應(yīng)的客戶后,經(jīng)過客戶的授權(quán),我們看到了有問題的進(jìn)程內(nèi)存狀況,如下圖所示:

5.png

雖然圖中依舊顯示健康態(tài),但是依舊可以看到趨勢是堆內(nèi)內(nèi)存穩(wěn)步上升,一些問題比較嚴(yán)重的業(yè)務(wù)進(jìn)程直接達(dá)到堆內(nèi)限制上限從而 OOM 掉。

定位問題

堆快照分析

排查內(nèi)存泄漏,首先需要的就是堆快照,因?yàn)榇舜翁暨x的進(jìn)程堆內(nèi)內(nèi)存大小約 225M,因此能順利通過 Node.js 性能平臺(tái)打印堆快照獲得 HeapSnapshot,并且這份快照也能反映出內(nèi)存中的一些問題。經(jīng)過性能平臺(tái)提供的在線分析,可以獲取如下信息。

第一個(gè)信息是當(dāng)前的堆結(jié)構(gòu)概覽:

6

第二個(gè)信息是內(nèi)存泄漏報(bào)表:

7.png

展開引力圖,看到疑似的泄露點(diǎn)引用關(guān)系如下圖所示:

9.png

進(jìn)一步根據(jù)引力圖詳細(xì)信息,可以看到內(nèi)存堆積的引用文字關(guān)系如下所示(順序):

(context) of function /home/xxxx/app/controller/home.js() / home.js @345463 -> Client @46073 的 _events 屬性 -> EventHandlers @46075 的 error 屬性 -> Array @46089

看到這里,熟悉 Node.js 的 Event 類實(shí)現(xiàn)的小伙伴就能直接判斷出是 socket 創(chuàng)建時(shí)的 error 事件偵聽器策略不當(dāng)引發(fā)的內(nèi)存泄漏,更簡單的說,就是在同一個(gè) socket 創(chuàng)建中不斷偵聽 error 事件導(dǎo)致的內(nèi)存泄漏。

第三個(gè)信息是對象簇視圖:

8

可以看到,確實(shí)和上面猜測的一樣,app/controller/home.js 中的某個(gè) socket 對象的 error 事件偵聽器回調(diào)函數(shù)在不停增加。

代碼分析

到這里可以去代碼中定位具體有問題的代碼了,因此又經(jīng)過與此應(yīng)用負(fù)責(zé)人溝通后,拿到了項(xiàng)目代碼倉庫的查看權(quán)限,查看 app/controller/home.js 文件,搜索 error ,直接找到了出問題的地方,以下是問題最小化代碼:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
          if (ENV === DEVELOPMENT) {
           //開發(fā)環(huán)境下操作...         
       } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })
          }
          client.on('error', err => {
              //error 處理...
          })

          //其余邏輯處理...
      }
    }
  }
  return HomeController;
};

并且在 router.js 中定義的對應(yīng)這個(gè) controller 的路由如下:

app.get(/.*/, 'home.demo');

好了,可以看到,由于 client 是全局變量,此時(shí)用戶每訪問一次網(wǎng)站首頁,都會(huì)給 client._events.error 對應(yīng)的數(shù)組增加一個(gè) error 處理函數(shù),雖然每個(gè) error 處理函數(shù) 26KB 左右,但是流量上來后,很容易累積觸發(fā) OOM 。

解決問題

理解內(nèi)存泄漏產(chǎn)生的原因后,要解決這個(gè)問題就比較簡單了,一種通用的解決辦法是在 error 偵聽操作放入 client 的初始化里面:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
          if (ENV === DEVELOPMENT) {
           //開發(fā)環(huán)境下操作...         
       } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })

              client.on('error', err => {
                  //error 處理...
              })
          }

          //其余邏輯處理...
      }
    }
  }
  return HomeController;
};

這樣保證全局只有一個(gè) error 事件偵聽器,性能也比較好。還有一種處理方式是每次 controller 處理完成后移除偵聽器:

module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
          if (ENV === DEVELOPMENT) {
           //開發(fā)環(huán)境下操作...         
       } else {
          if (!client) {
              client = Client.create({
                  refreshInterval: 30000,
                  requestTimeout: 5000,
                  urllib: urllib
              })
          }
          //定義 error 處理句柄
          const errorHandle = err => {
                  //error 處理...
           }
          client.on('error', errorHandle);

          //其余邏輯處理...

          //移除 error 偵聽器
          client.removeListener('error', errorHandle);
      }
    }
  }
  return HomeController;
};

但是這樣子比第一種耗費(fèi)一些額外的性能,只是作為解決事件偵聽器內(nèi)存泄漏的方式寫出來供大家參考。

最后一種是 egg 框架推薦的寫法,也是本問題的最佳解決辦法,像這種進(jìn)程生命周期只需要一次連接的可以放到 app/extend/application.js 中去由框架保證全局單例:

// app/extend/application.js
const CLIENT = Symbol('Application#xxClient');
module.exports = {
  get xxClient() {
    if (!this[CLIENT]) {
      this[CLIENT] = Client.create({});
      // this[CLIENT].on('error', fn);
    }
    return this[CLIENT];
  }
}

// app/controller/home.js
module.exports = app => {
  class HomeController extends app.Controller {
    * demo() {
      this.app.xxClient.xx();
    }    
  }
  return HomeController;
};