立刻執行 df -hdu -sh。確認掛載點。找出最占用空間的項目。所謂訊息佇列伺服器磁碟已滿,就是儲存空間耗盡為零。這種情況會導致訊息接收停止、觸發 Broker 關閉,並帶來訊息遺失風險。暫時性佇列在重新啟動後若發生索引重建,可能會遺失全部資料。此時你面對的是磁碟空間不足,而且每一分鐘都至關重要。

本執行手冊遵循嚴格順序:先診斷問題,再手動釋放空間,然後復原 Broker,最後防止下次再次當機。優先檢查日誌檔案和佇列。先核實儲存使用情況,而不是只看佇列數量。動作要快,但在弄清楚究竟是什麼占滿磁碟之前,不要刪除任何內容。

診斷訊息佇列伺服器磁碟已滿

確認磁碟使用情況和掛載點

先從 df -h 開始。這個命令會顯示每個掛載點及其使用百分比。你需要準確找出哪個掛載點達到了 100%。訊息佇列伺服器磁碟已滿通常發生在某一個磁碟區上,而不是所有磁碟區同時滿。接著,對 /var/lib/var/log 以及你的 Broker 資料目錄執行 du -sh。這樣可以依體積找出最大空間占用者,並指向根本原因。

要特別留意這樣一種情況:某個磁碟區已經滿了,但其上的佇列目錄看起來並不大。訊息佇列目錄可能與 Broker 的實際儲存目錄位於不同掛載點上。兩邊都要檢查。磁碟寫滿往往源於磁碟空間不足、權限問題,或你未曾預料路徑中的檔案系統錯誤。在刪除任何東西之前,先確認掛載點。

找出最占空間的項目和佇列深度

僅看佇列深度很容易誤判。應使用管理工具檢查深度和訊息內容。Prometheus 可透過 rabbitmq_queue_messages 指標暴露停放佇列和等待佇列的訊息數。死信速率計數器比深度更早揭示變化。讀取 x-death 標頭可以統計重試次數,並將暫時性故障與永久性故障區分開來。當死信速率在 5 分鐘內超過發布速率的 1% 時,應觸發警示。

即使佇列看起來為空,儲存也可能已經滿了。這就是 MSMQ 悖論。要核實的是儲存,而不是佇列計數。索引開銷是主要原因之一:

常見原因影響
Kafka 磁碟空間預先配置每個分割區都會使用獨立的 LogSegment;分割區越多,占用放大越明顯
每個佇列的訊息索引儲存每個佇列都會保存索引,且每個索引檔案都會消耗空間
大型部署中的索引開銷大量佇列的索引可消耗數百 GB 空間;最佳化儲存後可大幅下降

還要檢查磁碟佇列長度,把它作為 I/O 壓力訊號。數值過高意味著磁碟子系統已經不堪負荷。舊日誌檔案和暫存檔也會占據空間。檢查日誌目錄和佇列目錄中是否存在陳舊資料。主機上的磁碟空間不足或記憶體不足會進一步惡化問題。應同時追蹤磁碟空間和記憶體,才能找到問題根因。

安全地手動清理訊息佇列

刪除舊日誌與暫存檔

先處理最容易下手的目標。一旦確認訊息佇列伺服器磁碟已滿,/var/log 目錄通常會有可以安全刪除的輪替日誌。執行 du -sh /var/log/ 找出最大的日誌檔案。刪除一週前的封存檔。Broker 自身的日誌輪替路徑——如 /var/log/rabbitmq//var/log/kafka/——也可能包含舊的 *.gz 檔案,可以刪除。/tmp 或 Broker 暫存目錄中的暫存檔也會占空間,也應一併清理。

如果因為磁碟已滿導致 Broker 無法存取,可以重新啟動進入復原模式。在 Ubuntu 上,可修改 GRUB 命令列進入單一使用者模式。這樣你可以取得一個 shell,在沒有其他程序干擾的情況下刪除 /var/log/ 中的日誌檔案。

