Go工程師體系課 003

8次閱讀

grpc

  • grpc
  • grpc-go

grpc 無縫集成了 protobuf

protobuf

  • 習慣用 Json、XML 數據存儲格式的你們,相信大多都沒聽過 Protocol Buffer。
  • Protocol Buffer 其實是 Google 出品的一種輕量 & 高效的結構化數據存儲格式,性能比 Json、XML 真的強!太!多!
  • protobuf 經歷了 protobuf2 和 protobuf3,pb3 比 pb2 簡化了很多,目前主流的版本是 pb3。

protoc 的下載

下載 protoc 和go get github.com/golang/protobuf/protoc-gen-go

syntax = "proto3";

option go_package = "."; // 這名不寫現在編譯不了

message HelloRequest {string name = 1; //1 是編號不是值}
protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

會在目錄中生成一個 helloworld.pb.go 的文件

壓縮比的對比

package main

import (
 "RpcLearn/protobuf"
 "encoding/json"
 "fmt"
 "github.com/golang/protobuf/proto"
)

// TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
type Hello struct {Name string `json:"name"`}

func main() {
 //TIP <p>Press <shortcut actionId="ShowIntentionActions"/> when your caret is at the underlined text
 // to see how GoLand suggests fixing the warning.</p><p>Alternatively, if available, click the lightbulb to view possible fixes.</p>
 //s := "gopher"
 //fmt.Printf("Hello and welcome, %s!\n", s)
 //
 //for i := 1; i <= 5; i++ {
 // //TIP <p>To start your debugging session, right-click your code in the editor and select the Debug option.</p> <p>We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
 // // for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.</p>
 // fmt.Println("i =", 100/i)
 //}
 jsonStruct := Hello{Name: "gopher",}
 jsonReq, _ := json.Marshal(jsonStruct)
 fmt.Println(string(jsonReq))
 fmt.Println(len(jsonReq))
 req := __.HelloRequest{Name: "gopher",}
 rsp, _ := proto.Marshal(&req)
 fmt.Println(rsp)
 fmt.Println(len(rsp))

}

差不多兩倍的差異

Go 語言中使用 Protobuf 的變化說明

教程內容 當前實際
以前只有一個 .pb.go 文件 現在生成兩個文件:一個 .pb.go(數據結構),一個 _grpc.pb.go(gRPC 接口)
插件不分離 插件已拆分爲 protoc-gen-goprotoc-gen-go-grpc,需要分別安裝和調用

📌 當前行爲說明

從 Go 的 Protobuf 插件 v1.20+ 開始,gRPC 支持被拆分爲單獨的插件 protoc-gen-go-grpc,用於生成服務接口相關代碼。這種方式更模塊化,職責更清晰。

因此,執行如下命令:

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./your_file.proto

將會生成:

  • your_file.pb.go: 包含消息結構(如 Request、Response)
  • your_file_grpc.pb.go: 包含服務接口定義(如 YourServiceServer)

建議保留並同步這兩個文件。

簡單梳理一下 grpc 開的流程

  1. 創建存根文件及方法定義,然後編譯
// proto/helloworld.proto
syntax = "proto3";
option go_package = ".;proto";

service Greeter{rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {string name = 1;}
message HelloReply {string message = 1;}
  1. server 端實現定義方法的調用, 注意 server 實現(鴨子類型)
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{Message: "Hello " + req.Name,}
 return reply, nil
}

func main() {g := grpc.NewServer()                     // 創建一個新的 gRPC 服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {log.Fatalf("failed to serve: %v", err)
 }
}
  1. 客戶端調用實現
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全連接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {panic(err)
 }
 fmt.Println(r.Message)

}

grpc 的 stream 方式(流模式)

  1. 簡單模式(simple RPC)
  2. 服務端的數據流
  3. 客戶端的數據流
  4. 雙向的

集中學習 grpc

Protobuf 類型 Go 類型
double float64
float float32
int32 int32
int64 int64
uint32 uint32
uint64 uint64
sint32 int32
sint64 int64
fixed32 uint32
fixed64 uint64

默認值

默認值說明

在 Protobuf 中,不同字段類型有以下默認值:

  • 字符串類型(string):默認值是一個空字符串。
  • 字節類型(bytes):默認值是一個空的字節序列。
  • 布爾類型(bool):默認值是 false
  • 數值類型(數字類型):默認值爲 0
  • 枚舉類型:默認值是第一個定義的枚舉值,且必須是 0
  • 消息類型(message):默認值是未設置(即 null)。

