gRPC 使用 protobuf 構建微服務

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文連結,轉載註明來源即可。
本文代碼:GitHub
本文目錄:

微服務架構

單一的程式碼程式庫

以前使用 Laravel 做 Web 專案時,是根據 MVC 去劃分目錄結構的,即 Controller 層處理商務邏輯,Model 層處理資料庫的 CURD,View 層處理資料渲染與頁面互動。以及 MVP、MVVM 都是將整個項目的代碼是集中在一個程式碼程式庫中,進行業務處理。這種單一彙總代碼的方式在前期實現業務的速度很快,但在後期會暴露很多問題:

  • 開發與維護困難:隨著業務複雜度的增加,代碼的耦合度往往會變高,多個模組相互耦合後不易橫向擴充
  • 效率和可靠性低:過大的代碼量將降低響應速度,應用潛在的安全問題也會累積

拆分的程式碼程式庫

微服務是一種軟體架構,它將一個大且彙總的商務專案拆解為多個小且獨立的業務模組,模組即服務,各服務間使用高效的協議(protobuf、JSON 等)相互調用即是 RPC。這種拆分程式碼程式庫的方式有以下特點:

  • 每個服務應作為小規模的、獨立的業務模組在運行,類似 Unix 的 Do one thing and do it well
  • 每個服務應在進行自動化測試和(分布式)部署時,不影響其他服務
  • 每個服務內部進行細緻的錯誤檢查和處理,提高了健壯性

二者對比

本質上,二者只是彙總與拆分代碼的方式不同。

參考:微服務架構的優勢與不足

構建微服務

UserInfoService 微服務

接下來建立一個處理使用者資訊的微服務:UserInfoService,用戶端通過 name 向服務端查詢使用者的年齡、職位等詳細資料,需先安裝 gRPC 與 protoc 編譯器:

go get -u google.golang.org/grpcgo get -u github.com/golang/protobuf/protoc-gen-go

目錄結構

├── proto│   ├── user.proto        // 定義用戶端請求、服務端響應的資料格式│   └── user.pb.go        // protoc 為 gRPC 產生的讀寫資料的函數├── server.go            // 實現微服務的服務端└── client.go            // 調用微服務的用戶端

調用流程

Protobuf 協議

每個微服務有自己獨立的程式碼程式庫,各自之間在通訊時需要高效的協議,要遵循一定的資料結構來解析和編碼要傳輸的資料,在微服務中常使用 protobuf 來定義。

Protobuf(protocal buffers)是Google推出的一種位元據編碼格式,相比 XML 和 JSON 的文本資料編碼格式更有優勢:

讀寫更快、檔案體積更小

它沒有 XML 的標籤名或 JSON 的欄位名,更為輕量,更多參考

語言中立

只需定義一份 .proto 檔案,即可使用各語言對應的 protobuf 編譯器對其編譯,產生的檔案中有對 message 編碼、解碼的函數

對於 JSON
  • 在 PHP 中需使用 json_encode()json_decode() 去編解碼,在 Golang 中需使用 json 標準庫的 Marshal()Unmarshal() … 每次解析和編碼比較繁瑣
  • 優點:可讀性好、開發成本低
  • 缺點:相比 protobuf 的讀寫速度更慢、儲存空間更多
對於 Protobuf
  • .proto 可產生 .php 或 *.pb.go … 在項目中可直接引用該檔案中編譯器產生的編碼、解碼函數
  • 優點:高效輕量、一處定義多處使用
  • 缺點:可讀性差、開發成本高

定義微服務的 user.proto 檔案

