go grpc高級(jí)用法
錯(cuò)誤處理
gRPC 一般不在 message 中定義錯(cuò)誤。畢竟每個(gè) gRPC 服務(wù)本身就帶一個(gè) error 的返回值,這是用來(lái)傳輸錯(cuò)誤的專用通道。gRPC 中所有的錯(cuò)誤返回都應(yīng)該是 nil 或者 由 status.Status 產(chǎn)生的一個(gè)error。這樣error可以直接被調(diào)用方Client識(shí)別。
常規(guī)用法
當(dāng)遇到一個(gè)go錯(cuò)誤的時(shí)候,直接返回是無(wú)法被下游client識(shí)別的。
恰當(dāng)?shù)淖龇ㄊ?/strong>:
調(diào)用 status.New 方法,并傳入一個(gè)適當(dāng)?shù)腻e(cuò)誤碼,生成一個(gè) status.Status 對(duì)象
調(diào)用該 status.Err 方法生成一個(gè)能被調(diào)用方識(shí)別的error,然后返回
st := status.New(codes.NotFound, “some description”)
err := st.Err()
傳入的錯(cuò)誤碼是 codes.Code 類型。
此外還有更便捷的辦法:使用 status.Error。它避免了手動(dòng)轉(zhuǎn)換的操作。
err := status.Error(codes.NotFound, "some description")
進(jìn)階用法
上面的錯(cuò)誤有個(gè)問(wèn)題,就是 code.Code 定義的錯(cuò)誤碼只有固定的幾種,無(wú)法詳盡地表達(dá)業(yè)務(wù)中遇到的錯(cuò)誤場(chǎng)景。
gRPC 提供了在錯(cuò)誤中補(bǔ)充信息的機(jī)制:status.WithDetails 方法
Client 通過(guò)將 error 重新轉(zhuǎn)換位 status.Status ,就可以通過(guò) status.Details 方法直接獲取其中的內(nèi)容。
status.Detials 返回的是個(gè)slice, 是interface{}的slice,然而go已經(jīng)自動(dòng)做了類型轉(zhuǎn)換,可以通過(guò)斷言直接使用。
服務(wù)端示例
- 生成一個(gè) status.Status 對(duì)象
- 填充錯(cuò)誤的補(bǔ)充信息
// 生成一個(gè) status.Status
st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
// 填充錯(cuò)誤的補(bǔ)充信息 WithDetails
ds, err := st.WithDetails(
&epb.QuotaFailure{
Violations: []*epb.QuotaFailure_Violation{{
Subject: fmt.Sprintf("name:%s", in.Name),
Description: "Limit one greeting per person",
}},
},
)
if err != nil {
return nil, st.Err()
}
return nil, ds.Err()
客戶端的示例
- 調(diào)用RPC錯(cuò)誤后,解析錯(cuò)誤信息
- 通過(guò)斷言直接獲取錯(cuò)誤詳情
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
// 調(diào)用 RPC 如果遇到錯(cuò)誤就對(duì)錯(cuò)誤處理
if err != nil {
// 轉(zhuǎn)換錯(cuò)誤
s := status.Convert(err)
// 解析錯(cuò)誤信息
for _, d := range s.Details() {
// 通過(guò)斷言直接使用
switch info := d.(type) {
case *epb.QuotaFailure:
log.Printf("Quota failure: %s", info)
default:
log.Printf("Unexpected type: %s", info)
}
}
}
原理
這個(gè)錯(cuò)誤是如何傳遞給調(diào)用方Client的呢?
是放到 metadata中的,而metadata是放到HTTP的header中的。
metadata是key:value格式的數(shù)據(jù)。錯(cuò)誤的傳遞中,key是個(gè)固定值:grpc-status-details-bin。
而value,是被proto編碼過(guò)的,是二進(jìn)制安全的。
目前大多數(shù)語(yǔ)言都實(shí)現(xiàn)了這個(gè)機(jī)制。
多路復(fù)用
同一臺(tái)服務(wù)器上的多個(gè)RPC服務(wù)的多路復(fù)用,比如同時(shí)保存一個(gè)訂單的存根、一個(gè)歡迎的存根因?yàn)槎鄠€(gè)RPC服務(wù)運(yùn)行在一個(gè)服務(wù)端上,所以客戶端的多個(gè)存根之間是可以共享gRPC連接的
服務(wù)端代碼
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
// 注冊(cè)進(jìn)訂單服務(wù)
ordermgt_pb.RegisterOrderManagementServer(grpcServer, &orderMgtServer{})
// 注冊(cè)進(jìn)歡迎服務(wù)
hello_pb.RegisterGreeterServer(grpcServer, &helloServer{})
}
客戶端代碼
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 訂單服務(wù)建立實(shí)例連接
orderManagementClient := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
res, addErr := orderManagementClient.AddOrder(ctx, &order1)
// 歡迎服務(wù)建立實(shí)例連接
helloClient := hwpb.NewGreeterClient(conn)
hwcCtx, hwcCancel := context.WithTimeout(context.Background(), time.Second)
defer hwcCancel()
helloResponse, err := helloClient.SayHello(hwcCtx, &hwpb.HelloRequest{Name: "gRPC Up and Running!"})
fmt.Println("Greeting: ", helloResponse.Message)
}
元數(shù)據(jù)
在多個(gè)微服務(wù)的調(diào)用當(dāng)中,信息交換常常是使用方法之間的參數(shù)傳遞的方式,但是在有些場(chǎng)景下,一些信息可能和 RPC 方法的業(yè)務(wù)參數(shù)沒(méi)有直接的關(guān)聯(lián),所以不能作為參數(shù)的一部分,在 gRPC 中,可以使用元數(shù)據(jù)來(lái)存儲(chǔ)這類信息。
元數(shù)據(jù)創(chuàng)建
// 方法1
md := metadata.Pairs(
"1", "v1",
"1", "v2", // 方法1會(huì)把相同的鍵的字段合并,[ ]string{"v1","v2"}
"2", "v3",
)
// 方法2
md := metadata.New(map[string]string{"1":"v1","2":"v2"})
客戶端收發(fā)
在context中設(shè)置的元數(shù)據(jù)會(huì)轉(zhuǎn)換成線路層的gRPC頭信息和 trailer
客戶端發(fā)送這些頭信息,收件方會(huì)以頭信息的形式接收他們
// 創(chuàng)建元數(shù)據(jù)
md := metadata.Pairs(
"timestamp", time.Now().Format(time.StampNano),
"kn", "vn",
)
// 創(chuàng)建新元數(shù)據(jù)的上下文,這種方法會(huì)替換掉已有的上下文
mdCtx := metadata.NewOutgoingContext(context.Background(), md)
// 這種方法是將元數(shù)據(jù)附加到已有的上下文
ctxA := metadata.AppendToOutgoingContext(mdCtx, "k1", "v1", "k1", "v2", "k2", "v3")
// 定義頭信息和 trailer,可以用來(lái)接收元數(shù)據(jù)
var header, trailer metadata.MD
order1 := pb.Order{Id: "101", Items: []string{"iPhone XS", "Mac Book Pro"}, Destination: "San Jose, CA", Price: 2300.00}
res, _ := client.AddOrder(ctxA, &order1, grpc.Header(&header), grpc.Trailer(&trailer))
log.Print("AddOrder Response -> ", res.Value)
// 獲取頭信息
head, err := res.Header()
// 獲取trailer
trail, err := res.Trailer()
服務(wù)端收發(fā)
// 從上下文中獲取元數(shù)據(jù)列表
md, metadataAvailable := metadata.FromIncomingContext(ctx)
if !metadataAvailable {
return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata")
}
// 操作元數(shù)據(jù)邏輯
if t, ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i, e := range t {
fmt.Printf("====> Metadata %d. %s\n", i, e)
}
}
// 創(chuàng)建元數(shù)據(jù)
header := metadata.New(map[string]string{"location": "San Jose", "timestamp": time.Now().Format(time.StampNano)})
// 發(fā)送頭信息
grpc.SendHeader(ctx, header)
trailer := metadata.Pairs("status","ok")
// 設(shè)置trailer
grpc.SetTrailer(ctx,trailer)
負(fù)載均衡
負(fù)載均衡器代理
也就是說(shuō)后端的結(jié)構(gòu)對(duì)gRPC客戶端是不透明的,客戶端只需要知道均衡器的斷點(diǎn)就可以了,比如NGINX代理、Envoy代理
客戶端負(fù)載均衡
func main(){
roundrobinConn, err := grpc.Dial(
address,
grpc.WithBalancerName("round_robin"), // 指定負(fù)載均衡的算法
// 默認(rèn)是"pick_first",也就是從服務(wù)器列表中第一個(gè)服務(wù)端開始嘗試發(fā)送請(qǐng)求,成功則后續(xù)所有RPC都發(fā)往這個(gè)服務(wù)器
// "round_robin"輪詢調(diào)度算法,連接所有地址,每次向后端發(fā)送一個(gè)RPC
grpc.WithInsecure(),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer roundrobinConn.Close()
// 起10個(gè)RPC調(diào)度任務(wù)
makeRPCs(roundrobinConn, 10)
}
func makeRPCs(cc *grpc.ClientConn, n int) {
hwc := ecpb.NewEchoClient(cc)
for i := 0; i < n; i++ {
callUnary(hwc)
}
}
func callUnary(c ecpb.EchoClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
}

壓縮數(shù)據(jù)
在服務(wù)端會(huì)對(duì)已注冊(cè)的壓縮器自動(dòng)解碼,響應(yīng)時(shí)自動(dòng)編碼
始終從客戶端獲取指定的壓縮方法,如果沒(méi)被注冊(cè)就會(huì)返回Unimplemented
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
defer conn.Close()
client := pb.NewOrderManagementClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second * 5)
defer cancel()
order1 := pb.Order{Id: "101", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
// 通過(guò) grpc.UseCompressor(gzip.Name) 就可以輕松壓縮數(shù)據(jù)
res, _ := client.AddOrder(ctx, &order1, grpc.UseCompressor(gzip.Name))
}到此這篇關(guān)于go grpc高級(jí)用法的文章就介紹到這了,更多相關(guān)go grpc內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang如何交叉編譯各個(gè)平臺(tái)的二進(jìn)制文件詳解
這篇文章主要給大家介紹了關(guān)于Golang如何交叉編譯各個(gè)平臺(tái)的二進(jìn)制文件的相關(guān)資料,并介紹了golang如何讓編譯生產(chǎn)的二進(jìn)制文件變小,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn)
這篇文章主要介紹了基于go手動(dòng)寫個(gè)轉(zhuǎn)發(fā)代理服務(wù)的代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02
go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門
這篇文章主要為大家介紹了go語(yǔ)言標(biāo)準(zhǔn)庫(kù)fmt包的一鍵入門使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Golang并發(fā)讀取文件數(shù)據(jù)并寫入數(shù)據(jù)庫(kù)的項(xiàng)目實(shí)踐
本文主要介紹了Golang并發(fā)讀取文件數(shù)據(jù)并寫入數(shù)據(jù)庫(kù)的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
go語(yǔ)言基礎(chǔ) seek光標(biāo)位置os包的使用
這篇文章主要介紹了go語(yǔ)言基礎(chǔ) seek光標(biāo)位置os包的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05