如果另一個檔案系統上有可用空間,優先將較舊的 send-summary 檔案移走,而不是直接刪除。對於 GreenArrow,可使用 hvmail_move_old_send_summary_files

  1. 確認腳本位於 /var/hvmail/bin/hvmail_move_old_send_summary_files
  2. 對 send-summary 目錄執行 du -hs,估算所需可用空間。
  3. 建立目標目錄,例如 mkdir /media/scratch/var-hvmail-log-send-summary
  4. 以最小檔案年齡 7 天和目標路徑為參數執行該命令。
  5. 確保備份涵蓋新位置。Managed Backups 會自動處理這一點。

要遵守每個目錄的儲存配額。如果不能先完成日誌輪替,就不要刪除目前仍在寫入的 Broker 日誌。先刪除舊的輪替資料,往往就能釋放出大量空間,而無須立即動到佇列資料。這會給你爭取到足夠空間,以更安全的方式繼續操作。儲存磁碟區中的磁碟空間不足,可能會阻止 Broker 寫入新項目。僅這一步,就可能釋放出足夠空間,讓 Broker 乾淨地重新啟動。重新啟動前還要檢查主機是否存在磁碟空間不足或記憶體不足的問題。

清空死信佇列和過期訊息

清理完日誌之後,再處理佇列資料。訊息佇列目錄——例如 /var/lib/rabbitmq/mnesia 或同類目錄——保存著佇列資料。該位置如果磁碟空間不足,可能導致索引損毀。應使用 Broker 管理工具清空死信佇列。對於 RabbitMQ,可使用 rabbitmqctl purge_queue。對於 Kafka,可使用 kafka-delete-records 並指定目標 offset。在清理之前,務必先與業務方確認這些訊息不再需要用於重播。只有在確認之後,才能清理佇列。

絕對不要直接從檔案系統中刪除正在使用的訊息資料檔案。這樣會破壞索引並導致資料遺失。必須使用 Broker 命令。對於 MSMQ,儲存目錄即使在佇列看起來為空時也可能被寫滿。MSMQ 使用由 .stg.mq 檔案組成的儲存系統,即使訊息數顯示為零,這些檔案仍會持續占用空間。應透過在該目錄上執行 dir 來核實實際磁碟使用情況。

對於基於 PostgreSQL 的 Broker,舊的 WAL 檔案可能會占用大量儲存。先找出 WAL 增長的根因——例如不活躍的複寫槽、封存失敗,或大量批次寫入。如果原因是不再使用的複寫槽,且對應消費者已經消失,在確認其處於不活躍狀態後,可使用 SELECT pg_drop_replication_slot('slot_name') 刪除該複寫槽。然後執行 CHECKPOINT; 以回收不再需要的 WAL 區段。如果資料庫因磁碟已滿而停機,應先擴充 WAL 所在磁碟區,啟動 PostgreSQL,再繼續調查。作為最後手段,可先進行 dry run,再使用 pg_archivecleanup 清理舊區段。一個常見陷阱是:磁碟空間不足、權限問題或檔案系統錯誤會偽裝成佇列故障。切勿透過直接刪除資料檔案來「重設」佇列。

清空死信佇列後,執行 Broker 的維護命令。對於 RabbitMQ,可使用 rabbitmqctl force_gc 觸發壓縮整理。對於 Kafka,可使用分割區大小工具。隨後觀察幾分鐘佇列深度,確認新訊息已開始恢復流動。此時還不要急於重新啟動 Broker。先驗證佇列深度。手動清理訊息佇列的目標,是為 Broker 復原建立一個穩定基礎。

復原 Broker 並驗證完整性

重新啟動 Broker 並確認訊息流恢復

只有在釋放出足夠空間後,才啟動 Broker。執行你的服務管理器命令,並觀察啟動日誌中是否存在錯誤。磁碟寫滿可能觸發線性日誌行為,從而改變 Broker 在磁碟上處理訊息的方式。要逐行閱讀日誌輸出。確認生產者已重新連線,消費者已恢復投遞。

