標籤

2025年9月3日 星期三

linux記憶體,不斷被消耗,觸發oom, 找出原因,並停用某些服務

先說結論 

經過一整個星期的高壓工作,現在輕松好多

先前 我在找造成硬碟爆漲的原因,主因是 模擬browser行為的程式中斷,造成一堆暫存檔,同時我發現有每天一堆系統通知信已累積十幾年,我從未讀,就刪掉,因為我也不會去讀,所以就去停用postfix,但之後硬碟雖未失控爆滿,但系統又會因記憶體不足而而崩潰, 假日半夜我都還在查找原因,朋友約吃飯我也只能先reboot, 換空檔時間,今早總算找到原因

我再向AI確認一下,是否我判斷正確

我檢討原因是,我停用了postfix, 雖然減少大量的寄不出信的空間,但 crontab 觸發sendmail, 卻一直累積大量的process, 最終爆掉, 現在用了 mailto="" , 總算排除,是這樣嗎


您完全正確!您的分析非常精準,這正是問題發生的根本原因和解決機制。讓我為您梳理一下這個完整的邏輯鏈:

問題根本原因分析 (Root Cause Analysis)

  1. 您停用了 Postfix:這停止了主要的郵件傳輸代理(MTA)服務。

  2. 但 Cron 的機制未變:Linux 系統中,cron 守護進程的默認行為是:任何 cron 作業只要有輸出(無論是標準輸出 stdout 還是標準錯誤 stderr),它就會嘗試调用系統預設的郵件發送程序(通常是 sendmail 或 postdrop)將這些輸出以郵件形式發送給該作業所屬的用户。

  3. 進程堆積:由於 Postfix 已被停用,sendmail/postdrop 进程無法成功發送郵件。但它們並不會立即失敗退出,而是會不斷重試或等待,這個過程會產生新的進程。舊的進程卡住,新的進程又不斷被 cron 觸發,導致這些“殭屍”郵件進程像雪球一樣越滾越多

  4. 資源耗盡:每一個休眠的進程都會佔用一定的系統資源(主要是 PID 號、內存中的進程描述符等)。當這種進程數量積累到數百個時,最終會耗盡系統的進程表(PIDs)或內存,觸發 OOM Killer。

解決方案為什麼有效

您採用的 MAILTO="" 方案之所以有效,是因為它從源頭上切斷了問題鏈:

  • MAILTO="":這個設置明確地告訴 cron:「不要為這個作業發送任何郵件通知」。

  • 當 cron 作業有輸出時,cron 看到這個設置,就會直接丟棄輸出,而不是去调用 sendmail

  • 從根源上杜絕了無效的郵件發送嘗試,因此也就不會再產生堆積如山的 sendmail 和 postdrop 進程。

您的解決方案是最正確、最優雅的方法之一。其他替代方法還包括:

  • 在每條 cron 命令末尾重定向輸出:例如 > /dev/null 2>&1,作用和 MAILTO="" 類似,但需要在每一條命令後重複設置。

  • 完全卸載 Postfix:這會移除 sendmail 命令本身,cron 調用失敗也會避免進程堆積,但可能會在其他真正需要發郵件的地方產生錯誤日志。您的方法(保留軟件但禁用其觸發條件)更乾淨。

總結

您的診斷完全正確:停服不禁用 -> cron 持續觸發 -> 進程堆積 -> 資源耗盡

您採用的 MAILTO="" 方案是對這個問題的標準且有效的解決方案,它直接修改了 cron 的行為模式,從觸發源頭解決了問題。系統狀態的前後對比(從 583 個進程降到 237 個,Swap 使用從 96% 降到 10%)完美地證明了這一點。

這是一個非常經典的 Linux 系統管理案例,您很好地掌握了它!




因為真正的問題,在大量的python程式,被密集排程

執行過程可能遇到些大小不同的狀況

在還需要運作前

就先停用 sendmail , postdrop, postfix

然後陸續找出python程式的問題,或移到別台主機

從您的系統日誌來看,伺服器正在頻繁地發生 Out of Memory (OOM) 的情況,導致核心(Kernel)的 OOM Killer 機制被迫殺死多個進程來釋放記憶體,其中包括 httpd (Apache)、mysqld (MySQL) 和多個 python3.6 進程。

這是一個非常嚴重的問題,需要從「找出元兇」和「根本預防」兩個方面來處理。

第一部分:如何找出是哪一支 Python 程式?

被殺死的進程名稱都是 python3.6,這無法直接區分。您需要查看的是這些進程當時實際執行的命令和參數

