錄:3步搞定死機(jī),兼顧性能優(yōu)化)
電腦重啟不了排查實(shí)錄:3步搞定死機(jī),兼顧性能優(yōu)化
剛升級(jí)完驅(qū)動(dòng),電腦重啟不了?別急著砸鍵盤(pán)。
版本升級(jí)后 API 全變了,內(nèi)核加載邏輯變了,舊配置直接沖突。
這時(shí)候硬重啟只是治標(biāo),我們要做的是定位瓶頸,順手做個(gè)性能優(yōu)化。
項(xiàng)目目標(biāo):構(gòu)建自動(dòng)化故障診斷工具
很多兄弟一遇到“電腦重啟不了”,第一反應(yīng)就是拔電源。這沒(méi)錯(cuò),但下次呢?還是卡住。
我們需要一個(gè)輕量級(jí)的腳本,能在系統(tǒng)半死機(jī)狀態(tài)(比如還有網(wǎng)絡(luò),或者能進(jìn)安全模式)下,快速收集關(guān)鍵日志,并給出重啟建議。
這個(gè)項(xiàng)目目標(biāo)很明確:快速定位:是硬盤(pán)壞了??jī)?nèi)存爆了?還是驅(qū)動(dòng)沖突?
數(shù)據(jù)留存:把崩潰前的最后幾條日志抓下來(lái),別丟。
溫和重啟:通過(guò)系統(tǒng)指令強(qiáng)制重啟,避免硬件損傷。這不是為了炫技,而是為了在下次故障發(fā)生時(shí),你能像醫(yī)生看X光片一樣,一眼看出病灶。對(duì)于經(jīng)常跑大型編譯、虛擬機(jī)集群的開(kāi)發(fā)者來(lái)說(shuō),這種“重啟不了”的代價(jià)是巨大的——丟掉的上下文、中斷的構(gòu)建,都是時(shí)間成本。
目錄結(jié)構(gòu):極簡(jiǎn)主義,拒絕過(guò)度設(shè)計(jì)
別搞那種幾百個(gè)文件的框架,排查工具講究一個(gè)“快”。
我們?cè)陧?xiàng)目根目錄下只放三個(gè)核心文件:
project-root/
├── diag.py # 主診斷腳本
├── config.yaml # 閾值配置(內(nèi)存、CPU、磁盤(pán)IO)
└── logs/ # 自動(dòng)生成的日志目錄└── crash_YYYYMMDD_HHMMSS.logdiag.py 是核心,config.yaml 讓你可以根據(jù)不同機(jī)器調(diào)整靈敏度。比如你跑的是高負(fù)載服務(wù)器,內(nèi)存閾值設(shè)高一點(diǎn);如果是辦公本,設(shè)低一點(diǎn)。
logs 目錄必須存在,腳本會(huì)在啟動(dòng)時(shí)自動(dòng)創(chuàng)建,防止寫(xiě)入失敗。
核心代碼實(shí)現(xiàn):Python 驅(qū)動(dòng)的診斷邏輯
這里我們用 Python 3.9+ 實(shí)現(xiàn)。為什么選 Python?因?yàn)榭缙脚_(tái),Windows、Linux、Mac 都能跑,而且?guī)熵S富。
核心依賴只有 psutil(監(jiān)控資源)和 pyyaml(讀配置)。
import psutil
import yaml
import os
import time
import subprocess
import logging
from datetime import datetime# 配置日志,輸出到控制臺(tái)和文件
log_file = flogs/crash_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log
os.makedirs(logs, exist_ok=True)
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s',handlers=[logging.FileHandler(log_file),logging.StreamHandler()]
)def load_config(path='config.yaml'):加載閾值配置try:with open(path, 'r') as f:return yaml.safe_load(f)except Exception as e:logging.error(f配置加載失敗: {e}, 使用默認(rèn)值)return {'cpu_threshold': 95,'mem_threshold': 95,'disk_io_threshold': 90,'restart_timeout': 30}def check_system_health(cfg):檢查系統(tǒng)健康度返回: (is_healthy: bool, reason: str)cpu_percent = psutil.cpu_percent(interval=1)mem_percent = psutil.virtual_memory().percentdisk_io = psutil.disk_io_counters()# 計(jì)算磁盤(pán)IO使用率 (簡(jiǎn)化處理,實(shí)際需結(jié)合讀寫(xiě)速度)# 這里僅作為演示,真實(shí)場(chǎng)景需監(jiān)控特定分區(qū)io_percent = (disk_io.read_count + disk_io.write_count) % 100 # 偽代碼,實(shí)際需更復(fù)雜邏輯logging.info(f當(dāng)前狀態(tài) - CPU: {cpu_percent}%, MEM: {mem_percent}%, IO: {io_percent}%)if cpu_percent cfg['cpu_threshold']:return False, fCPU占用過(guò)高: {cpu_percent}%if mem_percent cfg['mem_threshold']:return False, f內(nèi)存占用過(guò)高: {mem_percent}%if io_percent cfg['disk_io_threshold']:return False, f磁盤(pán)IO瓶頸: {io_percent}%return True, System OKdef force_restart(reason):執(zhí)行強(qiáng)制重啟在Windows下使用 shutdown /r /t 0在Linux下使用 rebootlogging.warning(f觸發(fā)強(qiáng)制重啟: {reason})# 等待日志刷新time.sleep(2)if os.name == 'nt':# Windows: /r 重啟, /t 0 立即, /f 強(qiáng)制關(guān)閉應(yīng)用程序subprocess.call(['shutdown', '/r', '/t', '0', '/f'])else:# Linux/Macsubprocess.call(['reboot'])def main():logging.info(=== 電腦重啟不了診斷工具啟動(dòng) ===)cfg = load_config()# 循環(huán)監(jiān)控,直到系統(tǒng)健康或超時(shí)start_time = time.time()timeout = cfg.get('restart_timeout', 30)while time.time() - start_time timeout:is_healthy, reason = check_system_health(cfg)if not is_healthy:# 記錄詳細(xì)進(jìn)程列表,方便事后分析top_procs = psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent'])with open(log_file, 'a') as f:f.write(f\n--- 崩潰前Top進(jìn)程 ---\n)for p in sorted(top_procs, key=lambda x: x.info['cpu_percent'], reverse=True)[:10]:f.write(fPID:{p.pid} Name:{p.name()} CPU:{p.info['cpu_percent']}% MEM:{p.info['memory_percent']}%\n)force_restart(reason)breakelse:time.sleep(5)else:logging.info(監(jiān)控超時(shí),未觸發(fā)重啟,系統(tǒng)可能已恢復(fù)或需人工干預(yù))if __name__ == '__main__':main()逐行解析關(guān)鍵點(diǎn)psutil.cpu_percent(interval=1):這個(gè) interval=1 很關(guān)鍵。默認(rèn)是0,返回的是上次調(diào)用的差值,第一次調(diào)用永遠(yuǎn)是0。設(shè)為1秒,能拿到真實(shí)的瞬時(shí)負(fù)載。
日志雙寫(xiě):handlers 里同時(shí)加了 FileHandler 和 StreamHandler。文件用來(lái)存檔,屏幕用來(lái)實(shí)時(shí)看。如果系統(tǒng)卡死,屏幕可能沒(méi)反應(yīng),但文件通常還在寫(xiě)(除非磁盤(pán)也掛了)。
進(jìn)程快照:在觸發(fā)重啟前,top_procs 那一段是靈魂。它把當(dāng)時(shí)占用最高的10個(gè)進(jìn)程PID和名字記下來(lái)。很多“重啟不了”是因?yàn)槟硞€(gè)后臺(tái)服務(wù)(比如殺毒軟件、更新程序)死鎖了。有了這個(gè)日志,重啟后一查PID,立馬知道是誰(shuí)在搞鬼。
跨平臺(tái)重啟:os.name 判斷系統(tǒng)。Windows 用 shutdown 命令是最穩(wěn)定的,比 PowerShell 腳本更底層,不容易被攔截。運(yùn)行與測(cè)試:模擬“假死”場(chǎng)景
代碼寫(xiě)完了,怎么測(cè)?總不能真等電腦死機(jī)吧?
我們用一個(gè)簡(jiǎn)單的技巧:人為制造資源瓶頸。準(zhǔn)備環(huán)境:
安裝依賴:pip install psutil pyyaml編寫(xiě) config.yaml:
cpu_threshold: 50 # 故意設(shè)低,方便測(cè)試
mem_threshold: 50
disk_io_threshold: 50
restart_timeout: 10制造壓力:
打開(kāi)另一個(gè)終端,運(yùn)行一個(gè)簡(jiǎn)單的死循環(huán)腳本,把CPU吃滿:
# stress_test.py
import time
while True:pass或者在 Windows 上打開(kāi)任務(wù)管理器,啟動(dòng)幾個(gè)高CPU占用的程序。運(yùn)行診斷:
python diag.py預(yù)期現(xiàn)象:日志開(kāi)始滾動(dòng),顯示 CPU 占用超過(guò) 50%。
5秒后(因?yàn)?sleep(5)),再次檢測(cè)。
如果持續(xù)超標(biāo),打印 Top 進(jìn)程列表。
執(zhí)行 shutdown /r /t 0,電腦黑屏,重啟。避坑指南:殺毒軟件干擾:有些殺毒軟件會(huì)攔截 shutdown 命令,或者把 diag.py 標(biāo)記為可疑。測(cè)試前,把腳本目錄加入白名單。
日志寫(xiě)入失?。喝绻疟P(pán)IO極高,logging.FileHandler 可能會(huì)阻塞。進(jìn)階版可以用異步日志,但對(duì)于排查工具來(lái)說(shuō),同步寫(xiě)入更安全,確保數(shù)據(jù)不丟。優(yōu)化擴(kuò)展:從“重啟”到“性能優(yōu)化”
重啟只是止血,我們要的是性能優(yōu)化。
這個(gè)腳本目前只能做“重啟”,怎么讓它更智能?增加“優(yōu)雅退出”邏輯:
在強(qiáng)制重啟前,先嘗試發(fā)送 SIGTERM 給高占用進(jìn)程。如果10秒內(nèi)沒(méi)退出,再 SIGKILL。
# 偽代碼
for proc in top_procs:try:proc.terminate()except:pass
time.sleep(10)
# 檢查是否退出,沒(méi)退出再 kill這樣能減少數(shù)據(jù)丟失風(fēng)險(xiǎn)。歷史趨勢(shì)分析:
每次運(yùn)行都把 CPU/MEM 數(shù)據(jù)追加到一個(gè) CSV 文件。
重啟后,用 Python 讀這個(gè) CSV,畫(huà)個(gè)折線圖。
你會(huì)發(fā)現(xiàn):是不是每次在編譯到 80% 的時(shí)候內(nèi)存就爆?如果是,那就是代碼內(nèi)存泄漏,或者編譯器配置問(wèn)題。
這才是性能優(yōu)化的核心:數(shù)據(jù)驅(qū)動(dòng),而不是玄學(xué)調(diào)參。集成到 CI/CD:
在自動(dòng)化測(cè)試服務(wù)器上,把這個(gè)腳本跑起來(lái)。
如果服務(wù)器重啟了,自動(dòng)把 logs/crash_*.log 打包,通過(guò)郵件或企業(yè)微信發(fā)給開(kāi)發(fā)者。
我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì),靠這個(gè)功能,在一個(gè)周內(nèi)定位了三個(gè)導(dǎo)致服務(wù)器死機(jī)的 Java 線程死鎖問(wèn)題。Stack Overflow 上有很多關(guān)于 OutOfMemoryError 的討論,但光看堆棧不夠,要看系統(tǒng)級(jí)的資源曲線。Windows 事件日志集成:
在 Windows 上,可以用 win32eventlog 庫(kù)讀取系統(tǒng)事件日志。
有些“重啟不了”是因?yàn)閮?nèi)核藍(lán)屏(BSOD),但重啟后藍(lán)屏信息丟了。
腳本可以在啟動(dòng)時(shí),讀取最近一次的 BSOD dump 文件,提取錯(cuò)誤代碼。
這樣,你重啟后打開(kāi)腳本,它直接告訴你:“上次死機(jī)是因?yàn)?nvlddmkm.sys (NVIDIA驅(qū)動(dòng)) 崩潰”。
這比你自己翻事件查看器快十倍。小結(jié)
“電腦重啟不了”是個(gè)老生常談的問(wèn)題,但大多數(shù)人的處理方式是“拍腦袋重啟”。
今天分享的這套方案,核心不在于代碼有多復(fù)雜,而在于思維轉(zhuǎn)變:把故障當(dāng)數(shù)據(jù)看:日志、進(jìn)程、資源曲線,都是線索。
工具化:別靠記憶,靠腳本。
性能優(yōu)化前置:在崩潰前發(fā)現(xiàn)瓶頸,比崩潰后重啟更有價(jià)值。這個(gè)腳本你可以直接拿去用,改改閾值,適配你的環(huán)境。
更重要的是,它幫你建立了一種“可觀測(cè)性”的思維。
下次再遇到重啟不了,別慌,打開(kāi)終端,跑一下 diag.py。
看看日志,看看進(jìn)程,看看趨勢(shì)。
你會(huì)發(fā)現(xiàn),問(wèn)題往往沒(méi)那么玄乎,只是你以前沒(méi)看見(jiàn)而已。
你公司項(xiàng)目里是怎么處理這種“死機(jī)”問(wèn)題的?是用腳本自動(dòng)恢復(fù),還是直接重啟了事?歡迎評(píng)論,分享你的實(shí)戰(zhàn)經(jīng)驗(yàn)。