Go 語言的宕機恢復,如何防止程序奔潰

Recover 是一個 Go 語言的內建函數,可以讓進入宕機流程中的 goroutine 恢復過來,recover 僅在延遲函數 defer 中有效,在正常的執行過程中,調用 recover 會返回 nil 並且沒有其他任何效果,如果當前的 goroutine 陷入恐慌,調用 recover 可以捕獲到 panic 的輸入值,並且恢復正常的執行。

通常來說,不應該對進入 panic 宕機的程序做任何處理,但有時,需要我們可以從宕機中恢復,至少我們可以在程序崩潰前,做一些操作,舉個例子,當 web 服務器遇到不可預料的嚴重問題時,在崩潰前應該將所有的連接關閉,如果不做任何處理,會使得客戶端一直處於等待狀態,如果 web 服務器還在開發階段,服務器甚至可以將異常信息反饋到客戶端,幫助調試。

提示
在其他語言裏,宕機往往以異常的形式存在,底層拋出異常,上層邏輯通過 try/catch 機制捕獲異常,沒有被捕獲的嚴重異常會導致宕機,捕獲的異常可以被忽略,讓代碼繼續運行。

Go 語言沒有異常系統,其使用 panic 觸發宕機類似於其他語言的拋出異常,recover 的宕機恢復機制就對應其他語言中的 try/catch 機制。

讓程序在崩潰時繼續執行

下面的代碼實現了 ProtectRun() 函數,該函數傳入一個匿名函數或閉包後的執行函數,當傳入函數以任何形式發生 panic 崩潰後,可以將崩潰發生的錯誤打印出來,同時允許後面的代碼繼續運行,不會造成整個進程的崩潰。

保護運行函數:

 1package main
 2
 3import (
 4    "fmt"
 5    "runtime"
 6)
 7
 8// 崩潰時需要傳遞的上下文信息
 9type panicContext struct {
10    function string // 所在函數
11}
12
13// 保護方式允許一個函數
14func ProtectRun(entry func()) {
15
16    // 延遲處理的函數
17    defer func() {
18
19        // 發生宕機時,獲取panic傳遞的上下文並打印
20        err := recover()
21
22        switch err.(type) {
23        case runtime.Error: // 運行時錯誤
24            fmt.Println("runtime error:", err)
25        default: // 非運行時錯誤
26            fmt.Println("error:", err)
27        }
28
29    }()
30
31    entry()
32
33}
34
35func main() {
36    fmt.Println("運行前")
37
38    // 允許一段手動觸發的錯誤
39    ProtectRun(func() {
40
41        fmt.Println("手動宕機前")
42
43        // 使用panic傳遞上下文
44        panic(&panicContext{
45            "手動觸發panic",
46        })
47
48        fmt.Println("手動宕機後")
49    })
50
51    // 故意造成空指針訪問錯誤
52    ProtectRun(func() {
53
54        fmt.Println("賦值宕機前")
55
56        var a *int
57        *a = 1
58
59        fmt.Println("賦值宕機後")
60    })
61
62    fmt.Println("運行後")
63}

代碼輸出結果:

運行前
手動宕機前
error: &{手動觸發 panic}
賦值宕機前
runtime error: runtime error: invalid memory address or nil pointer dereference
運行後

對代碼的說明:
第 9 行聲明描述錯誤的結構體,保存執行錯誤的函數。
第 17 行使用 defer 將閉包延遲執行,當 panic 觸發崩潰時,ProtectRun() 函數將結束運行,此時 defer 後的閉包將會發生調用。
第 20 行,recover() 獲取到 panic 傳入的參數。
第 22 行,使用 switch 對 err 變量進行類型斷言。
第 23 行,如果錯誤是有 Runtime 層拋出的運行時錯誤,如空指針訪問、除數爲 0 等情況,打印運行時錯誤。
第 25 行,其他錯誤,打印傳遞過來的錯誤數據。
第 44 行,使用 panic 手動觸發一個錯誤,並將一個結構體附帶信息傳遞過去,此時,recover 就會獲取到這個結構體信息,並打印出來。
第 57 行,模擬代碼中空指針賦值造成的錯誤,此時會由 Runtime 層拋出錯誤,被 ProtectRun() 函數的 recover() 函數捕獲到。

panic 和 recover 的關係

panicrecover 的組合有如下特性:

提示
雖然 panic/recover 能模擬其他語言的異常機制,但並不建議在編寫普通函數時也經常性使用這種特性。

panic 觸發的 defer 函數內,可以繼續調用 panic,進一步將錯誤外拋,直到程序整體崩潰。

如果想在捕獲錯誤時設置當前函數的返回值,可以對返回值使用命名返回值方式直接進行設置。

文章首發:https://www.lmlphp.com/user/10603/article/item/422458

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0igEX_764Q2v4wFP2D_7mg