gRPC 中的錯誤處理:構(gòu)建更健壯、更可靠的微服務!
gRPC 在設計上鼓勵將錯誤處理作為服務的一部分,而不是將其隱藏在消息體中。每個 gRPC 服務天生就擁有一個錯誤返回值,作為專門的錯誤傳輸通道。所有 gRPC 中的錯誤返回值應該要么是 nil,要么是 由 status.Status 生成的錯誤。這樣可以確保調(diào)用方可以輕松識別錯誤。
1. 基本用法
簡單地返回 Go 錯誤并不能被下游客戶端識別。正確的做法是:
- 調(diào)用 status.New 方法并傳入合適的錯誤代碼以生成 status.Status 對象。
- 調(diào)用 status.Err 方法生成調(diào)用方可以識別的錯誤,然后返回。
st := status.New(codes.NotFound, "some description")
err := st.Err()
傳入的錯誤代碼類型為 codes.Code。或者,你可以使用 status.Error 方法,它可以避免手動轉(zhuǎn)換。
err := status.Error(codes.NotFound, "some description")
2. 高級用法
上述錯誤有一個限制:codes.Code 定義的錯誤代碼只涵蓋了某些場景,無法全面表達業(yè)務中遇到的各種錯誤場景。
gRPC 提供了一種機制來補充錯誤中的信息:status.WithDetails 方法。
客戶端可以通過將錯誤轉(zhuǎn)換回 status.Status 并使用 status.Details 方法直接獲取內(nèi)容。
status.Details 返回一個切片,它是 interface{} 的切片。但是,Go 會自動執(zhí)行類型轉(zhuǎn)換,允許通過斷言直接使用。
服務器端示例
- 生成 status.Status 對象
- 填充額外的錯誤信息
func ErrorWithDetails() error {
st := status.Newf(codes.Internal, fmt.Sprintf("something went wrong: %v", "api.Getter"))
v := &errdetails.PreconditionFailure_Violation{ //errDetails
Type: "test",
Subject: "12",
Description: "32",
}
br := &errdetails.PreconditionFailure{}
br.Violations = append(br.Violations, v)
st, _ = st.WithDetails(br)
return st.Err()
}
客戶端端示例
- 在 RPC 錯誤后解析錯誤信息
- 通過斷言直接獲取錯誤詳細信息
resp, err := odinApp.CreatePlan(cli.StaffId.AssetId, gentRatePlanMeta(cli.StaffId))
if status.Code(err) != codes.InvalidArgument {
logger.Error("create plan error:%v", err)
} else {
for _, d := range status.Convert(err).Details() {
//
switch info := d.(type) {
case *errdetails.QuotaFailure:
logger.Info("Quota failure: %s", info)
case *errdetails.PreconditionFailure:
detail := d.(*errdetails.PreconditionFailure).Violations
for _, v1 := range detail {
logger.Info(fmt.Sprintf("details: %+v", v1))
}
case *errdetails.ResourceInfo:
logger.Info("ResourceInfo: %s", info)
case *errdetails.BadRequest:
logger.Info("ResourceInfo: %s", info)
default:
logger.Info("Unexpected type: %s", info)
}
}
}
logger.Infof("create plan success,resp=%v", resp)
原理
這些錯誤是如何傳遞給調(diào)用方客戶端的呢?它們被放置在元數(shù)據(jù)中,然后在 HTTP 頭部中。元數(shù)據(jù)以鍵值對的形式存在。在錯誤傳輸中,鍵是一個固定值:grpc-status-details-bin。值由 proto 編碼,并且是二進制安全的。大多數(shù)語言都實現(xiàn)了這種機制。
圖片
注意
gRPC 對響應頭有限制,最大為 8K,因此錯誤不能太大。
參考
- Protocol Buffers Tutorial[1]
- errdetails[2]
總結(jié)
gRPC 提供了靈活的錯誤處理機制,允許你以結(jié)構(gòu)化的方式傳遞錯誤信息,幫助你構(gòu)建更健壯、更可靠的微服務。通過正確使用 status.Status 和 status.WithDetails 方法,你可以確保你的錯誤信息清晰易懂,并能被客戶端輕松理解和處理。
參考資料
[1] Protocol Buffers Tutorial: https://protobuf.dev/getting-started/gotutorial/
[2] errdetails: https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/errdetails