成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Node.js 異步打快照的探索

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

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

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

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

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

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

了解了原理后,接著看一下實現。

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;
    }
}

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

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 !

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

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

可以看到內存大小不一樣時,fork 的耗時是不一樣的。下面是操作系統 fork 時復制頁表的大致過程。

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;
	// 設置進程的cr3字段,即最高級頁目錄表首地址
	SET_PAGE_DIR(tsk, new_pgd);
	// 取得當前進程的最高級頁目錄表首地址
	old_pgd = pgd_offset(current, 0);
	// 復制每一項
	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;
}

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

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

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

責任編輯:武曉燕 來源: 編程雜技
相關推薦

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應用Ebpf

2021-10-23 06:42:46

Node.js 抓取堆快照.js

2021-03-04 23:12:57

Node.js異步迭代器開發

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技術

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2021-12-25 22:29:57

Node.js 微任務處理事件循環

2012-02-03 09:25:39

Node.js

2020-05-29 15:33:28

Node.js框架JavaScript

2021-10-22 08:29:14

JavaScript事件循環

2021-09-07 07:53:43

工具

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美电影在线观看网站 | 午夜在线免费观看 | 日韩在线看片 | 日韩免费一区 | 成人在线网 | 天天艹天天干天天 | 国产精品成人在线播放 | 欧美另类视频 | 黄网站涩免费蜜桃网站 | 久久久综合精品 | 美女天堂 | 黄色一级电影免费观看 | 精品国产高清一区二区三区 | 国产美女永久免费无遮挡 | 国产超碰人人爽人人做人人爱 | 国产一区二区精品在线 | 草久久| 国产精品v| 欧美日本久久 | 一区二区三区四区av | 欧美精品一区二区三区一线天视频 | 一区二区免费视频 | 日韩在线一区二区三区 | 欧美一区二区在线免费观看 | 久久久久九九九女人毛片 | 毛片区| 日韩精品在线观看一区二区三区 | 精品少妇一区二区三区在线播放 | 九九热在线视频观看这里只有精品 | 精品福利视频一区二区三区 | 欧美在线一区二区三区 | 成人二区 | 欧美a免费| 亚洲3p| 日韩精品视频在线 | 久久久国产一区二区三区 | 国产激情免费视频 | 国精品一区 | 国产精品久久久久久 | 成人av片在线观看 | 久久久看 |