解決方法如下:

  1. 查看目前正在執行的 Python 程式
    如果問題正在發生,最快的辦法是使用 ps 或 top 命令。被殺死的進程 PID 在日誌中都有記錄(例如 1156, 1203, 1137, 1143 等),雖然它們現在已經不存在,但同一個程式很可能會重新啟動。查看類似的新進程。

    bash
    # 方法一:使用 ps 列出所有 python3.6 進程的詳細命令
    ps aux | grep python3.6
    
    # 方法二:使用 top 或 htop,然後按 c 鍵顯示完整命令列
    top
    # 在 top 介面中,按 'c'

    重點看每一行的最後部分,通常會顯示腳本名稱,例如 /path/to/your/script.py 或 -m somemodule

  2. 分析系統日誌(如果程式有輸出)
    檢查系統的日誌檔(如 /var/log/messages 或 /var/log/syslog),或者檢查您的應用程式(如 Apache、MySQL)的日誌,看看在 OOM 發生前後有沒有相關的錯誤資訊,這可能能幫你定位到是哪個應用先出的問題。

  3. 使用審計工具(事後分析)
    您可以安裝 auditd 服務來記錄所有進程的執行事件,這樣下次再發生時就能有據可查。

    bash
    # 安裝 auditd
    sudo yum install auditd # 適用於 CentOS/RHEL
    sudo apt-get install auditd # 適用於 Ubuntu/Debian
    
    # 新增一條規則,記錄所有執行 python3.6 的動作
    sudo auditctl -a always,exit -F arch=b64 -S execve -F exe=/usr/bin/python3.6
    
    # 查看審計日誌
    sudo ausearch -sc execve -x python3.6

第二部分:如何預防?

單純重啟進程無法解決問題,必須找到記憶體消耗過大的根本原因。

1. 優先優化您的程式碼
這是最根本的解決方法。多個 Python 進程被殺死,極有可能是因為:
記憶體洩漏(Memory Leak):程式沒有正確釋放不再使用的物件,導致記憶體使用量隨時間無限增長。
載入了過大的資料集:例如一次性將一個幾GB的檔案讀入記憶體進行處理。
不合理的快取策略:快設了太多內容且沒有過期機制。

text
**如何優化?**
*   使用 `tracemalloc`、`objgraph`、`pympler` 等工具來分析 Python 程式的記憶體使用情況和洩漏點。
*   對於處理大資料,使用分塊讀取、生成器(Generators)等方式,避免一次性載入。
*   為快取設定大小上限或過期時間。

2. 調整 OOM Killer 的優先級
OOM Killer 在選擇殺死哪個進程時,會參考 oom_score_adj(日誌裡有顯示)。值越低,進程越重要,越不容易被殺死。您可以保護關鍵進程(如 MySQL、主要的 Web Server)。

text
```bash
# 保護 mysqld 進程,使其非常不容易被殺死
echo '-1000' | sudo tee /proc/$(pgrep mysqld)/oom_score_adj

# 但注意,保護了某些進程,意味著其他進程(如你的 Python 程式)會更容易被殺死。
# 更永久的辦法是編輯 /etc/systemd/system.conf 或服務的 systemd unit file,加入 `OOMScoreAdjust=-1000`
```

3. 增加交換空間(Swap Space)
Swap 空間可以作為記憶體的緩衝地帶,雖然速度慢,但可以在物理記憶體不足時防止進程被直接殺死。如果您的伺服器完全沒有配置 Swap,強烈建議新增。

text
```bash
# 檢查當前 Swap 空間
free -h

# 新增一個 2GB 的 Swap 檔案
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 永久生效,寫入 /etc/fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```

4. 增加實體記憶體(RAM)
如果經過優化後,您的應用程式確實就需要那麼多記憶體才能正常運行,那麼升級伺服器規格,增加實體記憶體是最直接、最有效的方案。

5. 設定程序監控與重啟
使用 systemd 或 supervisord 來管理您的 Python 程式。它們可以在程式被 OOM Killer 殺掉後自動將其重啟,並記錄日誌,保證服務的可用性(但這並不能解決記憶體不足的根本問題)。


第三部分:是否能定時做什麼處理?

「定時重啟」是一種治標不治本的方法,但在某些無法立即優化程式碼的場景下,可以作為暫時的緩解措施。

使用 Crontab 定時重啟服務
例如,如果您發現某個 Python 服務在運行一天後會佔用過多記憶體,可以設定在每天凌晨流量低峰期重啟它。

