Go 語言與 gRPC 的完美結合

一、gRPC 簡介

gRPC(Remote Procedure Call) 是一種遠程過程調用技術, 通過壓縮和序列化數據來優化網絡通信, 可以顯著提高服務調用的性能和效率。

  1. gRPC 的概念

gRPC 是一個高性能、通用的開源 RPC 框架, 是一個由 Google 主導開發的 RPC 框架。其以 HTTP/2 爲基礎通信協議, 支持多種語言, 通過 protocol buffers 數據格式來實現服務之間的調用。

gRPC 使用 protocol buffers 來實現服務定義和數據序列化。Protocol buffers 是由 Google 開發的數據描述語言, 跨平臺、跨語言支持良好, 性能好、版本兼容性高。

gRPC 框架包含了服務端和客戶端兩部分。服務端實現 gRPC 服務接口, 客戶端通過 stub 來調用遠程服務。

  1. gRPC 的優勢
  • 基於 HTTP/2 設計, 性能高, 可擴展性強

  • 支持流式傳輸, 低延遲

  • 支持跨語言調用

  • 支持雙向流式通信

  • 支持服務發現及負載均衡

  • Protobuf 格式高效便捷

  1. gRPC 適用場景
  • 需要高性能、低延遲的服務通信

  • 要實現異構系統、不同語言間的調用

  • 需要流式數據處理的場景

  • 微服務架構下服務間的通信

二、gRPC 詳解

  1. gRPC 架構

gRPC 基於 HTTP/2 協議設計, 採用 Protocol Buffers 機制序列化結構化數據, 主要包含以下組件:

  • Stub: 客戶端調用 gRPC 服務的接口

  • gRPC Server: 實現 gRPC 服務邏輯的服務器

  • Channel: 抽象連接, 實現 Socket 級別連接及 RPC 交互

  • Protocol Buffers: 服務接口描述語言和數據序列化機制

在服務器端通過 Protobuf 接口實現服務, 客戶端通過 Stub 完成遠程調用。

  1. gRPC 通信流程

gRPC 通信流程主要包括:

  1. 客戶端調用 Stub 接口

  2. Stub 序列化參數爲 Protobuf 數據

  3. 數據通過 HTTP/2 協議發送給服務器

  4. 服務器獲取請求數據並反序列化

  5. 服務器處理請求並序列化返回結果

  6. 通過 HTTP/2 返回序列化後的數據

  7. 客戶端獲取響應數據並反序列化

  1. Protobuf 數據格式

Protocol Buffer (Protobuf) 是谷歌推出的一種輕便高效的數據序列化格式, 主要用於促進數據在網絡間高效傳輸。

Protobuf 的數據格式主要特點:

  • 跨平臺、語言中立

  • 版本兼容

  • 體積小, serialize 後數據大小隻有 XML 的 1/10 到 1/3

  • 序列化 / 反序列化速度快

Protobuf 通過. proto 文件定義數據結構, 然後使用 protoc 編譯器生成各目標語言的數據訪問類。

  1. gRPC 方法的定義

在 .proto 文件中可以定義服務接口和方法:

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

方法可以指定請求參數消息類型和返回值消息類型。

  1. gRPC 服務的實現

Go 語言中實現 gRPC 服務的步驟:

  1. 定義服務實現結構體

  2. 在結構體中實現服務接口方法

  3. 創建 gRPC 服務器

  4. 用服務器註冊服務

示例:

type HelloServiceImpl struct{}
func (p *HelloServiceImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
   // 方法實現
}
func main() {
  server := grpc.NewServer()
  pb.RegisterMessageServiceServer(server, &HelloServiceImpl{})
  server.Serve(lis)
}
  1. gRPC 客戶端調用

Go 語言 gRPC 客戶端調用主要分爲三步:

  1. 建立到 gRPC 服務器的連接

  2. 通過連接新建客戶端 stub 實例

  3. 使用 stub 調用遠程服務方法

示例:

conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := pb.NewHelloServiceClient(conn)
resp, err := client.SayHello(ctx, req)

三、gRPC 高級用法

  1. 流式 RPC

gRPC 支持流式 RPC 調用, 分爲四種類型:

  • 單向流式: 客戶端流式, 只有請求是流

  • 單向流式: 服務器流式, 只有響應是流

  • 雙向流式: 客戶端和服務器端都可以是流

在 proto 文件中使用 stream 關鍵字定義:

rpc ClientStream(stream HelloRequest) returns (HelloResponse);
rpc ServerStream(HelloRequest) returns (stream HelloResponse); 
rpc Bidirectional(stream HelloRequest) returns (stream HelloResponse);
  1. 證書和認證

gRPC 支持 SSL/TLS 安全傳輸及各種身份認證方式:

  • SSL/TLS 傳輸級安全保障

  • 支持基於證書、Token 和 AWS IAM 等認證手段

  1. 錯誤處理