如果事故期間 rsyslog 的磁碟佇列被啟用,重新啟動後還要確認它們能夠正常排空。重新啟動後再次檢查佇列深度。如果佇列維持不變,通常意味著消費者被阻塞,或儲存目錄存在權限問題。還要關注日誌中是否持續出現重試訊息。這類重試通常指向一個有毒載荷,需要將其路由到死信佇列。

如有需要,重新平衡分割區或佇列

重新啟動後,分割區可能分布不均。檢查 Broker 指標,識別熱點。若某個 Broker 承擔了大部分負載,應將分割區或佇列遷移到負載較低的節點上。這個步驟在清理大量積壓後尤其重要,因為負載分布不均會導致某個磁碟區很快再次被寫滿。

在宣布復原完成之前,必須驗證訊息完整性。死信佇列用於承接有毒訊息,而在智慧代理架構中,它的重要性會進一步提升。當代理無法驗證訊息的語意完整性時,它會將載荷路由到死信佇列進行檢查,而不是直接丟棄或無限重試。這樣可以在復原結束之前提前暴露完整性故障。

訊息簽章與驗證可透過數位簽章確保真實性並防止竄改。訊息雜湊會使用帶有 PSS 填充的私鑰進行簽章,產生一個再經過 base64 編碼的簽章。相應的驗證函式會在訊息被處理之前校驗該簽章——從而在宣布復原完成前確認其完整性。

對於批次驗證,每個接收模組都會使用匹配的演算法、驗證標籤和金鑰檢查每一個訊息批次。驗證通過意味著處理正確,可以清理中間資料。驗證失敗則會觸發警示、檢查錯誤記錄、重新傳送訊息並重新處理。基於標籤的方案會透過識別碼選擇訊息資料,並依據標籤值驗證完整性。在關閉事故之前,請確認這三項檢查全部通過。

防止下一次磁碟寫滿事故

擴充 LVM 磁碟區並設定配額

這次事故你撐過來了。現在必須阻止下一次發生。最快的預防手段,就是在磁碟寫滿之前擴充 LVM 磁碟區。這可以防止因磁碟空間耗盡而導致崩潰。可按以下步驟操作:

  1. 執行 sudo lvssudo vgssudo pvs 檢查目前 LVM 設定,確認磁碟區群組中是否仍有可用空間。
  2. 使用磁碟區群組中全部可用空閒空間擴充邏輯磁碟區:sudo lvextend -l +100%FREE /dev/mapper/vg-lv_root
  3. 擴充檔案系統,使其與擴容後的磁碟區相符。對於 ext4,執行 sudo resize2fs /dev/mapper/vg-lv_root。對於 XFS,執行 sudo xfs_growfs /

為每個佇列和每個日誌目錄設定磁碟配額與保留策略。儲存配額能從源頭限制增長。設定佇列配額門檻,避免單一佇列占滿整個磁碟區。對於 Kafka,基於大小和基於時間的保留策略可以同時生效。Kafka 會採用先滿足的那個條件,從而同時提供時間和儲存兩方面的雙重保護。設定 log.retention.bytes 以限制每個分割區的總區段大小,設定 log.segment.bytes 以限制單一區段的大小,再設定 log.roll.hours 依排程產生新區段。保留週期應與業務需求對齊:即時資料保留 7 到 30 天,分析型資料保留 90 到 365 天,合規資料保留 3 到 7 年。資料保留超過 90 天後,磁碟使用往往呈線性增長,因此 30 到 90 天通常是較合適的平衡點。

設定保留策略和電子郵件警示

