微服務架構
微服務是一種開發軟體的架構和組織方法,其中軟體由通過明確定義的API 進行通信的小型獨立服務組成, 這些服務由各個小型獨立團隊負責, 微服務架構使應用程式更易于擴展和更快地開發,從而加速創新并縮短新功能的上市時間,
將軟體應用程式構建為一組獨立、自治(獨立開發、部署和擴展)、松耦合、面向業務能力(強調能力,而不是完成任務)的服務,
為什么微服務軟體系統需要借助行程間(服務間,應用程式間)通信技術?
傳統軟體系統被進一步拆分為一組細粒度,自治和面向業務能力的物體,也就是微服務,
強、弱型別介面
服務API介面有強、弱型別之分,
強型別介面
?傳統的RPC服務(定制二進制協議 ,對訊息進行編碼和解碼),采用TCP傳輸訊息,RPC服務通常有嚴格的契約,開發服務前需要使用IDL(Interface description language)定義契約,最終通過契約自動生成強型別的服務器端、客戶端的介面,服務呼叫直接使用強型別的客戶端,(GRPC、Thrift)
- 優點:不需要手動的編碼和解碼、介面規范、自動代碼生成、編譯器自動型別檢查,
- 缺點:服務端和客戶端強耦合、任何一方升級改動可能會造成另一方break,自動代碼生成需要工具支持,開發這些工具的成本比較高,強型別介面開發測驗不友好、瀏覽器、Postman這些工具無法直接訪問這些強型別介面,
弱型別介面
?Restful服務通常采用JSON作為傳輸訊息,使用HTTP作為傳輸協議,沒有嚴格契約的概念,使用普通的HTTP Client即可呼叫,呼叫方需要對JSON訊息進行手動的編碼和解碼作業,(Springboot)
- 優點:服務端和客戶端非強耦合、開發測驗友好,
- 缺點:呼叫方手動編碼解碼,沒有自動代碼生成、沒有編譯期介面型別檢查、相對不規范、容易出現運行期錯誤,
Rest
描述性狀態轉移架構,是面向資源架構的基礎,將分布式應用程式建模為資源集合,訪問這些資源的客戶端可以變更這些資源的狀態,有三大局限性,
-
基于文本的低效訊息協議
-
應用程式之間缺乏強型別介面
-
架構風格難以強制實施
gRPC
gRPC 是一項行程間通信技術,用來連接、呼叫、操作和除錯分布式異構應用程式,
定義服務介面
開發gRPC應用需要先定義服務介面,使用的語言叫做 介面定義語言
- 確定消費者消費服務的方式
- 消費者遠程呼叫的方法和傳入的引數和訊息格式
優勢
- 提供高效的行程間通信,不使用json、xml,基于在HTTP/2之上的protocol buffers的二進制協議
- 簡單且定義良好的服務介面和模式
- 屬于強型別介面,構建跨團隊、技術型別的云原生應用程式,對于其所產生的的大多數運行時錯誤和互操作錯誤們可以通過靜態型別來克服
- 支持多語言
- 支持雙工流,同時構建傳統請求-回應風格的訊息以及客戶端流和服務端流
- 商業化特性內置支持
- 與云原生生態系統進行了集成
劣勢
- 不太適合面向外部服務
- 巨大的服務定義變更是復雜的開發流程
- 生態系統相對較小
撰寫gRPC服務
創建 client、service 目錄,分別用指令生成 go.mod 檔案
go mod init productinfo/client
go mod init productinfo/service
目錄結構
PS C:\Users\小能喵喵喵\Desktop\Go\gRPC\chapter2\productinfo> tree /f
├─client
│ │ client.go
│ │ go.mod
│ │ go.sum
│ │
│ ├─bin
│ │ client.exe
│ │
│ └─ecommerce
│ product_info.pb.go
│ product_info.proto
│ product_info_grpc.pb.go
│
└─service
│ go.mod
│ go.sum
│ server.go
│ service.go
│
├─bin
│ server.exe
│
└─ecommerce
product_info.pb.go
product_info.proto
product_info_grpc.pb.go
product_info.proto 介面定義
syntax = "proto3";
package ecommerce;
option go_package = ".";
service ProductInfo {
rpc addProduct (Product) returns (ProductID);
rpc getProduct (ProductID) returns (Product);
}
message Product{
string id = 1;
string name = 2;
string description = 3;
float price = 4;
}
message ProductID {
string value = https://www.cnblogs.com/linxiaoxu/archive/2022/09/18/1;
}
編譯工具
安裝:Release Protocol Buffers v21.6 · protocolbuffers/protobuf (github.com)
教程:Go Generated Code | Protocol Buffers | Google Developers
注: $GOPATH/bin要添加到系統環境變數里
protoc-gen-go-grpc: program not found or is not executable 解決方案
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
Please specify either:
? a "go_package" option in the .proto source file, or
? a "M" argument on the command line.
解決方案 在syntax=”proto3″;下一行增加option go_package配置項,
編譯方法
protoc [opt...] file.proto
/* 例如 */
protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto
- 當前版本編譯時,之前的方法
protoc --go_out=plugins=grpc:. *.proto不再使用,轉而用protoc --go_out=. --go-grpc_out=. ./hello.proto代替, go_out=.指定生成的pb.go檔案所在目錄(如果沒有該目錄,需要手動提前創建),.代表當前protoc執行目錄,結合.proto檔案中的option go_package,其最終的生成檔案目錄為go_out指定目錄/go_package指定目錄,go-grpc_out針對_grpc.pb.go檔案,同理,--go_opt=paths=source_relative,其含義代表生成的.pb.go檔案路徑不依賴于.proto檔案中的option go_package配置項,直接在go_out指定的目錄下生成.pb.go檔案(.pb.go檔案的package名還是由option go_package決定),--go-grpc_opt=paths=source_relative,針對_grpc.pb.go檔案,同理,
PS C:\Users\小能喵喵喵\Desktop\Go\gRPC\chapter2\productinfo\service\ecommerce>
protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative ./product_info.proto

