gRPC(1):入門(mén)及簡(jiǎn)單使用(go)

1、RPC

1.1 什么是RPC

RPC(Remote Procedure Call),即遠(yuǎn)程過(guò)程調(diào)用,過(guò)程就是方法,簡(jiǎn)單來(lái)說(shuō),它就是一種能夠像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程計(jì)算機(jī)進(jìn)程中的方法的技術(shù),在這種調(diào)用中,我們不需要了解任何網(wǎng)絡(luò)通信的細(xì)節(jié)(當(dāng)然,就使用來(lái)說(shuō))

最終解決的問(wèn)題:讓分布式或者微服務(wù)系統(tǒng)中不同服務(wù)之間的調(diào)用像本地調(diào)用一樣簡(jiǎn)單

1.2 RPC和HTTP

調(diào)用遠(yuǎn)程服務(wù),HTTP 就可以完成的任務(wù),為什么還需要 RPC 呢?需要注意,這兩個(gè)并不是同一層次的概念,HTTP 是一種傳輸協(xié)議,RPC 應(yīng)該是比 HTTP 更高層級(jí)的概念。完整的 RPC 實(shí)現(xiàn)包含有 傳輸協(xié)議序列化協(xié)議,其中,傳輸協(xié)議既可以使用 HTTP,也可以使用 TCP 等,不同的選擇可以適應(yīng)不同的場(chǎng)景

RPC 并不是一個(gè)嶄新的概念,它實(shí)際上就是遠(yuǎn)程通信的一個(gè)更高層級(jí)的封裝,不同傳輸協(xié)議和序列化協(xié)議的組合構(gòu)成了不同的具體 RPC 實(shí)現(xiàn),比如我們熟知的 RESTful,就是 HTTP + JSON + 一些其他細(xì)節(jié)構(gòu)成

1.3 RPC技術(shù)的演化

早期有一些很流行的 RPC 實(shí)現(xiàn),比如 CORBA(通用對(duì)象請(qǐng)求代理體系結(jié)構(gòu)),Java RMI(遠(yuǎn)程方法調(diào)用),它們都用來(lái)構(gòu)建和鏈接服務(wù)或應(yīng)用程序,但是,大多數(shù)傳統(tǒng) RPC 實(shí)現(xiàn)極其復(fù)雜,因?yàn)樗鼈儤?gòu)建在 TCP 之上,并且還有大量的規(guī)范限制

鑒于以上 RPC 實(shí)現(xiàn)的局限性,SOAP(簡(jiǎn)單對(duì)象訪(fǎng)問(wèn)協(xié)議)應(yīng)運(yùn)而生,SOAP 是 SOA(面向服務(wù)的架構(gòu))中的標(biāo)準(zhǔn)通信技術(shù),能夠基于任意底層通信協(xié)議進(jìn)行通信,最常用的是 HTTP,序列化協(xié)議使用的是 XML

REST(描述性狀態(tài)轉(zhuǎn)移)是 ROA(面向資源的架構(gòu))的基礎(chǔ),在這種架構(gòu)中,將應(yīng)用程序建模為各種資源的集合,客戶(hù)端可以變更這些資源的狀態(tài)(增刪改查)。REST 的通用實(shí)現(xiàn)是 HTTP + JSON,通過(guò) HTTP 將應(yīng)用程序建模為能夠通過(guò)唯一標(biāo)識(shí)符表示的資源集合,狀態(tài)變更操作會(huì)采用 HTTP 動(dòng)作(GET,POST,PUT,DELETE等)。實(shí)際上,REST 架構(gòu)風(fēng)格已經(jīng)成為了各種服務(wù)間通信中非常流行的方法,但是,隨著微服務(wù)大行其道以及網(wǎng)絡(luò)交互的激增,REST 已經(jīng)無(wú)法滿(mǎn)足現(xiàn)代化的需求了,其主要原因是以下三個(gè)主要的局限性:

  • 基于文本的消息協(xié)議效率太低。REST 服務(wù)建立在基于文本的傳輸協(xié)議 HTTP1.x 上,使用人類(lèi)可讀的文本格式如 JSON,但是,很多時(shí)候我們并不需要這種可讀性,如果能夠直接發(fā)送映射服務(wù)和客戶(hù)端業(yè)務(wù)邏輯的二進(jìn)制內(nèi)容,將大大提高效率

  • 缺乏強(qiáng)類(lèi)型接口。開(kāi)發(fā) REST 服務(wù)時(shí),應(yīng)用程序之間并不需要共享服務(wù)定義和類(lèi)型定義,我們要么通過(guò)網(wǎng)絡(luò)查看文本格式,要么通過(guò) API 文檔,構(gòu)建這種分散的應(yīng)用程序時(shí),會(huì)遇到很多不兼容、運(yùn)行時(shí)錯(cuò)誤和互操作等問(wèn)題

  • REST架構(gòu)風(fēng)格難以實(shí)施。REST 架構(gòu)風(fēng)格有很多 “好的實(shí)踐”,遵循這些實(shí)踐能構(gòu)建出真正好用的 REST 服務(wù),但是,它們并沒(méi)有作為協(xié)議的一部分進(jìn)行強(qiáng)制要求,事實(shí)上,大多數(shù) REST 服務(wù)不過(guò)是通過(guò)網(wǎng)絡(luò)公開(kāi)的 HTTP 服務(wù),并沒(méi)有很好地遵循基礎(chǔ)的架構(gòu)風(fēng)格

