升級 Node.js 版本遇到的 co 和 pm2 問題解析

netwjx曹宇發表於2018-10-25

背景

nodejs 4.x 的專案, 需要升級到6.9.5(當時最新的穩定版本)以改善效能和可靠性.

業務中使用到了co, 程式使用 pm2 管理.

升級nodejs版本

確保構建指令碼能夠使用nvm安裝nodejs 6.9.5, 本地執行基本ok

從 co 的問題開始

UnhandledPromiseRejectionWarning: Cannot read property 'done' of undefined
複製程式碼

服務啟動時即產生上述報警資訊, 服務不可用, 通過搜尋發現是存在某個promise最終reject了, 但是沒有catch. 知道直接原因是這樣, 但沒啥幫助, 我上哪去找這種特點的程式碼.

考慮斷點除錯

process.on('unhandledRejection', function(reason, p){
  console.log('=======================');
  console.log(reason);
  console.log(p);
});
複製程式碼

上述程式碼加到服務開始啟動, 本地除錯啟動, 發現一切正常 -_-b

不復現問題, 回顧整個問題, 目測可能是測試環境問題, 先看看nodejs版本吧

console.log(process.versions)
複製程式碼

竟然nodejs版本還是舊版本

pm2 的問題

能決定nodejs版本的途徑就只有程式啟動了, 哪問題就落到 pm2 這邊了, 去檢查pm2的程式配置

> pm2 show myapp_name
│ interpreter       │ node
│ interpreter args  │ --harmony
....
│ exec mode         │ cluster_mode
│ node.js version   │ 4.4.2
複製程式碼

果然版本有問題, 考慮pm2這種程式管理模型, daemon程式啟動後, 再逐個啟動worker程式, 而 exec mode: cluster_mode 意味著它使用了nodejs的cluster模組來啟動子程式.

進一步的, cluster啟動子程式是用fork()啟動的, 子程式的版本和父程式版本應該是一致的. 大概率是這個原因.

簡單重啟daemon程式的辦法是 pm2 kill 幹掉daemon和所有worker程式後, 重新pm2 start. 一番折騰後的結論:

  1. daemon程式更新, 除了 pm2 kill && pm2 ping可以重啟daemon外, 還可以 pm2 update 它還會使用當前版本pm2
  2. 想要讓app生效還是建議重新新增app, pm2 delete app.json && pm2 start app.json

測試環境 重啟了pm2 daemon程式後, 啟動仍舊是前文遇到的報警, 但 node.js version 輸出是符合預期了.

雖然沒解決問題, 但升級版本是必須的. 繼續看看, 收集線索

回到 co 的問題

現在nodejs版本一致, 但是測試環境報警, 本地不復現, 可能還是環境問題, 繼續看程式配置, 發現 interpreter args: --harmony

這個是舊版本nodejs為了相容新的特性加的開關, 考慮到錯誤堆疊是從co中過來的, 檢視co的文件

co platform compatibility

按理v4+之後就不需要加這個開關, 暫且不關心為什麼加這個開關, 目前能找到的差異就是這個地方, 先本地加上這個開關執行看看

結果復現相同的報警, 本地和測試環境現象一致

接下來就好辦了, 到app.json中刪除這段配置, pm2 delete app.json && pm2 start app.json 重新啟動app, 問題解決.

總結

  1. pm2 cluster_mode 升級nodejs時需要同步更新daemon程式
  2. worker程式的配置也需要手工更新
  3. nodejs不會保證部分實驗性開關的相容性

實際遇到的環境問題可能都是混雜多個關鍵原因, 必須得解決所有的原因才能正常工作.

錯誤才是常態, 正確是一連串的偶然組合在一起

遺留疑問: --harmony 怎麼加上去的

瞭解cluster的同學應該知道 fork() 只有一個引數 環境變數, 那就有些奇怪的地方了.

一種可能是 pm2 daemon 啟動時加上去的, 但也不合邏輯, daemon可能會管理多個專案, 有的是cluster, 有的不是.

只是猜測顯然不行, 看原始碼吧

God.nodeApp = function nodeApp(env_copy, cb){
  var clu = null;
 
  console.log('Starting execution sequence in -cluster mode- for app name:%s id:%s',
              env_copy.name,
              env_copy.pm_id);
 
  if (env_copy.node_args && Array.isArray(env_copy.node_args)) {
    // 注意下面這行
    cluster.settings.execArgv = env_copy.node_args;
  }
 
  env_copy._pm2_version = pkg.version;
 
  try {
    // node.js cluster clients can not receive deep-level objects or arrays in the forked process, e.g.:
    // { "args": ["foo", "bar"], "env": { "foo1": "bar1" }} will be parsed to
    // { "args": "foo, bar", "env": "[object Object]"}
    // So we passing a stringified JSON here.
    clu = cluster.fork({pm2_env: JSON.stringify(env_copy)});
  } catch(e) {
    God.logAndGenerateError(e);
    return cb(e);
  }
複製程式碼

重新翻看 cluster 的文件, 發現確實存在 cluster.settings

cluster.settings


大家好,我是貓眼娛樂前端技術專家-曹宇,我主要負責貓眼娛樂電影選座交易業務前端, 除了大家能看到的各種 Web 頁面, 還有小程式端和供應鏈端. 同時負責貓眼內部的前端基礎設施, 質量保證相關工作。

貓眼電影小程式從零發展到票務類別第一, 主要關注點都集中線上上, 這次分享的是一個線上 線下聯動的活動, 從開發到上線後遇到的一些有趣的事情, 除了小程式技術的深度應用, 還包括產品 運營層面的思考.

本週六(10月21日)我會做客掘金Bilibili直播間為大家做一場《打碼指南:由貓眼線下掃碼1分購談起》的直播。直播中我們也會送出技術圖書,大號定製滑鼠墊等獎品,歡迎週六下午大家與我們一起交流。

升級 Node.js 版本遇到的 co 和 pm2 問題解析

相關文章