注意事項

  1. 對於標量消息字段:一旦消息被解析後,無法區分字段是否被設置爲默認值(例如布爾值是否爲 false)還是字段根本未被設置。
  2. 布爾值的使用建議:避免定義布爾值的默認值爲 false,因爲可能會導致觸發無意的行爲。
  3. 標量消息的序列化:如果一個標量消息字段被設置爲默認值,該值不會被序列化傳輸。

可以通過參考 Generated Code Guide 來更詳細地瞭解 Protobuf 默認值的處理方式。


枚舉類型的定義

在需要爲消息類型的某個字段指定一組“預定義值序列”時,可以使用枚舉類型。每個枚舉值都對應一個整數值。

示例

下面是一個枚舉的定義示例:

message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 1;
}
  • 服務端數據流(獲取實時數據)
  • 客戶端數據流(上報數據)
  • 雙向數據流
 syntax = "proto3";

option go_package = ".;proto";

service Greeter {rpc GetStream (StreamReqData) returns (stream StreamResData); // 服務端流模式
    rpc PutStream (stream StreamReqData) returns (StreamResData); // 客戶端流模式
    rpc AllStream (stream StreamReqData) returns (stream StreamResData); // 雙向流模式
}

message StreamReqData {string data = 1;}

message StreamResData {string message = 1;}

編譯生成 go 的 protobuf

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

服務端代碼

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "fmt"
 "google.golang.org/grpc"
 "log"
 "net"
 "sync"
 "time"
)

const PORT = ":50052"

type server struct {proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型}