由于 REST 的局限性,出現(xiàn)了許多新興的 RPC 技術(shù),較為流行的有 gRPC、Thrift、GraphQL等

2、gRPC

2.1 gRPC簡(jiǎn)介

gRPC 是一個(gè)現(xiàn)代化的開(kāi)源 RPC 框架,一開(kāi)始由 google 開(kāi)發(fā),是一款語(yǔ)言中立、平臺(tái)中立、的 RPC 系統(tǒng),與許多 RPC 系統(tǒng)類(lèi)似,gRPC 也是基于以下理念:定義一個(gè) 服務(wù),指定能夠被遠(yuǎn)程調(diào)用的 方法(包含參數(shù)和返回類(lèi)型)。在服務(wù)端實(shí)現(xiàn)這個(gè)接口,并運(yùn)行一個(gè)gRPC 服務(wù)器來(lái)處理客戶(hù)端調(diào)用,在客戶(hù)端擁有一個(gè) stub 連接服務(wù)端上的方法

image.png

2.2 gRPC的優(yōu)勢(shì)

gRPC 的優(yōu)勢(shì)是它被越來(lái)越多人采用的關(guān)鍵所在,主要有以下幾個(gè)方面:

  • 提供高效的進(jìn)程間通信。使用一個(gè)基于 protocol buffers 的二進(jìn)制協(xié)議而不是文本格式與客戶(hù)端通信,同時(shí)在 HTTP2 上實(shí)現(xiàn),擁有更好的性能

  • 具有簡(jiǎn)單且定義良好的服務(wù)接口。契約優(yōu)先,必須首先定義服務(wù)接口,然后才能去處理細(xì)節(jié),簡(jiǎn)單一致,可擴(kuò)展

  • 強(qiáng)類(lèi)型。服務(wù)契約清晰地定義了應(yīng)用程序間通信所使用的類(lèi)型,分布式應(yīng)用程序的開(kāi)發(fā)更加穩(wěn)定

  • 支持多語(yǔ)言?;?protocol buffers 的服務(wù)定義是語(yǔ)言中立的,可以選擇任意一種語(yǔ)言具體實(shí)現(xiàn)

  • 支持雙工流。與傳統(tǒng)的 REST 相比,gRPC 能夠同時(shí)構(gòu)建傳統(tǒng)的請(qǐng)求-響應(yīng)風(fēng)格的消息以及客戶(hù)端流和服務(wù)端流

  • 具備內(nèi)置的商業(yè)化特性。如認(rèn)證、加密、彈性時(shí)間、元數(shù)據(jù)交換、壓縮、負(fù)載均衡以及服務(wù)發(fā)現(xiàn)等

  • 與云原生生態(tài)進(jìn)行了集成。gRPC 是 CNCF(云原生計(jì)算基金會(huì))的一部分,大多數(shù)現(xiàn)代框架和技術(shù)都對(duì) gRPC 提供了原生支持

  • 業(yè)界成熟。通過(guò)在谷歌進(jìn)行的大量實(shí)戰(zhàn)測(cè)試,gRPC 已經(jīng)發(fā)展成熟,被許多公司采用

2.3 gRPC的缺點(diǎn)

gRPC 也存在一定劣勢(shì),選擇它用來(lái)構(gòu)建應(yīng)用程序時(shí),需要注意以下三點(diǎn):

  • gRPC 不太適合面向外部的服務(wù)。gRPC 具有契約驅(qū)動(dòng)、強(qiáng)類(lèi)型等特點(diǎn),這會(huì)限制向外部暴露服務(wù)的靈活性,對(duì)客戶(hù)端有諸多限制,所以更適合用在內(nèi)部服務(wù)器之間通信

  • 避免巨大的服務(wù)定義變更。如果出現(xiàn)巨大的服務(wù)定義變更,通常需要重新生成客戶(hù)端代碼和服務(wù)端代碼,會(huì)讓整個(gè)開(kāi)發(fā)生命周期變得復(fù)雜,需要小心引入破壞性的變更

  • 生態(tài)系統(tǒng)相對(duì)較小。與傳統(tǒng) REST 等協(xié)議相比,gRPC 仍然處于起步階段,瀏覽器和移動(dòng)應(yīng)用程序?qū)?gRPC 的支持才剛剛起步