Linux 本身不會在磁碟即將寫滿時原生發出事件,因此任何解決方案都需要輪詢。可以使用 cron 搭配 ntfy 之類的通知工具,定期檢查磁碟使用率,並在超過門檻時發送警示。也可以查找現成腳本,透過輪詢磁碟空間並觸發電子郵件通知。還可考慮 Zabbix 或 Netdata 這類完整監控方案,它們可以在多個門檻上發送電子郵件警示。應按嚴重等級定義分層通知流程:75% 為 Warning,80% 為 Average,85% 為 High,90% 為 Critical,95% 為 Disaster。再根據等級把訊息路由到不同管道。

建議將監控警示門檻設定為 70% 和 85%。當儲存磁碟使用率連續 10 分鐘超過 70% 時觸發預警;當連續 5 分鐘超過 85% 時觸發嚴重警示。達到 100% 時,所有持久化訊息發送都會失敗。應警示的是寫滿趨勢,也就是 days-to-full,而不是只盯著目前占用百分比。具體數值沒有是否具備「預警 + 嚴重」兩個層級來得重要。

自動化清理任務,並監控其效果。套用訊息 TTL 策略,使超過門檻的舊訊息自動移除。設定最大佇列長度策略,並指定如 drop-head 或 reject-publish 之類的溢出行為。這些機制可以將磁碟使用控制在有界範圍內。使用 Prometheus 擷取 Broker 指標以追蹤可用磁碟空間。定義一個預警:當剩餘磁碟空間低於 10 GB 且持續 5 分鐘時觸發;定義一個嚴重警示:當剩餘磁碟空間低於 3 GB 且持續 1 分鐘時觸發。透過 Alert Manager 將警示路由到 PagerDuty 或 Slack。透過檢查 rabbitmqctl status,確認目前沒有活動警示。確認發布者已不再被阻塞。定期結合 df -hrabbitmqctl status | grep disk_free 監控磁碟空間。留意主機是否存在磁碟空間不足或記憶體不足問題。檢查日誌檔案和儲存目錄中是否有陳舊資料。按計畫刪除舊日誌檔案。這樣才能讓你的佇列保持健康,讓儲存使用更可預測。

復原分四步:診斷、手動清理空間、重新啟動 Broker、預防再次發生。最快的處理路徑,是在刪除任何內容之前,先弄清楚到底是什麼占滿了磁碟。暫時性佇列和 MSMQ 儲存都可能在佇列計數顯示為零時寫滿磁碟區,因此必須直接核查儲存目錄本身。現在,你已經擁有一套可重複執行的執行手冊。

向前看。要在 LVM 磁碟區寫滿前就完成擴充。透過自動化保留策略,讓舊日誌和陳舊檔案按計畫清出。讓每一類日誌都按定時策略輪替。把電子郵件警示設在 70% 使用率,而不是等到 100%。如果你能及早發現,訊息佇列伺服器磁碟已滿這種事故,只會發生一次。把這份手冊放在手邊。

常見問題

我該如何確認磁碟寫滿的確切原因?

執行 df -h 找出已寫滿的掛載點。對 /var/lib/var/log 和 Broker 資料目錄執行 du -sh。這樣就能看出究竟是舊日誌檔案、佇列索引,還是已儲存訊息占用了空間。

我可以直接從檔案系統刪除訊息資料檔案嗎?

絕對不要直接刪除正在使用的訊息資料檔案。這樣會破壞 Broker 索引,並造成永久性資料遺失。應使用諸如 rabbitmqctl purge_queuekafka-delete-records 之類的 Broker 管理命令。只能透過官方認可的工具來釋放空間。

為什麼 MSMQ 顯示佇列為空,但磁碟卻是滿的?

MSMQ 使用 .stg.mq 檔案進行儲存,即使訊息數顯示為零,這些檔案也可能依舊存在並持續占用空間。應在儲存目錄上執行 dir 以核實真實占用。務必檢查儲存目錄本身,而不要只看佇列計數。

我應該設定哪些監控門檻?

建議將預警設在 70%,嚴重警示設在 85%。監控重點應放在寫滿趨勢,而不是原始百分比。透過 Prometheus 和 Alert Manager 設定通知,實現自動化回應。