gRPC 框架定義了狀態碼和錯誤模型, 客戶端可以根據狀態碼判斷 RPC 調用是否成功:

  • OK: 調用成功

  • Cancelled: 調用被取消

  • Unknown: 未知錯誤

  • InvalidArgument: 參數無效

  • DeadlineExceeded: 超時錯誤等

  1. 超時和取消

gRPC 支持請求級別的超時控制, 通過 Context 指定超時時間, 還可以通過 Context 取消正在執行的 RPC。

  1. gRPC 攔截器

gRPC 支持在服務器端和客戶端使用攔截器 (Interceptor) 攔截請求:

  • 客戶端攔截器: 攔截出站請求及響應

  • 服務端攔截器: 攔截入站請求及響應

主要用於日誌記錄、監控等功能。

  1. gRPC 元數據

gRPC 通過自定義元數據提供請求上下文等附加信息。可以在請求和響應中設置和獲取元數據。

  1. gRPC 路由

gRPC 支持按服務方法特徵進行請求路由, 路由選擇不同的後端服務。主要通過 gRPC intestine 實現。

  1. 和 HTTP/2 的對比

TGReuY

四、gRPC 實踐案例

  1. 簡單 RPC 服務端與客戶端

簡單的 gRPC Server 端和 Client 端示例, 演示基本的 RPC 服務開發、註冊和調用流程。

   // server
   type Server struct{}
   func (s *Server) SayHello(ctx context.Context, in *pb.StringRequest) (*pb.StringReply, error) {
     return &pb.StringReply{Value: "Hello " + in.Value}, nil
   }
   func main() {
     grpcServer := grpc.NewServer()
     pb.RegisterMessageServiceServer(grpcServer, &Server{})
     grpcServer.Serve(lis) 
   }
   // client
   conn, _ := grpc.Dial("localhost:1234", grpc.WithInsecure())
   client := pb.NewStringServiceClient(conn)
   reply, _ := client.SayHello(context.Background(), &pb.StringRequest{Value: "world"})
   fmt.Println(reply.Value)
  1. 帶參數驗證的 RPC 服務

示例在 gRPC 服務中實現參數校驗邏輯, 如果參數名稱不符合規範, 將返回錯誤。

   // server
   type Server struct{}
   func (s *Server) SayHello(ctx context.Context, in *pb.StringRequest) (*pb.StringReply, error) {
       if ok := validate(in.Value); !ok {
           return nil, status.Error(codes.InvalidArgument, "Invalid parameter")
       }
       return &pb.StringReply{Value: "Hello " + in.Value}, nil 
   }
   // client
   r, err := client.SayHello(context.Background(), &pb.StringRequest{Value: "World!"})
   if err != nil {
     //錯誤處理 
   }

通過狀態碼和錯誤信息, 客戶端可以明確判斷是什麼原因導致的調用異常。

  1. 客戶端、服務端流 RPC

示例實現了客戶端流式 RPC 和服務器流式 RPC。客戶端可以通過流方式連續發送多個請求, 服務器端可以返回流式的響應。

   // server
   type Server struct { }
   func (s *Server) ClientStream
   (stream pb.StringService_ClientStreamServer) error {
       for {
           in, err := stream.Recv()
           // 處理
           stream.SendAndClose(&pb.StringReply{})
       } 
   }
   // client
   stream, _ := client.ClientStream(context.Background()) 
   for {
       stream.Send(&pb.StringRequest{}) 
   }
   reply, _ := stream.CloseAndRecv()
  1. 雙向流 RPC

下面演示瞭如何使用 gRPC 完成雙向流 RPC 的編碼實現。客戶端和服務器端都可以獨立地通過流發送多個請求或響應。

// server
type Server struct{} 
func (s *Server) Bidirectional(
      stream pb.StringService_BidirectionalServer) error
   { 
    for {
        in, err := stream.Recv()
        if err != io.EOF {
         // 處理請求
         stream.Send(&pb.StringReply{})
        }
     }
   }
   // client
   stream, _ := client.Bidirectional(context.Background())
   go func() {
     for {
       stream.Send(&pb.StringRequest{})
     } 
   }()
   for {
     reply, err := stream.Recv()
     if err != nil {
       break
     } 
   }

總結

通過實踐表明, Go 語言結合 gRPC 框架可以方便高效地實現各類 RPC 服務。gRPC 優化了網絡資源利用效率, 支持複雜數據交互模式, 整體提高了分佈式服務架構的性能。

gRPC 的優點包括高效、跨平臺、流式傳輸等。但也存在需要應用 HTTP/2 特性的學習成本, 以及被限制在 Protobuf 生態內等問題。

隨着雲原生技術體系的逐步完善, gRPC 在微服務和 Service Mesh 體系中的地位日益突出, 它的重要性會持續提升。預計 gRPC 會越來越多地用於雲原生基礎設施的打造。

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