偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Node.js 異步打快照的探索

開發(fā) 前端
每次幫助用戶排查內(nèi)存泄露問題時獲取快照都是比較麻煩的事情,一來擔(dān)心影響阻塞用戶服務(wù)導(dǎo)致無法處理請求,二來擔(dān)心把用戶服務(wù)打掛了。本文嘗試通過異步的方式獲取快照來解決問題 1,從而避免獲取快照過程目的線程無法工作的問題。

在 Node.js 中,內(nèi)存快照是分析內(nèi)存問題的主要手段,通過內(nèi)存快照我們可以進行內(nèi)存優(yōu)化或解決內(nèi)存泄露的問題。獲取內(nèi)存快照的方式雖然簡單,但是也存在一些問題。

1. 獲取內(nèi)存快照的過程是阻塞式的,這樣意味著在這期間,目的線程是無法處理其他工作的,而這個過程通常非常耗時,這個耗時一來取決于當(dāng)前使用的內(nèi)存大小,二來取決于 V8 的實現(xiàn)(之前也有同學(xué)優(yōu)化了這部分),所以線上操作打快照需要非常謹慎。

2. 獲取內(nèi)存快照期間需要消耗更多的內(nèi)存,尤其是第一次的時候內(nèi)存通常會成倍的增長,這樣很容易導(dǎo)致 OOM。

之前在做 Node.js APM 時,每次幫助用戶排查內(nèi)存泄露問題時獲取快照都是比較麻煩的事情,一來擔(dān)心影響阻塞用戶服務(wù)導(dǎo)致無法處理請求,二來擔(dān)心把用戶服務(wù)打掛了。本文嘗試通過異步的方式獲取快照來解決問題 1,從而避免獲取快照過程目的線程無法工作的問題。

異步獲取快照的原理是通過在目的線程中 fork 一個子進程,然后目的線程可以繼續(xù)執(zhí)行,因為子進程“復(fù)制”了父進程的內(nèi)存,所以可以在子進程中”慢慢地“獲取目的線程的內(nèi)存快照而不影響目的線程,Redis 的 aof 和 rdb 也用到了類似的方式。我們知道獲取內(nèi)存快照的過程就是把內(nèi)存的信息記錄到一個文件中(或其他地方),如果消耗的內(nèi)存越大,則處理的過程越久,所以如果在目的進程/線程做肯定是存在一定的影響的,而通過 fork 方式主要是利用了 fork 只復(fù)制頁表不需要復(fù)制物理內(nèi)存來達到快速復(fù)制內(nèi)存信息的目的,又因為進程的內(nèi)存是隔離的,所以雖然 fork 只復(fù)制了頁表,但是頁表對應(yīng)的物理內(nèi)存也是各個進程獨立的,在 fork 后,父子進程都會使用同一份物理內(nèi)存,當(dāng)某個進程進行寫操作時,操作系統(tǒng)再通過 COW(寫時復(fù)制)技術(shù)分配一塊新的內(nèi)存給該內(nèi)存并修改進程的頁表信息,這也是異步方式帶來的一點副作用,即可能需要分配更多的系統(tǒng)內(nèi)存和性能損耗,但是在獲取快照的過程中應(yīng)用的寫操作不多的話理論上影響也不會很大。

了解了原理后,接著看一下實現(xiàn)。

void TakeSnapshotByFork(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    String::Utf8Value filename(isolate, args[0]);
    high_resolution_clock::time_point fork_t1 = high_resolution_clock::now();
    pid_t pid = fork();
    switch (pid) {
    case -1:
        perror("fork");
        exit(EXIT_FAILURE);
    case 0: {
        FILE* fp = fopen(*filename, "w");
        if (fp == NULL) {
          perror("fopen");
          exit(EXIT_FAILURE);
        }
        high_resolution_clock::time_point take_snapshot_t1 = high_resolution_clock::now();
        const v8::HeapSnapshot* const snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
        FileOutputStream stream(fp);
        snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
        high_resolution_clock::time_point take_snapshot_t2 = high_resolution_clock::now();
        duration<double, std::milli> time_span = take_snapshot_t2 - take_snapshot_t1;
        std::cout << "taking snapshot cost " << time_span.count() << " milliseconds."<<std::endl;
        std::cout <<"take snapshot done !\n"<<std::endl;
        fclose(fp);
        const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
        exit(EXIT_SUCCESS);
    }
    default:
        high_resolution_clock::time_point fork_t2 = high_resolution_clock::now();
        duration<double, std::milli> time_span = fork_t2 - fork_t1;
        std::cout << "fork cost " << time_span.count() << " milliseconds."<<std::endl;
        break;
    }
}