3、一個(gè)簡(jiǎn)單gRPC服務(wù)的golang實(shí)現(xiàn)

3.1 環(huán)境準(zhǔn)備

  • 下載 protoc 編譯器:protobuf,選擇合適的平臺(tái),解壓后將可執(zhí)行文件加入環(huán)境變量,此編譯器用來(lái)編譯服務(wù)定義文件 .proto 生成指定語(yǔ)言的目標(biāo)代碼,這些代碼用來(lái)實(shí)現(xiàn) gRPC 服務(wù)以及客戶(hù)端 stub
image-20210626104740589
  • 安裝 grpc-go 插件用來(lái)生成 go 目標(biāo)代碼
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
  • 創(chuàng)建代碼目錄 product-info,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的查看商品信息和添加商品的 rpc 服務(wù),在其中新建三個(gè)文件夾 proto、server、client 分別存放服務(wù)定義文件和生成的目標(biāo)代碼、服務(wù)端程序?qū)崿F(xiàn)、客戶(hù)端程序?qū)崿F(xiàn),然后執(zhí)行 go mod init product-info 初始化模塊。當(dāng)然這里只是示例程序,實(shí)際場(chǎng)景中服務(wù)代碼和客戶(hù)端代碼一般都不在同一個(gè)機(jī)器上,更不可能在同一個(gè)模塊下了,最終目錄結(jié)構(gòu)如下:
image-20210626214221300

3.2 服務(wù)定義

開(kāi)發(fā) gRPC 應(yīng)用程序時(shí),要首先定義服務(wù)接口,然后生成服務(wù)端骨架和客戶(hù)端 stub,客戶(hù)端通過(guò)調(diào)用其中定義的方法來(lái)訪(fǎng)問(wèn)遠(yuǎn)程服務(wù)器上的方法,服務(wù)定義都以 protocol buffers 的形式記錄,也就是 gRPC 所使用的服務(wù)定義語(yǔ)言

  • 在 proto 目錄下新建服務(wù)定義文件 product-info.proto
// 版本
syntax = "proto3";
// proto文件所屬包名
package proto;
// 聲明生成的go文件所屬的包,路徑末尾為包名,相對(duì)路徑是相對(duì)于編譯生成目標(biāo)代碼時(shí)的工作路徑
option go_package = "./proto";

// 包含兩個(gè)遠(yuǎn)程方法的 rpc 服務(wù),遠(yuǎn)程方法只能有一個(gè)參數(shù)和一個(gè)返回值
service ProductInfo {
    rpc addProduct(Product) returns (ProductID);
    rpc getProduct(ProductID) returns (Product);
}

// 自定義消息類(lèi)型,用這種方法傳遞多個(gè)參數(shù),必須使用唯一數(shù)字標(biāo)識(shí)每個(gè)字段
message Product {
    string id = 1;
    string name = 2;
    string description = 3;
    float price = 4;
}

message ProductID {
    string value = 1;
}
  • 編譯服務(wù)定義文件生成目標(biāo)源代碼,這一步之后在 proto 文件下生成了以下兩個(gè)文件:

    • product-info.pb.go,包含用于填充、序列化、檢索請(qǐng)求和響應(yīng)消息類(lèi)型的所有 protocol buffers 代碼

    • product-info_grpc.pb.go,包含服務(wù)端需要繼承實(shí)現(xiàn)和客戶(hù)端進(jìn)行調(diào)用的接口定義

# go_out 和 go-grpc-out 目錄是相對(duì)于服務(wù)定義文件中 go_package 指定的目錄
protoc proto/product-info.proto --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative

3.3 服務(wù)端實(shí)現(xiàn)

編譯生成服務(wù)端骨架的時(shí)候,已經(jīng)得到了建立 gRPC 連接、相關(guān)消息類(lèi)型和接口的基礎(chǔ)代碼,接下來(lái)就是實(shí)現(xiàn)得到的接口,在 server 文件夾中新建服務(wù)端主程序 main.go:

package main