func (s server) GetStream(data *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {
 //TODO implement me
 //panic("implement me")
 i := 0
 for {
  i++
  _ = res.Send(&proto.StreamResData{Message: fmt.Sprintf("%v", time.Now().Unix()),
  })
  time.Sleep(time.Second)
  if i >= 10 {break}
 }
 return nil
}

func (s server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
 //TODO implement me
 //panic("implement me")//
 for {if a, err := cliStr.Recv(); err != nil {fmt.Println(err)
   break
  } else {fmt.Println(a)
  }

 }
 return nil
}

func (s server) AllStream(allStr proto.Greeter_AllStreamServer) error {
 //TODO implement me
 //panic("implement me")
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {defer wg.Done()
  for {if a, err := allStr.Recv(); err != nil {fmt.Println(err)
    break
   } else {fmt.Println(" 客戶端發送的消息:", a)
   }
  }

 }()
 go func() {defer wg.Done()
  for {
   allStr.Send(&proto.StreamResData{Message: fmt.Sprintf(" 我是服務端數據:%v\n", time.Now().Unix()),
   })
   time.Sleep(time.Second)
  }

 }()
 wg.Wait()
 return nil
}

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {md, ok := metadata.FromIncomingContext(ctx)
    if (!ok) {// handle missing metadata}
    // do something with metadata
    return &pb.SomeResponse{}, nil}

func main() {
 // 創建一個新的 gRPC 服務器
 g := grpc.NewServer()
 proto.RegisterGreeterServer(g, &server{}) // 註冊服務
 listener, err := net.Listen("tcp", PORT)
 if err != nil {log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {log.Fatalf("failed to serve: %v", err)
 }
}

客戶端代碼

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "sync"
 "time"
)

func main() {
 // Create a new gRPC server
 conn, err := grpc.("localhost:50052",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 //res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: " 慕課網 "})
 //for {// r, err := res.Recv()
 // if err != nil {
 //  break
 // }
 // fmt.Println(r)
 //}
 //// 客戶端流模式
 //putS, _ := c.PutStream(context.Background())
 //i := 0
 //for {
 // i++
 // putS.Send(&proto.StreamReqData{Data: fmt.Sprintf(" 慕課網 %v", i)})
 // time.Sleep(time.Second)
 // if i >= 10 {
 //  break
 // }
 //}

 // 雙向流模式
 allStr, _ := c.AllStream(context.Background())
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {defer wg.Done()
  for {if a, err := allStr.Recv(); err != nil {fmt.Println(err)
    break
   } else {fmt.Println(" 我是客戶端:%v", a)
   }
  }
 }()
 go func() {defer wg.Done()
  i := 0
  for {
   i++
   allStr.Send(&proto.StreamReqData{Data: fmt.Sprintf(" 雙向的慕課網:%v", i)})
   time.Sleep(time.Second)
   if i >= 10 {break}
  }
 }()
 wg.Wait()}

Protobuf 類型與 Go 類型對應關係(含編碼建議)

.proto Type 描述說明 Go Type
double 浮點類型 float64
float 浮點類型 float32
int32 使用變長編碼,對負值效率低;如有可能爲負數,建議用 sint32 替代 int32
int64 使用變長編碼 int64
uint32 使用變長編碼 uint32
uint64 使用變長編碼 uint64
sint32 使用變長編碼,對負值更高效(比 uint32 更好) int32
sint64 使用變長編碼,對負值更高效 int64
fixed32 始終佔 4 字節,適用於總是比 2²²⁸ 大的值,比 uint32 更高效 uint32
fixed64 始終佔 8 字節,適用於總是比 2²⁵⁶ 大的值,比 uint64 更高效 uint64
sfixed32 始終佔 4 字節 int32
sfixed64 始終佔 8 字節 int64
bool 布爾值 bool
string 必須是 UTF-8 或 ASCII 編碼的文本 string
bytes 可包含任意順序的字節數據 []byte

option go_package

編譯生成目錄的指定

如何在一個 proto 中引入其它 proto

import "base.proto"

import "google/protobuf/empty.proto"

嵌套的 message

枚舉類型

enum Gender{
  MALE = 0;
  FEMALE = 1;
}

map 類型 map<string,string> mp = 4

如何使用時間戳 google/protobuf/timestamp.proto

message HelloRequest {
  string name = 1; // 姓名 相當於文檔
  string url = 2;
  Gender g = 3;
  map<string, string> mp = 4;
  google.protobuf.Timestamp addTime = 5;
}

grpc 的更多功能

gRPC 讓我們可以像本地調用一樣實現遠程調用,對於每一次的 RPC 調用中,都可能會有一些有用的數據,而這些數據就可以通過 metadata 來傳遞。metadata 是以 key-value 的形式存儲數據的,其中 key 是 string 類型,而 value 是[]string,即一個字符串數組類型。metadata 使得 client 和 server 能夠爲對方提供關於本次調用的一些信息,就像一次 http 請求的 RequestHeader 和 ResponseHeader 一樣。http 中 header 的生命週期是一 http 請求,那麼 metadata 的生命週期就是一次 RPC 調用。

metadata

不是直接侵入到業務代碼中的

// 新建
type MD mp[string][]string
// 創建
// 第一種方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})

// 第二種方式 key 不區分大小寫,會被統一轉成小寫。md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
    "key2", "val2",
)

發送 metadata

md := metadata.Pairs("key", "val")

// 新建一個有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 單向 RPC
response, err := client.SomeRPC(ctx, someRequest)

接收 metadata

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {md, ok := metadata.FromIncomingContext(ctx)
    if !ok {// handle missing metadata}
    // do something with metadata
    return &pb.SomeResponse{}, nil}

grpc 的攔截器

服務端和客戶端的攔截器

package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{Message: "Hello " + req.Name,}
 return reply, nil
}

func main() {interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {log.Println("Request: 接收到一個新的請求 ", req)
  resp, err = handler(ctx, req)
  if err != nil {log.Println("Error:", err)
  }
  log.Println("Response: 處理這個新請求完成 ", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的 gRPC 服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全連接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 // 添加攔截器
 clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println(" 耗時 ", time.Since(start), err)
  return err
 }
 opt := grpc.WithUnaryInterceptor(clientInterceptor)
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {panic(err)
 }
 fmt.Println(r.Message)

}

grpc 通過 metadata 來驗證登錄

// server
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/metadata"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{Message: "Hello " + req.Name,}
 return reply, nil
}

func main() {interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {log.Println("Request: 接收到一個新的請求 ", req)
  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {log.Println(" 沒有 metadata")
   return resp, status.Error(codes.Unauthenticated, " 沒有 metadata")
  }

  var (
   appid  string
   appkey string
  )
  if val1, ok := md["appid"]; ok {appid = val1[0]
  }
  if val2, ok := md["appkey"]; ok {appkey = val2[0]
  }
  if appid != "1010101" || appkey != "123456" {return resp, status.Error(codes.Unauthenticated, " 無 token 認證信息 ")
  }

  resp, err = handler(ctx, req)
  if err != nil {log.Println("Error:", err)
  }
  log.Println("Response: 處理這個新請求完成 ", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的 gRPC 服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

type customCredentials struct {
}

func (c *customCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{
  "appid":  "1010101",
  "appkey": "123456",
 }, nil
}
func (c *customCredentials) RequireTransportSecurity() bool {return false}

func main() {

 // 添加攔截器
 //clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {// start := time.Now()
 // // 生成 metadata
 // md := metadata.New(map[string]string{
 //  "appid":  "1010101",
 //  "appkey": "123456",
 // })
 // ctx = metadata.NewOutgoingContext(ctx, md)
 // err := invoker(ctx, method, req, reply, cc, opts...)
 // fmt.Println(" 耗時 ", time.Since(start), err)
 // return err
 //}
 //opt := grpc.WithUnaryInterceptor(clientInterceptor
 // 另一種方式
 opt := grpc.WithPerRPCCredentials(&customCredentials{}) // 有一個專門的 metadata 的攔截器
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {panic(err)
 }
 fmt.Println(r.Message)

}

grpc(protoc-gen-validate)

protoc-gen-validate(簡稱 PGV)是一個 Protocol Buffers 插件,用於在生成的 Go 代碼中添加結構體字段的驗證邏輯。

它通過在 .proto 文件中添加 validate 規則,自動爲每個字段生成驗證代碼,避免你手動寫驗證邏輯。

syntax = "proto3";

import "validate/validate.proto";

message User {string name = 1 [(validate.rules).string.min_len = 3];
  int32 age = 2 [(validate.rules).int32.gte = 18];
}

查看 protoc-gen-validate 的簡介

grpc_proto_validate

syntax = "proto3";

import "validate.proto";

option go_package = ".;proto";


service Greeter {rpc SayHello (Person) returns (Person);
}

message Person {uint64 id = 1 [(validate.rules).uint64.gt = 999];

  string email = 2 [(validate.rules).string.email = true];

  string mobile = 3 [(validate.rules).string = {pattern: "^1[3-9]\\d{9}$"
    }
  ];
}

編譯這個 proto 添加一個--validate_out="lang=go" , 先要安裝go install github.com/bufbuild/protoc-gen-validate@latest

protoc \
  --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  --validate_out=lang=go:. \
  ./helloworld.proto
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型}

func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person, error) {
 // Simulate some processing
 return &proto.Person{
  Id:     request.Id,
  Mobile: request.Mobile,
  Email:  request.Email,
 }, nil
}

type Validator interface {Validate() error
}

func main() {//p := new(proto.Person)
 //err := p.Validate()
 //if err != nil {// panic(err)
 //}
 var interceptor grpc.UnaryServerInterceptor
 interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {if v, ok := req.(Validator); ok {if err := v.Validate(); err != nil {return nil, status.Error(codes.InvalidArgument, err.Error())
   }
  }
  return handler(ctx, req)
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的 gRPC 服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":50051")
 if err != nil {log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

type customCredential struct{}

func main() {
 // 攔截器(可選)clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println(" 調用耗時:", time.Since(start), " 錯誤:", err)
  return err
 }

 // 使用新方式連接(推薦)conn, err := grpc.NewClient("localhost:50051",
  grpc.WithTransportCredentials(insecure.NewCredentials()), // 替代 grpc.WithInsecure()
  grpc.WithUnaryInterceptor(clientInterceptor),
 )
 if err != nil {panic(err)
 }
 defer conn.Close()

 // 構建 gRPC 客戶端
 c := proto.NewGreeterClient(conn)

 // 調用 RPC
 rsp, err := c.SayHello(context.Background(), &proto.Person{
  Id:     1000,
  Email:  "bobby@test.com",
  Mobile: "13222223333",
 })
 if err != nil {panic(err)
 }

 fmt.Println(" 返回 ID:", rsp.Id)
}

grpc 中的異常處理

1. grpc 的狀態碼

grpc 狀態碼文檔


go 的異常處理

1. 服務端

st := status.New(codes.InvalidArgument, "invalid username")

2. 客戶端

st, ok := status.FromError(err)
if !ok {// Error was not a status error}
st.Message()
st.Code()

超時機制

ctx,_ = content.WithTimeout(context.Background(),time.Second*3)
正文完
 0