實現(xiàn)上并不復(fù)雜,只是把獲取快照的代碼移到了 fork 出來的子進程中,我大概寫了初步的實現(xiàn)并驗證了一下可行性。下面是測試例子。

const addon = require('..')
class MainClass {}
const obj = new MainClass();
for (let i = 0; i < 100000000; i++) {
    obj[i] = i
}
console.log('rss ', process.memoryUsage().rss / 1024 / 1024 / 1024)
setInterval(() => {
    obj
}, 10000)
setTimeout(() => {
    const t1 = Date.now();
    addon.takeSnapshotByFork(`./${process.pid}.heapsnapshot`)
    console.log('addon.takeSnapshotByFork cost ', Date.now()-t1, 'ms')
}, 1000);

輸出如下:

rss  1.7279319763183594
fork cost 9.38548 milliseconds.
addon.takeSnapshotByFork cost  9 ms
taking snapshot cost 2413.08 milliseconds.
take snapshot done !

可以看到內(nèi)存 rss 消耗了 1 G 多,fork 耗時 9 ms,獲取快照的過程消耗了 2s,但是這 2 s 期間,目的線程是可以執(zhí)行其他代碼的,當(dāng) for 循環(huán)改成 100000 時輸出如下。

rss  0.029743194580078125
fork cost 0.655211 milliseconds.
addon.takeSnapshotByFork cost  0 ms
taking snapshot cost 283.35 milliseconds.
take snapshot done !

可以看到內(nèi)存大小不一樣時,fork 的耗時是不一樣的。下面是操作系統(tǒng) fork 時復(fù)制頁表的大致過程。

int copy_page_tables(struct task_struct * tsk)
{
	int i;
	pgd_t *old_pgd;
	pgd_t *new_pgd;
	// 分配一頁
	new_pgd = pgd_alloc();
	if (!new_pgd)
		return -ENOMEM;
	// 設(shè)置進程的cr3字段,即最高級頁目錄表首地址
	SET_PAGE_DIR(tsk, new_pgd);
	// 取得當(dāng)前進程的最高級頁目錄表首地址
	old_pgd = pgd_offset(current, 0);
	// 復(fù)制每一項
	for (i = 0 ; i < PTRS_PER_PGD ; i++) {
		int errno = copy_one_pgd(old_pgd, new_pgd);
		if (errno) {
			free_page_tables(tsk);
			invalidate();
			return errno;
		}
		old_pgd++;
		new_pgd++;
	}
	invalidate();
	return 0;
}

內(nèi)存越大,所需要的頁表越多,上面的 for 循環(huán)過程就越久,但是相比復(fù)制整個物理內(nèi)存來說,還是快很多。

以上是針對 Node.js 阻塞式獲取快照痛點的一些方案探索,初步來看是可行的,但是還沒有完全驗證,有興趣的可以參考:

https://github.com/theanarkh/async-snapshot。

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2021-04-06 10:15:29

Node.jsHooks前端

2020-12-08 06:28:47

Node.js異步迭代器

2022-04-01 08:02:32

Node.js快照加速hooks

2011-12-23 13:58:57

node.js

2021-12-01 00:05:03

Js應(yīng)用Ebpf

2021-10-23 06:42:46

Node.js 抓取堆快照.js

2021-03-04 23:12:57

Node.js異步迭代器開發(fā)

2021-03-16 16:16:41

GeneratorWebsockets前端

2023-06-30 08:05:41

2021-01-26 08:07:44

Node.js模塊 Async

2013-11-01 09:34:56

Node.js技術(shù)

2015-03-10 10:59:18

Node.js開發(fā)指南基礎(chǔ)介紹

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2021-10-22 08:29:14

JavaScript事件循環(huán)

2021-12-25 22:29:57

Node.js 微任務(wù)處理事件循環(huán)

2011-09-09 14:23:13

Node.js

2011-09-02 14:47:48

Node

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js
點贊
收藏

51CTO技術(shù)棧公眾號