import (
    "context"
    "log"
    "net"

    pb "product-info/proto"

    "github.com/gofrs/uuid"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

const (
    port = ":50051"
)

// 對(duì)服務(wù)器的抽象,用來(lái)實(shí)現(xiàn)服務(wù)方法
type server struct {
    pb.UnimplementedProductInfoServer
}

// 存放商品,模擬業(yè)務(wù)邏輯
var productMap map[string]*pb.Product

// 實(shí)現(xiàn) AddProduct 方法
func (s *server) AddProduct(ctx context.Context, in *pb.Product) (*pb.ProductID, error) {
    out, err := uuid.NewV4()
    if err != nil {
        return nil, status.Errorf(codes.Internal, "Error while generating Product ID", err)
    }
    in.Id = out.String()
    if productMap == nil {
        productMap = make(map[string]*pb.Product)
    }
    productMap[in.Id] = in
    log.Printf("Product %v : %v - Added.", in.Id, in.Name)
    return &pb.ProductID{Value: in.Id}, nil
}

// 實(shí)現(xiàn) GetProduct 方法
func (s *server) GetProduct(ctx context.Context, in *pb.ProductID) (*pb.Product, error) {
    product, exists := productMap[in.Value]
    if exists && product != nil {
        log.Printf("Product %v : %v - Retrieved.", product.Id, product.Name)
        return product, nil
    }
    return nil, status.Errorf(codes.NotFound, "Product does not exist.", in.Value)
}

func main() {
    // 創(chuàng)建一個(gè) tcp 監(jiān)聽(tīng)器
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 創(chuàng)建一個(gè) gRPC 服務(wù)器實(shí)例
    s := grpc.NewServer()
    // 將服務(wù)注冊(cè)到 gRPC 服務(wù)器上
    pb.RegisterProductInfoServer(s, &server{})
    // 綁定 gRPC 服務(wù)器到指定 tcp
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

3.4 客戶(hù)端實(shí)現(xiàn)

接下來(lái)創(chuàng)建客戶(hù)端程序來(lái)與服務(wù)器對(duì)話(huà),之前編譯服務(wù)定義文件生成的目標(biāo)源代碼已經(jīng)包含了訪(fǎng)問(wèn)細(xì)節(jié)的實(shí)現(xiàn),我們只需要?jiǎng)?chuàng)建客戶(hù)端實(shí)例就可以直接調(diào)用遠(yuǎn)程方法。在 client 文件夾中創(chuàng)建客戶(hù)端主程序 main.go:

package main

import (
    "context"
    "log"
    "time"

    pb "product-info/proto"

    "google.golang.org/grpc"
)

const (
    // 服務(wù)端地址
    address = "localhost:50051"
)

func main() {
    // 創(chuàng)建 gRPC 連接
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()

    // 創(chuàng)建客戶(hù)端 stub,利用它調(diào)用遠(yuǎn)程方法
    c := pb.NewProductInfoClient(conn)

    name := "XiaoMi 11"
    description := "XiaoMi 11 with MIUI 12.5"
    price := float32(3999.00)

    // 調(diào)用遠(yuǎn)程方法
    r, err := c.AddProduct(context.Background(), &pb.Product{Name: name, Description: description, Price: price})
    if err != nil {
        log.Fatalf("Could not add product: %v", err)
    }
    log.Printf("Product ID: %s added successfully", r.Value)

    product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value})
    if err != nil {
        log.Fatalf("Could not get product: %v", err)
    }
    log.Printf("Product: %v", product.String())
}

3.5 構(gòu)建和運(yùn)行

最終工作空間如下:

image-20210626224031250

分別構(gòu)建運(yùn)行服務(wù)端和客戶(hù)端程序,go build 或者直接 go run

  • 啟動(dòng)服務(wù)端:go run ./server/main.go

  • 啟動(dòng)客戶(hù)端:go run ./client/main.go

image-20210626224655394
  • 服務(wù)端 log:
image-20210626224627896

到這里就成功構(gòu)建了一個(gè)簡(jiǎn)單的 gRPC 服務(wù),并在客戶(hù)端調(diào)用成功。當(dāng)然這只是一個(gè)簡(jiǎn)單的入門(mén)程序,更多的細(xì)節(jié)還需要更加深入的學(xué)習(xí),另外,gRPC 是支持多語(yǔ)言的,這里采用 golang 實(shí)現(xiàn)了服務(wù)端和客戶(hù)端程序,其他的語(yǔ)言構(gòu)建 gRPC 服務(wù)也都遵循類(lèi)似的步驟,且客戶(hù)端和服務(wù)端代碼無(wú)關(guān),也可用不同的語(yǔ)言實(shí)現(xiàn),其他語(yǔ)言的用法可見(jiàn) 官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容