日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:52000
  • 待審:37
  • 小程序:12
  • 文章:1037587
  • 會員:756

在某次持續壓測過程中,我們發現 GreptimeDB 的 Frontend 節點內存即使在請求量平穩的階段也在持續上漲,直至被 OOM kill。我們判斷 Frontend 應該是有內存泄漏了,于是開啟了排查內存泄漏之旅。

Heap Profiling

大型項目幾乎不可能只通過看代碼就能找到內存泄漏的地方。所以我們首先要對程序的內存用量做統計分析。幸運的是,GreptimeDB 使用的 jemalloc 自帶 heap profiling[1],我們也支持了導出 jemalloc 的 profile dump 文件[2]。于是我們在 GreptimeDB 的 Frontend 節點內存達到 300MB 和 800MB 時,分別 dump 出了其內存 profile 文件,再用 jemalloc 自帶的 jeprof 分析兩者內存差異(--base 參數),最后用火焰圖顯示出來:

顯然圖片中間那一大長塊就是不斷增長的 500MB 內存占用了。仔細觀察,居然有 thread 相關的 stack trace。難道是創建了太多線程?簡單用 ps -T -p 命令看了幾次 Frontend 節點的進程,線程數穩定在 84 個,而且都是預知的會創建的線程。所以“線程太多”這個原因可以排除。

再繼續往下看,我們發現了很多 Tokio runtime 相關的 stack trace,而 Tokio 的 task 泄漏也是常見的一種內存泄漏。這個時候我們就要祭出另一個神器:Tokio-console[3]。

Tokio Console

Tokio Console 是 Tokio 官方的診斷工具,輸出結果如下:

我們看到居然有 5559 個正在運行的 task,且絕大多數都是 Idle 狀態!于是我們可以確定,內存泄漏發生在 Tokio 的 task 上。現在問題就變成了:GreptimeDB 的代碼里,哪里 spawn 了那么多的無法結束的 Tokio task?

從上圖的 "Location" 列我們可以看到 task 被 spawn 的地方[4]:

implRuntime{

/// Spawn a future and execute it in this thread pool

///

/// Similar to Tokio::runtime::Runtime::spawn

pubfnspawn<F>(&self, future: F) -> JoinHandle<F::Output>

where

F: Future + Send+ 'static,

F::Output: Send+ 'static,

{

self.handle.spawn(future)

}

}

接下來的任務是找到 GreptimeDB 里所有調用這個方法的代碼。

..Default::default

經過一番看代碼的仔細排查,我們終于定位到了 Tokio task 泄漏的地方,并在 PR #1512[5]中修復了這個泄漏。簡單地說,就是我們在某個會被經常創建的 struct 的構造方法中,spawn 了一個可以在后臺持續運行的 Tokio task,卻未能及時回收它。對于資源管理來說,在構造方法中創建 task 本身并不是問題,只要在 Drop 中能夠順利終止這個 task 即可。而我們的內存泄漏就壞在忽視了這個約定。

這個構造方法同時在該 struct 的 Default::default 方法當中被調用了,更增加了我們找到根因的難度。

Rust 有一個很方便的,可以用另一個 struct 來構造自己 struct 的方法,即 "Struct Update Syntax"[6]。如果 struct 實現了 Default,我們可以簡單地在 struct 的 field 構造中使用 ..Default::default。

如果 Default::default內部有 “side effect”(比如我們本次內存泄漏的原因——創建了一個后臺運行的 Tokio task),一定要特別注意:struct 構造完成后,Default創建出來的臨時 struct 就被丟棄了,一定要做好資源回收

例如下面這個小例子:Rust Playground[7]

structA{

i: i32,

}

implDefaultforA{

fndefault-> Self{

println!("called A::default");

A { i: 42}

}

}

#[derive(Default)]

structB{

a: A,

i: i32,

}

implB{

fnnew(a: A) -> Self{

B {

a,

// A::default is called in B::default, even though "a" is provided here.

..Default::default

}

}

}

fnmAIn{

leta= A { i: 1};

letb= B::new(a);

println!("{}", b.a.i);

}

struct A 的 default 方法是會被調用的,打印出 called A::default。

總結

• 排查 Rust 程序的內存泄漏,我們可以用 jemalloc 的 heap profiling 導出 dump 文件;再生成火焰圖可直觀展現內存使用情況。

• Tokio-console 可以方便地顯示出 Tokio runtime 的 task 運行情況;要特別注意不斷增長的 idle tasks。

• 盡量不要在常用 struct 的構造方法中留下有副作用的代碼。

•Default只應該用于值類型 struct。

參考

[1] https://Github.com/jemalloc/jemalloc/wiki/Use-Case%3A-Heap-Profiling

[2] https://github.com/GreptimeTeam/greptimedb/blob/develop/src/common/mem-prof/README.md

[3] https://github.com/tokio-rs/console

[4] https://github.com/GreptimeTeam/greptimedb/blob/develop/src/common/runtime/src/runtime.rs#L63

[5] https://github.com/GreptimeTeam/greptimedb/pull/1512

[6] https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax

[7] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c121ffd32d2ff0fa8e1241a62809bcef

分享到:
標簽:Rust
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 52000

    網站

  • 12

    小程序

  • 1037587

    文章

  • 756

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定