Product和ProductID結構體定義在product_info.pb.go檔案中,通過product_info.proto自動生成,
service.go 業務邏輯代碼
package main
import (
"context"
pb "productinfo/service/ecommerce" // ^ 匯入 protobuf編譯器生成代碼的包
"github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ^ 實作 service/ecommerce 服務
type service struct {
pb.UnimplementedProductInfoServer
productMap map[string]*pb.Product
}
// ^ 實作方法邏輯、添加商品
// ^ ctx 物件包含一些元資料,比如終端用戶授權令牌標識和請求的截止時間
func (s *service) AddProduct(ctx context.Context, in *pb.Product) (*pb.ProductID, error) {
out, err := uuid.NewUUID() // ^ 通用唯一標示符
if err != nil {
return nil, status.Errorf(codes.Internal, "生成產品編碼時出錯", err)
}
in.Id = out.String()
if s.productMap == nil {
s.productMap = make(map[string]*pb.Product)
}
s.productMap[in.Id] = in
return &pb.ProductID{Value: in.Id}, status.New(codes.OK, "").Err()
}
func (s *service) GetProduct(ctx context.Context, in *pb.ProductID) (*pb.Product, error) {
value, exists := s.productMap[in.Value]
if exists {
return value, status.New(codes.OK, "").Err()
}
return nil, status.Errorf(codes.NotFound, "商品條目不存在", in.Value)
}
server.go 托管服務的gRPC服務器
前向兼容
前向兼容一般指向上兼容, 向上兼容(Upward Compatible)又稱向前兼容(Forward Compatible),在某一平臺的較低版本環境中撰寫的程式可以在較高版本的環境中運行,
無法傳遞給RegisterXXXService方法
新版protoc-gen-go不支持grpc服務生成,需要通過protoc-gen-go-grpc生成grpc服務介面,但是生成的Server端介面中會出現一個mustEmbedUnimplemented***方法,為了解決前向兼容問題(現在的兼容未來的),如果不解決,就無法傳遞給RegisterXXXService方法,
- 在grpc server實作結構體中匿名嵌入Unimplemented***Server結構體
- 使用protoc生成server代碼時命令列加上關閉選項,protoc --go-grpc_out=require_unimplemented_servers=false
package main
import (
"fmt"
"log"
"net"
pb "productinfo/service/ecommerce"
"google.golang.org/grpc"
)
const (
ip = "127.0.0.1"
port = "23333"
)
func main() {
lis, err := net.Listen("tcp", fmt.Sprintf("%v:%v", ip, port))
if err != nil {
log.Fatalf("無法監聽埠 %v %v", port, err)
}
s := grpc.NewServer()
pb.RegisterProductInfoServer(s, &service{})
log.Println("gRPC服務器開始監聽", port)
if err := s.Serve(lis); err != nil {
log.Fatalf("提供服務失敗: %v", err)
}
}
2022/09/18 17:17:30 gRPC服務器開始監聽 23333
client.go 客戶端代碼
重新編譯proto
創建一個client目錄,并重新之前mod init、編譯proto的操作,
PS C:\Users\小能喵喵喵\Desktop\Go\gRPC\chapter2\productinfo\client\ecommerce>
protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative ./product_info.proto
grpc.WithInsecure已棄用
grpc.WithInsecure is Deprecated: use WithTransportCredentials and insecure.NewCredentials() instead. Will be supported throughout 1.x.
The function insecure.NewCredentials returns an implementation of credentials.TransportCredentials.
grpc.Dial(":9950", grpc.WithTransportCredentials(insecure.NewCredentials()))
代碼
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "productinfo/client/ecommerce"
)
const (
address = "localhost:23333"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials())) // ^ 不安全地創建端到端連接
if err != nil {
log.Fatalf("did not connect: %v", err)
}
c := pb.NewProductInfoClient(conn) // ^ 傳遞連接并創建存根實體,包含所有遠程呼叫方法
name := "小米 10 Pro"
description := "雷軍說:Are you ok?"
price := float32(2000.0)
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // ^ 用于傳遞元資料:用戶標識,授權令牌,請求截止時間
defer cancel()
r, err := c.AddProduct(ctx, &pb.Product{
Name: name, Price: price, Description: description,
})
if err != nil {
log.Fatalf("無法添加商品 %v", err)
}
log.Printf("添加商品成功 %v", r.Value)
/* -------------------------------------------------------------------------- */
product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
if err != nil {
log.Fatalf("獲取不到商品 %v", err)
}
log.Println("Product: ", product.String())
}
構建運行
分別進入service,client檔案夾執行如下命令,構建二進制檔案并運行(可以交叉編譯運行在其他作業系統上),
PS C:\Users\小能喵喵喵\Desktop\Go\gRPC\chapter2\productinfo\service>
go build -o bin/server.exe
PS C:\Users\小能喵喵喵\Desktop\Go\gRPC\chapter2\productinfo\client>
go build -o bin/client.exe


參考資料
-
《Go語言并發之道》Katherine CoxBuday
-
《Go語言核心編程》李文塔
-
《Go語言高級編程》柴樹彬、曹春輝
-
《Grpc 與云原生應用開發》卡山·因德拉西里、丹尼什·庫魯普
-
使用protoc編譯.proto檔案_田土豆的博客-CSDN博客_protoc 編譯
-
Go Generated Code | Protocol Buffers | Google Developers
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/509000.html
標籤:其他
上一篇:Go 語言學習系列(一) : Get started with Go
下一篇:從華為離職了
