背景
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
. 一番折騰後的結論:
- daemon程式更新, 除了
pm2 kill && pm2 ping
可以重啟daemon外, 還可以pm2 update
它還會使用當前版本pm2 - 想要讓app生效還是建議重新新增app,
pm2 delete app.json && pm2 start app.json
測試環境 重啟了pm2 daemon程式後, 啟動仍舊是前文遇到的報警, 但 node.js version
輸出是符合預期了.
雖然沒解決問題, 但升級版本是必須的. 繼續看看, 收集線索
回到 co 的問題
現在nodejs版本一致, 但是測試環境報警, 本地不復現, 可能還是環境問題, 繼續看程式配置, 發現 interpreter args: --harmony
這個是舊版本nodejs為了相容新的特性加的開關, 考慮到錯誤堆疊是從co中過來的, 檢視co的文件
按理v4+之後就不需要加這個開關, 暫且不關心為什麼加這個開關, 目前能找到的差異就是這個地方, 先本地加上這個開關執行看看
結果復現相同的報警, 本地和測試環境現象一致
接下來就好辦了, 到app.json中刪除這段配置, pm2 delete app.json && pm2 start app.json
重新啟動app, 問題解決.
總結
- pm2 cluster_mode 升級nodejs時需要同步更新daemon程式
- worker程式的配置也需要手工更新
- 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
大家好,我是貓眼娛樂前端技術專家-曹宇,我主要負責貓眼娛樂電影選座交易業務前端, 除了大家能看到的各種 Web 頁面, 還有小程式端和供應鏈端. 同時負責貓眼內部的前端基礎設施, 質量保證相關工作。
貓眼電影小程式從零發展到票務類別第一, 主要關注點都集中線上上, 這次分享的是一個線上 線下聯動的活動, 從開發到上線後遇到的一些有趣的事情, 除了小程式技術的深度應用, 還包括產品 運營層面的思考.
本週六(10月21日)我會做客掘金Bilibili直播間為大家做一場《打碼指南:由貓眼線下掃碼1分購談起》的直播。直播中我們也會送出技術圖書,大號定製滑鼠墊等獎品,歡迎週六下午大家與我們一起交流。