python程序報錯後除了try except之外有沒有好的辦法再次啟動?
只處理應該處理的異常!只重試可以重試的過程!不要隨隨便便去加try...except...,不加考慮隨便捕獲只會給你自己的調試過程帶來痛苦。
不是所有異常你都可以處理的,許多異常你就應該把它拋出到調用方去,如果你捕獲住一個異常不往外拋,你就等於告訴調用方前面的過程沒問題,可以繼續往下走,但如果真的出現了問題,繼續做下去往往會導致更嚴重的後果,通常都要比通過異常中止整個過程要糟糕。這些異常通常會最終拋出到執行程序的用戶,或者通過服務的框架記錄在日誌里或返回給遠程調用方,這樣看到這個異常就有線索去查找問題了。只有你確信捕獲了這個異常不會導致更嚴重的後果的時候才可以這麼做,而且如果這並不是一個常規會發生的異常,一定要將這個異常寫入日誌記錄下來,以提供查找問題的線索。不是所有過程都可以重試的,許多過程你retry一次就會徹底搞亂整個流程,比如說Web介面調用出現了網路異常,這種情況下請求可能已經執行,也可能沒有正確執行,大部分API設計的時候是沒有冪等性的,如果你重試一次,就會重新做一次相應的操作,比如你這個操作是轉賬100元,那你重試一次就會多轉100元。如果要引入失敗重試的機制,就必須在過程設計上保證這個過程是冪等的,冪等的意思就是相同的過程執行多次不會引起不正常的結果。這個設計要求其實有很多講究在裡面,並不是那麼容易就能滿足的,所以正常來說我是反對這種無腦重試三次的代碼的。這個原則也可以引申到使用外部工具比如supervisord自動重啟服務上,這其實是一個需要考慮的問題:
你的程序如果完全正常,那不該異常退出;
如果異常退出了,你又不知道出了什麼異常,你怎麼知道應該立即重啟呢?如果重啟之後又異常退出了怎麼辦?舉個例子來說,某些程序crash的時候可能會產生dump文件,可能會寫大量的異常日誌,這種情況下如果被不加考慮地自動重啟了,就會不斷寫日誌或者生成dump,很快可能會將磁碟寫滿,導致其他服務乃至整個伺服器出現異常。再比如某些程序啟動的時候會調用外部服務,初始化過程可能會對外部服務造成壓力,反覆異常反覆初始化就可能會導致整個外部服務都不可用。這些可能的後果需要進行謹慎的分析和討論,所以一些支持相應功能的工具,會有一些配置,比如重啟之後運行多長時間才允許再次重啟之類,需要謹慎進行配置。
如果你不能做出充分的論證證明這些自動重試、自動重啟的策略不會造成其他問題,我推薦你採用更保守的策略,只使用監控來檢測服務是否可用,通過告警人工處理的方式來解決服務可能出現的異常崩潰,在你的服務足夠穩定的情況下這並不會造成很大的運維壓力,如果服務不夠穩定,那你應該首先提高服務的穩定性。
KEEP IT SIMPLE AND STUPID, DON"T BE TOO SMART誰來告訴我try…except這種異常處理機制跟再次啟動有啥關係……
—————————————
好好回答一番。
首先理解一下報錯這個事。暫且不論更普遍的errcode的做法,對於python而言,所謂報錯就是拋出exception。當一個exception被拋出,它停止當前流程,沿著調用棧一層一層往外扔,直到這個exception被接住。如果拋到最後一層仍然沒被接住,則流程停止,異常拋給了操作系統,程序也就停下來了。
對於異常,最好的處理方法肯定是在外層把它接住,但是這需要改動原有的代碼。如果實在是不想改一行代碼,放心,仍然是有救的:你可以考慮寫一個獨立的程序,啟動一個新進程並調用原本的程序。當運行出錯時,異常會返回給OS,就能在你調用的程序中通過進程運行後的返回值取得執行情況。對於python,如果程序正常運行結束,解釋器會給OS一個為0的返回值,那麼當這次調用不為0時,再調一次就好了。
這樣,就可以完全避開try except了,畢竟只是用system/popen/subprocess啟動了一個進程並拿到了這個進程拋給OS的返回值,不需要鳥異常。
例如寫這樣一個runner.py
#!/usr/bin/env python3
import os
from sys import argv
if __name__ == "__main__":
while os.system("python" + " ".join(argv[1:])):
print("Halted by exception, restart")
然後我們隨便折騰一個小程序test.py,這個程序必然會報錯
#!/usr/bin/env python3
a = {}
print(a["1"]) # 拋出KeyError
接下來就可以用這個小玩意來反覆執行了。
python -m runner test.py
結果就很自然的會因為每次拋出異常帶來重新運行程序的效果。當然重試次數之類的、錯誤轉存至文件之類的功能,直接改runner就好,test里一行東西都不用加。並且你看,避開了try except吧?
其實類似的,別說py的程序,啥玩意寫出來的程序只要異常沒接丟到os的情況都可以用類似的方式和原理重新運行,嗯。於是我們就有了supervisor、launchctl……這樣的服務……你是想要我的 restart_if_failed 函數? https://github.com/lilydjwg/winterpy/blob/master/pylib/myutils.py#L135
再次啟動有很多種方案啊,但最關鍵的問題是:這樣的啟動是你需要的嘛?
- 一開始就註冊atexit函數。這種以Celery的重啟機製為例。實質上就是atexit上註冊一個os.execv,重新執行當前的python腳本
- 使用進程守護工具supervisord -&>是Python的那個工具,而不是centos那個supervisor,當然後者也可以。進程守護無非都是檢測子進程的退出狀態
- 更大的try catch
不存在一個可以修復自己所有錯誤的系統。
仔細想想這句話,這意味著的,如過所有錯誤都能修復,則系統應該是沒有錯誤,也就和修復錯誤矛盾。
正確的做法就是:藉助系統外工具還原錯誤現場,然後修復已經發現的錯誤。在錯誤的發現和修復的間隙,不是讓系統繼續運行(系統內嘗試修復),而是直接掛掉,並藉助系統外工具重啟,以回到一個確定還是正確的狀態運行。if __name__ == "__main__":
while 1:
try:
go()
except Exception as e:
go()
這個是我的解決辦法,
這個只適用於不關心錯誤,返回結果的只要腳本一直跑的任務....
額.剛剛跑一會還是有可能掛掉.
新版本
def main():
while 1:
try:
go()
except Exception as e:
main()
time.sleep(1)
if __name__ == "__main__":
main()
還在測試
大道理 @靈劍已經說完了,我來歪個樓: ajalt/fuckitpy
想知道try except有什麼弊端。我一直是設置嘗試的maxnum 然後try except…
今天剛寫了一個。
max_num = 0
while True:
try:
...
break
except Exception, e:
...
max_num += 1
if max_num &<= 3:
...
else:
# 如果重試3次後仍然出錯,則跳出
break
是想遇錯重試?比較直接的:定義一個計數變數,將處理函數的調用放在一個循環里,以計數變數與最大重試次數作為循環控制。捕捉到處理異常後,清理現場同時計數加1,進入下次循環。處理成功則跳出循環。
推薦閱讀: