愛可生南區分公司交付服務部成員,實習工程師。負責公司產品問題排查及日常運維工作。
引言
前陣子處理這樣一個案例,某客戶的實例 mysqld 進程內存經常持續增加導致最終被 OOM killer。作為 DBA 肯定想知道有哪些原因可能會導致 OOM(內存溢出)。
此篇文章敘述個人的一些拙見~
先介紹下這位朋友:OOM-killer
OOM Killer(Out of Memory Killer) 是當系統內存嚴重不足時 linux 內核採用的殺掉進程,釋放內存的機制。
OOM Killer 通過檢查所有正在運行的進程,然後根據自己的算法給每個進程一個 badness 分數,擁有最高 badness 分數的進程將會在內存不足時被殺掉。
它打分的算法如下:
- 某一個進程和它所有的子進程都佔用了很多內存的將會打一個高分。
- 為了釋放足夠的內存來解決這種情況,將殺死最少數量的進程(最好是一個進程)。
- 內核進程和其他較重要的進程會被打成相對較低的分。
上面打分的標準意味著,當 OOM killer 選擇殺死的進程時,將選擇一個使用大量內存,有很多子進程且不是系統進程的進程。
簡單來講,oom-killer 的原則就是損失最小、收益最大,因此它會讓殺死的進程數儘可能小、釋放的內存儘可能大。在數據庫服務器上,MySQL 被分配的內存一般不會小,因此容易成為 oom-killer 選擇的對象。
“既然發生了 OOM,那必然是內存不足,內存不足這個問題產生原因很多。
首先第一個就是 MySQL 自身內存的規劃有問題,這就涉及到 mysql 相應的配置參數。
另一個可以想到的原因就是一般部署 MySQL 的服務器,都會部署很多的監控和定時任務腳本,而這些腳本往往缺少必要的內存限制,導致在高峰期的時候佔用大量的內存,導致觸發 Linux 的 oom-killer 機制,最終 MySQL 無辜躺槍犧牲。”
all-important:MySQL 自身內存規劃
說到 MySQL 自身的內存規劃,最先想到的就是 MySQL 中各種 buffer 的大小,innodb buffer pool 就是最鶴立雞群的那個。innodb_buffer_pool_size 參數的大小究竟如何設置,才能保證 MySQL 的性能呢?在官網文檔中可以找到這個參數的一些描述:
A larger buffer pool requires less disk I/O to access the same table data more than once. On a dedicated database server, you might set the buffer pool size to 80% of the machine's physical memory size.
意思是在專用數據庫服務器上,可以將 innodb_buffer_pool_size 設置為計算機物理內存大小的 80%。在許許多多前輩的的經驗中瞭解到,此參數的值設置為物理內存的 50%~80% 頗為合理。
舉個栗子:
innodb buffer pool 分配 76G,每個連接線程最大可用 160M,最大有 3000 連接數,最大可能使用內存總量 545G,但是這一實例所在服務器的物理內存僅僅有 97G,遠超物理內存總量。結果可想而知,這個實例在運行中經常被 oom-killer 殺死,想必原因之一即是因為一開始 MySQL 自身的內存規劃欠妥。
innodb buffer pool 緩存數據的作用相信大家都懂,比如這個 case 中,可以發現該實例為寫密集,讀請求很少,innodb buffer 對性能改善作用不大,80% 的內存沒必要,完全可以降低到物理內存的50%。
“ 以上是對 OOM 發生原因的一些見解,那思考一下還有沒有其他的原因會導致內存溢出的情況呢?不知道大家對內存洩漏是否瞭解,有沒有可能 MySQL 因為內存洩漏堆積演變為內存溢出,最終 oom-killer ... ”
知識補給站:內存洩漏
內存洩漏(Memory Leak)是指程序中己動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重後果。
內存洩漏缺陷具有隱蔽性、積累性的特徵,比其他內存非法訪問錯誤更難檢測。因為內存洩漏的產生原因是內存塊未被釋放,屬於遺漏型缺陷而不是過錯型缺陷。此外,內存洩漏通常不會直接產生可觀察的錯誤症狀,而是逐漸積累,降低系統整體性能,極端的情況下可能使系統崩潰。
上文說到內存洩漏具有隱蔽性,就是不容易被發現唄......為之奈何?
那咱們就去找一個可以檢測內存洩漏的工具:valgrind
關於 valgrind 工具
Valgrind 是一個用於構建動態分析工具的工具框架。它提供了一組工具,每個工具都執行某種調試、分析或類似的任務,以幫助您改進程序。Valgrind 的體系結構是模塊化的,因此可以輕鬆地創建新工具,而不會影響現有的結構。
標配了許多有用的工具:
- Memcheck 是內存錯誤檢測器。
- Cachegrind 是一個緩存和分支預測探測器。
- Callgrind 是一個生成調用圖的緩存分析器。
- Helgrind 是線程錯誤檢測器。
- DRD 還是線程錯誤檢測器。
- Massif 是堆分析器。
- DHAT 是另一種堆分析器。
- SGcheck 是一種實驗性工具,可以檢測堆棧和全局陣列的溢出。
- BBV 是一個實驗性 SimPoint 基本塊矢量生成器。
關於內存洩漏,我們需要使用 valgrind 的默認工具,也就是 memcheck 工具。
Memcheck 是內存錯誤檢測器。它可以檢測以下和內存相關的問題:
- 使用未初始化的內存
- 讀取/寫入已釋放的內存
- 讀取/寫入 malloc 塊的末端
- 內存洩漏
- 對 malloc/new/new[]與free/delete/delete[] 的不匹配使用
- 雙重釋放內存
Valgrind Memcheck 工具的用法如下:
<code>valgrind --tool=memcheck ./a.out/<code>
從上面的命令可以清楚地看到,主要的命令是“ Valgrind”,而我們要使用的工具由選項“ --tool”指定。上面的“ a.out ” 表示我們要在其上運行 memcheck 的可執行文件。此外還可以使用其他的命令行選項,以滿足我們的需要。運行的程序結束後,會生成這個進程的內存分析報告。
“ OK,工具有了,這就如同摸金校尉拿到了洛陽鏟,寶藏還會遠嗎~ 還不快找幾塊地挖掘試試?”
搞個測試找找感覺
1. 使用 valgrind 的 memcheck 工具啟動 mysql:
<code>valgrind --tool=memcheck --leak-check=full --show-reachable=yes --log-file=/tmp/valgrind-mysql.log /usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --user=root/<code>
2. 利用 sysbench 模擬負載;
3. 進程結束後查看檢測報告:
<code>==29326== LEAK SUMMARY:==29326== definitely lost: 0 bytes in 0 blocks==29326== indirectly lost: 0 bytes in 0 blocks==29326== possibly lost: 549,072 bytes in 1,727 blocks==29326== still reachable: 446,492,944 bytes in 54 blocks==29326== suppressed: 0 bytes in 0 blocks==29326====29326== For counts of detected and suppressed errors, rerun with: -v==29326== ERROR SUMMARY: 339 errors from 339 contexts (suppressed: 0 from 0)/<code>
在報告的最後的總結中發現程序退出時有部分內存未釋放,而且存在潛在的內存洩漏。通過向上查看具體的信息,分析後發現主要集中在 performance_schema,偶然發現了一個疑點,那我們完全禁用掉performance_schema 呢?
<code>==9954== LEAK SUMMARY:==9954== definitely lost: 0 bytes in 0 blocks==9954== indirectly lost: 0 bytes in 0 blocks==9954== possibly lost: 0 bytes in 0 blocks==9954== still reachable: 32 bytes in 1 blocks==9954== suppressed: 0 bytes in 0 blocks==9954====9954== For counts of detected and suppressed errors, rerun with: -v==9954== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)/<code>
發現程序退出時幾乎沒有內存未釋放,也不存在潛在的內存洩漏。三次測試過後,發現結果是一致的。這是什麼原因?
“ 大家都知道 MySQL 的 performance schema 用於監控 MySQL server 在一個較低級別的運行過程中的資源消耗、資源等待等情況,但它為什麼可能會導致內存洩漏呢,看來關於 ps 還有不少待挖掘的寶藏哦~ ”
最後一個小總結
1. 注意 MySQL 自身的內存規劃,為保證 MySQL 的性能,innodb buffer pool 大小設置要合理,可以根據實例讀寫負載的情況適當調整 buffer pool 的大小。並且 innodb buffer 與連接會話內存的總和儘量不要超過系統物理內存。
2. 調整 oom_score_adj 參數(/proc/
3. 加強內存的監控和報警,一旦報警,DBA 應該迅速介入,選擇性 Kill 掉一些佔用較多內存的連接。
4. 在開啟 performance_schema 時,會有額外的內存開銷,通過 valgrind-memcheck 內存分析工具發現,較大概率發生內存洩漏。它有可能也會導致 OOM,在場景中若不需要 performance_schema 可以完全禁用,或需要儘量只開啟必要的 instrument。
關於愛可生
愛可生成立於2003年,依託於融合、開放、創新的數據處理技術和服務能力,為大型行業用戶的特定場景提供深度挖掘數據價值的解決方案。
公司持續積累的核心關鍵技術,覆蓋到分佈式數據庫集群、雲數據平臺、數據庫大體量運管平臺、海量數據集成於存儲、清洗與治理、人工智能分析挖掘、可視化展現、安全與隱私保護等多個領域。
公司已與多個行業內的專業公司建立了長期夥伴關係,不斷促進新技術與行業知識相結合,為用戶尋求新的數據驅動的價值增長點。公司已在金融、能源電力、廣電、政府等行業取得了眾多大型用戶典型成功案例,獲得了市場的認可和業務的持續增長。
閱讀更多 愛可生雲數據庫 的文章