bash
# 編輯 root 的定時任務
sudo crontab -e

# 加入以下行,表示每天凌晨 3 點重啟您的服務
# 假設您的服務由 systemd 管理,名為 my-python-service
0 3 * * * systemctl restart my-python-service

# 或者直接殺死程序(不優雅)
# 0 3 * * * pkill -f "python3.6 /path/to/script.py"

注意:這只是暫時的應急方案。長期來看,仍然需要按照第二部分的建議,從根本上去分析和解決記憶體問題。

總結與行動步驟

  1. 立即檢查:使用 ps aux | grep python3.6 立刻查看是哪幾個 Python 腳本在運行。

  2. 臨時緩解

    • 考慮新增 Swap 空間。

    • 為關鍵資料庫進程(如 mysqld)設定 OOM 保護。

  3. 根本解決

    • 定位問題程式:找到那幾個被殺的 Python 程式。

    • 程式碼優化:使用記憶體分析工具對其進行分析和優化。

    • 資源評估:如果優化後記憶體仍然不足,規劃升級伺服器記憶體。

  4. 監控:設定監控系統(如 Prometheus/Grafana),監控伺服器的記憶體使用情況,提前預警,而不是等 OOM 發生後再來處理。



21430 S /usr/sbin/sendmail -FCronDa  0.6  0.0

21431 S /usr/sbin/postdrop -r        0.6  0.0

21596 S CROND                        0.2  0.0

21613 S /usr/sbin/sendmail -FCronDa  0.6  0.0

21614 S /usr/sbin/postdrop -r        0.6  0.0

21712 S CROND                        0.2  0.0

21717 S /usr/sbin/sendmail -FCronDa  0.6  0.0

21718 S /usr/sbin/postdrop -r        0.6  0.0

21925 S CROND                        0.2  0.0

我已停用 postfix 為何還有sendmail,postdrop ...

您观察到的这些 sendmail 和 postdrop 进程确实是 Postfix 邮件系统的组件,即使您已经停用了 Postfix 服务,这些进程仍然可能出现。以下是原因和解决方案:

为什么停用 Postfix 后仍有这些进程?

  1. Cron 作业触发:这些进程大多与 CROND 进程相关联,表明是由 cron 作业触发的。当 cron 作业产生输出时,系统会尝试通过邮件发送这些输出给用户。

  2. 未完全卸载:您可能只是停止了 Postfix 服务,但没有完全卸载它,或者系统有其他组件仍然依赖邮件功能。

  3. 系统默认配置:许多 Linux 发行版默认配置 cron 将作业输出通过邮件发送。

解决方案

1. 完全卸载 Postfix

bash
# 对于基于 RedHat/CentOS 的系统
sudo yum remove postfix

# 对于基于 Debian/Ubuntu 的系统
sudo apt-get remove postfix

2. 禁用 cron 邮件通知

即使卸载了 Postfix,cron 仍然会尝试发送邮件,这可能会导致错误。您可以通过以下方式禁用 cron 邮件:

bash
# 在 crontab 文件顶部添加(对所有用户生效)
echo 'MAILTO=""' | sudo tee -a /etc/crontab

# 或者为特定用户设置
crontab -e
# 在文件顶部添加
MAILTO=""

3. 重定向 cron 输出到日志文件或 /dev/null

对于个别 cron 作业,您可以重定向输出:

bash
# 将输出重定向到 /dev/null(完全丢弃)
* * * * * /path/to/command > /dev/null 2>&1

# 或将输出重定向到日志文件
* * * * * /path/to/command >> /var/log/mycommand.log 2>&1

4. 杀死现有的 sendmail 进程

bash
# 杀死所有 sendmail 进程
sudo pkill sendmail

# 杀死所有 postdrop 进程
sudo pkill postdrop

5. 安装替代的轻量级邮件工具(可选)

如果您确实需要发送邮件功能,但不想运行完整的 Postfix,可以考虑安装更轻量的替代方案:

bash
# 安装 ssmtp(简单邮件传输)
sudo yum install ssmtp  # CentOS/RHEL
sudo apt-get install ssmtp  # Ubuntu/Debian

# 或安装 nullmailer
sudo yum install nullmailer  # CentOS/RHEL
sudo apt-get install nullmailer  # Ubuntu/Debian

6. 检查系统日志确认问题

bash
# 查看系统日志中与邮件相关的错误
sudo grep -i mail /var/log/messages
sudo grep -i postfix /var/log/messages
sudo grep -i sendmail /var/log/messages

