原文連接,轉載註明來源便可。
本文代碼:GitHub
本文目錄:php
之前使用 Laravel 作 web 項目時,是根據 MVC 去劃分目錄結構的,即 Controller 層處理業務邏輯,Model 層處理數據庫的 CURD,View 層處理數據渲染與頁面交互。以及 MVP、MVVM 都是將整個項目的代碼是集中在一個代碼庫中,進行業務處理。這種單一聚合代碼的方式在前期實現業務的速度很快,但在後期會暴露不少問題:nginx
微服務是一種軟件架構,它將一個大且聚合的業務項目拆解爲多個小且獨立的業務模塊,模塊即服務,各服務間使用高效的協議(protobuf、JSON 等)相互調用便是 RPC。這種拆分代碼庫的方式有如下特色:git
本質上,兩者只是聚合與拆分代碼的方式不一樣。github
參考:微服務架構的優點與不足golang
接下來建立一個處理用戶信息的微服務:UserInfoService,客戶端經過 name 向服務端查詢用戶的年齡、職位等詳細信息,需先安裝 gRPC 與 protoc 編譯器:web
go get -u google.golang.org/grpc go get -u github.com/golang/protobuf/protoc-gen-go
├── proto │ ├── user.proto // 定義客戶端請求、服務端響應的數據格式 │ └── user.pb.go // protoc 爲 gRPC 生成的讀寫數據的函數 ├── server.go // 實現微服務的服務端 └── client.go // 調用微服務的客戶端
每一個微服務有本身獨立的代碼庫,各自之間在通訊時須要高效的協議,要遵循必定的數據結構來解析和編碼要傳輸的數據,在微服務中常使用 protobuf 來定義。shell
Protobuf(protocal buffers)是谷歌推出的一種二進制數據編碼格式,相比 XML 和 JSON 的文本數據編碼格式更有優點:數據庫
它沒有 XML 的標籤名或 JSON 的字段名,更爲輕量,更多參考json
只需定義一份 .proto 文件,便可使用各語言對應的 protobuf 編譯器對其編譯,生成的文件中有對 message 編碼、解碼的函數數組
json_encode()
和 json_decode()
去編解碼,在 Golang 中需使用 json 標準庫的 Marshal()
和 Unmarshal()
… 每次解析和編碼比較繁瑣syntax = "proto3"; // 指定語法格式,注意 proto3 再也不支持 proto2 的 required 和 optional package 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 類型 }
# protoc 編譯器的 grpc 插件會處理 service 字段定義的 UserInfoService # 使 service 能編碼、解碼 message $ protoc -I . --go_out=plugins=grpc:. ./user.proto
package proto import ( 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"` } // 爲字段自動生成的 Getter func (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 main import (...) // 定義服務端實現約定的接口 type UserInfoService struct{} var u = UserInfoService{} // 實現 interface func (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 main import (...) 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 的微服務系列教程