摘要:如何避免內(nèi)存泄露內(nèi)存泄漏很常見,特別是前端去寫后端程序,閉包運(yùn)用不當(dāng),循環(huán)引用等都會(huì)導(dǎo)致內(nèi)存泄漏。有的時(shí)候很難避免一些可能產(chǎn)生內(nèi)存泄漏的問題,可以利用每次調(diào)用都在一個(gè)沙箱環(huán)境下調(diào)用,用完回收調(diào)。
某一天用戶反饋打開的頁(yè)面白屏幕,怎么定位到產(chǎn)生錯(cuò)誤的原因呢?日常某次發(fā)布怎么確定發(fā)布會(huì)沒有引入bug呢?此時(shí)捕獲到代碼運(yùn)行的bug并上報(bào)是多么的重要。
既然捕獲錯(cuò)誤并上報(bào)是日常開發(fā)中不可缺少的一環(huán),那怎么捕獲到錯(cuò)誤呢?萬(wàn)能的**try...catch**
try{ throw new Error() } catch(e) { // handle error }
看上去錯(cuò)誤捕獲是多么的簡(jiǎn)單,然而下面的場(chǎng)景下就不能捕獲到了
try { setTimeout(() => { throw new Error("error") }) } catch (e) { // handle error }
你會(huì)發(fā)現(xiàn)上面的例子中的錯(cuò)誤不能正常捕獲,看來(lái)錯(cuò)誤捕獲并不是這樣簡(jiǎn)單**try...catch**就能搞定,當(dāng)然你也可以為異步函數(shù)包裹一層**try...catch**來(lái)處理。
瀏覽器中,**window.onerror**來(lái)捕獲你的錯(cuò)誤
window.onerror = function (msg, url, row, col, error) { console.log("error"); console.log({ msg, url, row, col, error }) };
捕獲到錯(cuò)誤后就可以將錯(cuò)誤上報(bào),上報(bào)方式很簡(jiǎn)單,你可以通過(guò)創(chuàng)建簡(jiǎn)單的**img**,通過(guò)**src**指定上報(bào)的地址,當(dāng)然為了避免上報(bào)發(fā)送過(guò)多的請(qǐng)求,可以對(duì)上報(bào)進(jìn)行合并,合并上報(bào)??梢远〞r(shí)將數(shù)據(jù)進(jìn)行上報(bào)到服務(wù)端。
但但你去看錯(cuò)誤上報(bào)的信息的時(shí)候,你會(huì)發(fā)現(xiàn)一些這樣的錯(cuò)誤**Script error**
因?yàn)闉g覽器的同源策略,對(duì)于不同域名的錯(cuò)誤,都拋出了**Script error**,怎么解決這個(gè)問題呢?特別是現(xiàn)在基本上js資源都會(huì)放在cdn上面。
解決方案
1:所有的資源都放在同一個(gè)域名下。但是這樣也會(huì)存在問題是不能利用cdn的優(yōu)勢(shì)。
2:增加跨域資源支持,在cdn 上增加支持主域的跨域請(qǐng)求支持,在script 標(biāo)簽加**crossorigin**屬性
在使用Promise過(guò)程中,如果你沒有catch,那么可以這樣來(lái)捕獲錯(cuò)誤
window.addEventListener("unhandledrejection", function(err, promise) { // handle error here, for example log });如何在NodeJs中捕獲錯(cuò)誤
NodeJs中的錯(cuò)誤捕獲很重要,因?yàn)樘幚聿划?dāng)可能導(dǎo)致服務(wù)雪崩而不可用。當(dāng)然了不僅僅知道如何捕獲錯(cuò)誤,更應(yīng)該知道如何避免某些錯(cuò)誤。
當(dāng)你寫一個(gè)函數(shù)的時(shí)候,你也許曾經(jīng)思考過(guò)當(dāng)函數(shù)執(zhí)行的時(shí)候出現(xiàn)錯(cuò)誤的時(shí)候,我是應(yīng)該直接拋出throw,還是使用callback或者event emitter還是其它方式分發(fā)錯(cuò)誤呢?
我是否應(yīng)該檢查參數(shù)是否是正確的類型,是不是null
如果參數(shù)不符合的時(shí)候,你怎么辦呢?拋出錯(cuò)誤還是通過(guò)callback等方式分發(fā)錯(cuò)誤呢?
如果保存足夠的錯(cuò)誤來(lái)復(fù)原錯(cuò)誤現(xiàn)場(chǎng)呢?
如果去捕獲一些異常錯(cuò)誤呢?try...catch還是domain
操作錯(cuò)誤往往發(fā)生在運(yùn)行時(shí),并非由于代碼bug導(dǎo)致,可能是由于你的系統(tǒng)內(nèi)存用完了或者是由于文件句柄用完了,也可能是沒有網(wǎng)絡(luò)了等等
編碼錯(cuò)誤那就比較容易理解了,可能是undefined卻當(dāng)作函數(shù)調(diào)用,或者返回了不正確的數(shù)據(jù)類型,或者內(nèi)存泄露等等
你可以記錄一下錯(cuò)誤,然后什么都不做
你也可以重試,比如因?yàn)殒溄訑?shù)據(jù)庫(kù)失敗了,但是重試需要限制次數(shù)
你也可以將錯(cuò)誤告訴前端,稍后再試
也許你也可以直接處理,比如某個(gè)路徑不存在,則創(chuàng)建該路徑
錯(cuò)誤編碼是不好處理的,因?yàn)槭怯捎诰幋a錯(cuò)誤導(dǎo)致的。好的辦法其實(shí)重啟該進(jìn)程,因?yàn)?/p>
你不確定某個(gè)編碼錯(cuò)誤導(dǎo)致的錯(cuò)誤會(huì)不會(huì)影響其它請(qǐng)求,比如建立數(shù)據(jù)庫(kù)鏈接錯(cuò)誤由于編碼錯(cuò)誤導(dǎo)致不能成功,那么其它錯(cuò)誤將導(dǎo)致其它的請(qǐng)求也不可用
或許在錯(cuò)誤拋出之前進(jìn)行IO操作,導(dǎo)致IO句柄無(wú)法關(guān)閉,這將長(zhǎng)期占有內(nèi)存,可能導(dǎo)致最后內(nèi)存耗盡整個(gè)服務(wù)不可用。
上面提到的兩點(diǎn)其實(shí)都沒有解決問題根本,應(yīng)該在上線前做好測(cè)試,并在上線后做好監(jiān)控,一旦發(fā)生類似的錯(cuò)誤,就應(yīng)該監(jiān)控報(bào)警,關(guān)注并解決問題
在同步函數(shù)中,直接throw出錯(cuò)誤
對(duì)于一些異步函數(shù),可以將錯(cuò)誤通過(guò)callback拋出
async/await可以直接使用try..catch捕獲錯(cuò)誤
EventEmitter拋出error事件
一個(gè)NodeJs運(yùn)用,僅僅從碼層面是很難保證穩(wěn)定運(yùn)行的,還要從運(yùn)維層面去保障。
單進(jìn)程的nodejs一旦掛了,整個(gè)服務(wù)也就不可用了,所以我萌需要多個(gè)進(jìn)程來(lái)保障服務(wù)的可用,某個(gè)進(jìn)程只負(fù)責(zé)處理其它進(jìn)程的啟動(dòng),關(guān)閉,重啟。保障某個(gè)進(jìn)程掛掉后能夠立即重啟。
可以參考TSW中多進(jìn)程的設(shè)計(jì)。master負(fù)責(zé)對(duì)worker的管理,worker和master保持這心跳監(jiān)測(cè),一旦失去,就立即重啟之。
process.on("uncaughtException", function(err) { console.error("Error caught in uncaughtException event:", err); }); process.on("unhandleRejection", function(err) { // TODO })
上面捕獲nodejs中異常的時(shí)候,可以說(shuō)是很暴力。但是此時(shí)捕獲到異常的時(shí)候,你已經(jīng)失去了此時(shí)的上下文,這里的上下文可以說(shuō)是某個(gè)請(qǐng)求。假如某個(gè)web服務(wù)發(fā)生了一些異常的時(shí)候,還是希望能夠返回一些兜底的內(nèi)容,提升用戶使用體驗(yàn)。比如服務(wù)端渲染或者同構(gòu),即使失敗了,也可以返回個(gè)靜態(tài)的html,走降級(jí)方案,但是此時(shí)的上下文已經(jīng)丟失了。沒有辦法了。
function domainMiddleware(options) { return async function (ctx, next) { const request = ctx.request; const d = process.domain || domain.create(); d.request = request; let errHandler = (err) => { ctx.set("Content-Type", "text/html; charset=UTF-8"); ctx.body = options.staticHtml; }; d.on("error", errHandler); d.add(ctx.request); d.add(ctx.response); try { await next(); } catch(e) { errHandler(e) } }
上面是一個(gè)簡(jiǎn)單的koa2的domain的中間件,利用domain監(jiān)聽error事件,每個(gè)請(qǐng)求的Request, Response對(duì)象在發(fā)生錯(cuò)誤的時(shí)候,均會(huì)觸發(fā)error?事件,當(dāng)發(fā)生錯(cuò)誤的時(shí)候,能夠在有上下文的基礎(chǔ)上,可以走降級(jí)方案。
內(nèi)存泄漏很常見,特別是前端去寫后端程序,閉包運(yùn)用不當(dāng),循環(huán)引用等都會(huì)導(dǎo)致內(nèi)存泄漏。
不要阻塞Event Loop的執(zhí)行,特別是大循環(huán)或者IO同步操作
for ( var i = 0; i < 10000000; i++ ) { var user = {}; user.name = "outmem"; user.pass = "123456"; user.email = "outmem[@outmem](/user/outmem).com"; }
上面的很長(zhǎng)的循環(huán)會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)樗且粋€(gè)同步執(zhí)行的代碼,將在進(jìn)程中執(zhí)行,V8在循環(huán)結(jié)束的時(shí)候,是沒辦法回收循環(huán)產(chǎn)生的內(nèi)存的,這會(huì)導(dǎo)致內(nèi)存一直增長(zhǎng)。還有可能原因是,這個(gè)很長(zhǎng)的執(zhí)行,阻塞了node進(jìn)入下一個(gè)Event loop, 導(dǎo)致隊(duì)列中堆積了太多等待處理已經(jīng)準(zhǔn)備好的回調(diào),進(jìn)一步加劇內(nèi)存的占用。那怎么解決呢?
可以利用setTimeout將操作放在下一個(gè)loop中執(zhí)行,減少長(zhǎng)循環(huán),同步IO對(duì)進(jìn)程的阻.阻塞下一個(gè)loop 的執(zhí)行,也會(huì)導(dǎo)致應(yīng)用的性能下降
模塊的私有變量和方法都會(huì)常駐在內(nèi)存中
var leakArray = []; exports.leak = function () { leakArray.push("leak" + Math.random()); };
在node中require一個(gè)模塊的時(shí)候,最后都是形成一個(gè)單例,也就是只要調(diào)用該函數(shù)一下,函數(shù)內(nèi)存就會(huì)增長(zhǎng),閉包不會(huì)被回收,第二是leak方法是一個(gè)私有方法,這個(gè)方法也會(huì)一直存在內(nèi)存。加入每個(gè)請(qǐng)求都會(huì)調(diào)用一下這個(gè)方法,那么內(nèi)存一會(huì)就炸了。
這樣的場(chǎng)景其實(shí)很常見
// main.js function Main() { this.greeting = "hello world"; } module.exports = Main;
var a = require("./main.js")(); var b = require("./main.js")(); a.greeting = "hello a"; console.log(a.greeting); // hello a console.log(b.greeting); // hello a
require得到是一個(gè)單例,在一個(gè)服務(wù)端中每一個(gè)請(qǐng)求執(zhí)行的時(shí)候,操作的都是一個(gè)單例,這樣每一次執(zhí)行產(chǎn)生的變量或者屬性都會(huì)一直掛在這個(gè)對(duì)象上,無(wú)法回收,占用大量?jī)?nèi)存。
其實(shí)上面可以按照下面的調(diào)用方式來(lái)調(diào)用,每次都產(chǎn)生一個(gè)實(shí)例,用完回收。
var a = new require("./main.js"); // TODO
有的時(shí)候很難避免一些可能產(chǎn)生內(nèi)存泄漏的問題,可以利用vm每次調(diào)用都在一個(gè)沙箱環(huán)境下調(diào)用,用完回收調(diào)。
最后就是避免循環(huán)引用了,這樣也會(huì)導(dǎo)致無(wú)法回收
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/8070.html
摘要:淺談前端中的錯(cuò)誤捕獲某一天用戶反饋打開的頁(yè)面白屏幕,怎么定位到產(chǎn)生錯(cuò)誤的原因呢日常某次發(fā)布怎么確定發(fā)布會(huì)沒有引入呢此時(shí)捕獲到代碼運(yùn)行的并上報(bào)是多么的重要。 淺談前端中的錯(cuò)誤捕獲 某一天用戶反饋打開的頁(yè)面白屏幕,怎么定位到產(chǎn)生錯(cuò)誤的原因呢?日常某次發(fā)布怎么確定發(fā)布會(huì)沒有引入bug呢?此時(shí)捕獲到代碼運(yùn)行的bug并上報(bào)是多么的重要。 既然捕獲錯(cuò)誤并上報(bào)是日常開發(fā)中不可缺少的一環(huán),那怎么捕獲到...
摘要:如何避免內(nèi)存泄露內(nèi)存泄漏很常見,特別是前端去寫后端程序,閉包運(yùn)用不當(dāng),循環(huán)引用等都會(huì)導(dǎo)致內(nèi)存泄漏。有的時(shí)候很難避免一些可能產(chǎn)生內(nèi)存泄漏的問題,可以利用每次調(diào)用都在一個(gè)沙箱環(huán)境下調(diào)用,用完回收調(diào)。 某一天用戶反饋打開的頁(yè)面白屏幕,怎么定位到產(chǎn)生錯(cuò)誤的原因呢?日常某次發(fā)布怎么確定發(fā)布會(huì)沒有引入bug呢?此時(shí)捕獲到代碼運(yùn)行的bug并上報(bào)是多么的重要。 既然捕獲錯(cuò)誤并上報(bào)是日常開發(fā)中不可缺少的...
摘要:搭建一個(gè)應(yīng)用,少不了一個(gè)主文件,不少人根據(jù)各自喜好來(lái)定義名字,像??偨Y(jié)一個(gè)完整的由個(gè)部分組成,大家只要把主文件當(dāng)成白雪公主,把個(gè)組成部分當(dāng)作七個(gè)小矮人就行了,哈哈,這個(gè)記法真天才。 前言 Node妹子的問世,著實(shí)讓我們前端攻城獅興奮了一把,尤其本屌聽說(shuō)Javascript可以寫服務(wù)端后,興奮的像是看到了二次元蘿莉的胖子...(●?●)。呃哼...YY先到這里,原諒本屌是個(gè)二次元蘿莉控。...
摘要:如果有錯(cuò)誤,則到的第二個(gè)回調(diào)函數(shù)中,對(duì)錯(cuò)誤進(jìn)行處理。假設(shè)第一個(gè)的第一個(gè)回調(diào)沒有返回一個(gè)對(duì)象,那么第二個(gè)的調(diào)用者還是原來(lái)的對(duì)象,只不過(guò)其的值變成了第一個(gè)中第一個(gè)回調(diào)函數(shù)的返回值。 ES6標(biāo)準(zhǔn)出爐之前,一個(gè)幽靈,回調(diào)的幽靈,游蕩在JavaScript世界。 正所謂: 世界本沒有回調(diào),寫的人多了,也就有了})})})})})。 Promise的興起,是因?yàn)楫惒椒椒ㄕ{(diào)用中,往往會(huì)出現(xiàn)回調(diào)函數(shù)一...
閱讀 3844·2021-10-12 10:12
閱讀 1473·2021-10-11 10:58
閱讀 2309·2021-10-09 10:01
閱讀 2622·2021-09-24 09:48
閱讀 2716·2021-09-09 11:38
閱讀 3538·2019-08-30 15:44
閱讀 1740·2019-08-30 14:22
閱讀 531·2019-08-29 12:42