syntax = "proto3";    // 指定文法格式,注意 proto3 不再支援 proto2 的 required 和 optionalpackage proto;        // 指定產生的 user.pb.go 的包名,防止命名衝突// service 定義開放調用的服務,即 UserInfoService 微服務service UserInfoService {    // rpc 定義服務內的 GetUserInfo 遠程調用    rpc GetUserInfo (UserRequest) returns (UserResponse) {    }}// message 對應產生代碼的 struct// 定義用戶端請求的資料格式message UserRequest {    // [修飾符] 類型 欄位名 = 標識符;    string name = 1;}// 定義服務端響應的資料格式message UserResponse {    int32 id = 1;    string name = 2;    int32 age = 3;    repeated string title = 4;    // repeated 修飾符表示欄位是可變數組,即 slice 類型}

編譯 user.proto 檔案

# protoc 編譯器的 grpc 外掛程式會處理 service 欄位定義的 UserInfoService# 使 service 能編碼、解碼 message$ protoc -I . --go_out=plugins=grpc:. ./user.proto

產生 user.pb.go

package protoimport (    context "golang.org/x/net/context"    grpc "google.golang.org/grpc")// 請求結構type UserRequest struct {    Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`}// 為欄位自動產生的 Getterfunc (m *UserRequest) GetName() string {    if m != nil {        return m.Name    }    return ""}// 響應結構type UserResponse struct {    Id    int32    `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`    Name  string   `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`    Age   int32    `protobuf:"varint,3,opt,name=age" json:"age,omitempty"`    Title []string `protobuf:"bytes,4,rep,name=title" json:"title,omitempty"`}// ...// 用戶端需實現的介面type UserInfoServiceClient interface {    GetUserInfo(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)}// 服務端需實現的介面type UserInfoServiceServer interface {    GetUserInfo(context.Context, *UserRequest) (*UserResponse, error)}// 將微服務註冊到 grpc func RegisterUserInfoServiceServer(s *grpc.Server, srv UserInfoServiceServer) {    s.RegisterService(&_UserInfoService_serviceDesc, srv)}// 處理請求func _UserInfoService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {...}

服務端實現微服務

實現流程

代碼參考

package mainimport (...)// 定義服務端實現約定的介面type UserInfoService struct{}var u = UserInfoService{}// 實現 interfacefunc (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {    name := req.Name    // 類比在資料庫中尋找使用者資訊    // ...    if name == "wuYin" {        resp = &pb.UserResponse{            Id:    233,            Name:  name,            Age:   20,            Title: []string{"Gopher", "PHPer"}, // repeated 欄位是 slice 類型        }    }    err = nil    return}func main() {    port := ":2333"    l, err := net.Listen("tcp", port)    if err != nil {        log.Fatalf("listen error: %v\n", err)    }    fmt.Printf("listen %s\n", port)    s := grpc.NewServer()    // 將 UserInfoService 註冊到 gRPC    // 注意第二個參數 UserInfoServiceServer 是介面類型的變數    // 需要取地址傳參    pb.RegisterUserInfoServiceServer(s, &u)    s.Serve(l)}

運行監聽:

用戶端調用

實現流程

代碼參考

package mainimport (...)func main() {    conn, err := grpc.Dial(":2333", grpc.WithInsecure())    if err != nil {        log.Fatalf("dial error: %v\n", err)    }    defer conn.Close()    // 執行個體化 UserInfoService 微服務的用戶端    client := pb.NewUserInfoServiceClient(conn)    // 調用服務    req := new(pb.UserRequest)    req.Name = "wuYin"    resp, err := client.GetUserInfo(context.Background(), req)    if err != nil {        log.Fatalf("resp error: %v\n", err)    }    fmt.Printf("Recevied: %v\n", resp)}

運行調用成功:

總結

在上邊 UserInfoService 微服務的實現過程中,會發現每個微服務都需要自己管理服務端監聽連接埠,用戶端串連後調用,當有很多個微服務時連接埠的管理會比較麻煩,相比 gRPC,go-micro 實現了服務發現(Service Discovery)來方便的管理微服務,下節將隨服務的 Docker 化一起學習。

更多參考:Nginx 的微服務系列教程

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.