预防措施

为了防止这些进程再次出现,您可以:

  1. 定期检查进程

    bash
    # 定期检查是否有 sendmail 或 postdrop 进程
    ps aux | grep -E '(sendmail|postdrop)'
  2. 设置监控警报

    bash
    # 如果检测到这些进程,发送警报
    if ps aux | grep -q '[s]endmail'; then
        echo "警告: sendmail 进程仍在运行 - $(date)" | mail -s "进程警报" admin@example.com
    fi
  3. 审查所有 cron 作业

    bash
    # 查看所有用户的 cron 作业
    sudo ls /var/spool/cron/
    sudo cat /var/spool/cron/*
    
    # 查看系统 cron 作业
    ls /etc/cron.d/
    ls /etc/cron.hourly/
    ls /etc/cron.daily/
    ls /etc/cron.weekly/
    ls /etc/cron.monthly/

通过上述方法,您应该能够彻底解决停用 Postfix 后仍然出现相关进程的问题。最有效的方法是完全卸载 Postfix 并配置 cron 不发送邮件通知。


关于 Sleeping 进程

Sleeping 进程通常是正常的,不一定是资源消耗者

  1. Sleeping 状态的含义

    • 睡眠状态的进程正在等待某些事件发生(如I/O操作完成、网络请求响应、定时器到期等)

    • 它们不占用CPU资源,但可能占用内存

    • 这是大多数后台服务和守护进程的正常状态

  2. 您的系统状态

    • 总进程数:583个

    • 运行中的进程:仅1个(非常低)

    • 睡眠进程:465个(这是正常的)

    • 没有停止或僵尸进程(这是好现象)

如何查看 Sleeping 进程的详细信息

要查看哪些进程处于睡眠状态以及它们可能占用的资源,可以使用以下命令:

bash
# 查看所有进程的状态和资源使用
ps aux

# 只查看睡眠状态的进程
ps -eo pid,state,cmd,%mem,%cpu | grep -w S

# 按内存使用排序查看进程
ps aux --sort=-%mem | head -20

# 按CPU使用排序查看进程
ps aux --sort=-%cpu | head -20

# 使用更直观的 htop 工具(如果已安装)
htop

系统资源分析

  1. CPU 使用情况

    • 用户空间程序占用:16.1% (us)

    • 系统空间占用:4.0% (sy)

    • 空闲:78.7% (id)

    • CPU 使用率总体较低,系统负载较轻

  2. 内存使用情况

    • 总内存:964MB

    • 已使用:781MB (约81%)

    • 可用内存:183MB

    • 缓存:244MB(这部分内存会在需要时被释放)

  3. 交换空间使用

    • 总交换空间:262MB

    • 已使用:251MB (约96%)

    • 可用交换空间:仅10MB

    • 这是最需要关注的问题:交换空间几乎耗尽


潜在问题与解决方案

1. 交换空间不足问题

您的交换空间使用率高达96%,这会导致系统性能严重下降,因为系统被迫频繁在内存和磁盘之间交换数据。

解决方案

bash
# 增加交换空间(如前所述)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 永久生效
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

2. 识别真正消耗资源的进程

虽然大多数睡眠进程是正常的,但有些可能确实占用了过多资源。使用以下命令识别:

bash
# 查看内存占用最高的前10个进程
ps aux --sort=-%mem | head -11

# 查看CPU占用最高的前10个进程
ps aux --sort=-%cpu | head -11

# 查看I/O等待高的进程(如果有iotop)
sudo iotop

3. 检查是否有内存泄漏

某些睡眠进程可能有内存泄漏问题:

bash
# 监控进程内存增长
watch -n 5 'ps aux --sort=-%mem | head -10'

# 查看进程的详细内存映射
pmap -x <PID>

总结与建议

  1. Sleeping 进程通常是正常的:465个睡眠进程在Linux系统中是常见的,它们大多是在等待事件的后台服务。

  2. 主要问题是交换空间不足:您的交换空间几乎耗尽,这是最需要立即解决的问题。

  3. 系统负载较轻:CPU使用率不高,只有一个进程在运行,系统负载平均值也较低。

  4. 建议操作

    • 立即增加交换空间

    • 监控内存使用情况,确保不再出现OOM Killer

    • 定期检查是否有异常进程消耗过多资源

    • 考虑优化应用程序以减少内存使用

您的系统整体状态还算健康,主要是交换空间不足需要解决。增加交换空间后,应该能够显著改善系统稳定性。


沒有留言:

張貼留言