使用go-zero框架做rpc服務(wù)端

本文使用grpc協(xié)議

微服務(wù)導讀

使用gRPC一個完整的調(diào)用過程如下
1.客戶端調(diào)用A方法,發(fā)起RPC調(diào)用
2.客戶端對請求信息、參數(shù)使用Protobuf進行對象序列化壓縮
3.服務(wù)端接收到請求后,解碼請求體,進行業(yè)務(wù)邏輯處理并返回。
4.服務(wù)端對響應結(jié)果使用Protobuf進行對象序列化壓縮
5.客戶端接受到服務(wù)端響應,解碼請求體?;卣{(diào)被調(diào)用的A方法,喚醒正在等待響應(阻塞)的客戶端調(diào)用并返回響應結(jié)果

前4步為重要的請求周期,這個請求過程不在通過http方式運行,客戶端與服務(wù)端的通訊參數(shù),通過protocol buff通訊,而.proto文件定義了編碼解碼的規(guī)范,猶如一把鑰匙(交互文檔)一樣。
gRPC的支持請求響應參數(shù)一元傳輸(一次性傳遞完)、流式傳輸(持續(xù)的流式傳輸)

示例 user.proto
syntax = "proto3";

package user;
  
// protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否則無法生成
option go_package = "core/user";

message IdRequest {
    int32 id = 1;
}
  
message UserResponse {
    // 用戶id
    int32 id = 1;
    // 用戶名稱
    string name = 2;
}

message UserOauthResponse {
    int32 id = 1;
    string  open_id = 2;
    string nickname = 3;
    string avatar = 4;
}

service User {
    rpc getUser(IdRequest) returns(UserResponse);
    rpc getUserOauth(IdRequest) returns(UserOauthResponse);
}

定義了兩個rpc服務(wù)getUser,getUserOauth,其中定義了請求、返回參數(shù)

交互過程中,此文件可代替對接文檔,下放調(diào)用者即可

基礎(chǔ)依賴

pb.png

gRPC通訊數(shù)據(jù)傳輸協(xié)議為 Protocol Buffers(簡稱 protobuf)

需安裝3個軟件包protoc,protoc-gen-go,protoc-gen-go-grpc,作用分別是:

  1. protoc 是 Protocol Buffers 的核心編譯器,它將 .proto 文件編譯成多種編程語言的源代碼,包括 C++、Java、Python、Go 等。

protoc 本身不直接生成特定語言的代碼,而是通過調(diào)用各種語言的 "生成器插件" 來完成。例如,生成 Go 代碼時,它會調(diào)用 protoc-gen-go 插件

官方倉庫下載

 https://github.com/protocolbuffers/protobuf/releases 

直接下編譯好的zip包到本地,可執(zhí)行文件放入環(huán)境變量中
我這里用的mac系統(tǒng),下載的編譯好的包 protoc-xxx-osx-universal_binary.zip,注意設(shè)置mac的隱私打開權(quán)限
當然,你也可以使用 brew 安裝

xcode@MacBook-Pro-4 www % protoc --version
libprotoc 3.12.4
  1. protoc-gen-go 插件,是用于生成 Go 語言代碼的插件,它是 protoc 的一個插件。

當使用 protoc 編譯 .proto 文件時,可以指定使用 protoc-gen-go 插件來生成 Go 語言代碼。生成的 Go 代碼包括 Protocol Buffers 的消息類型定義、序列化/反序列化方法等,例如Go的結(jié)構(gòu)體。(不涉及 gRPC 服務(wù)的生成)

通過命令安裝 
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 
應確保GOPATH/bin目錄在環(huán)境變量中

這個程序本質(zhì)是一個插件/go庫,沒有進行命令行單獨運行,需搭配 protoc 命令,因此無法通過 protoc-gen-go --version 確認安裝成功

  1. protoc-gen-go-grpc 是用于生成 Go 語言 gRPC 服務(wù)接口代碼的插件,它也是 protoc 的一個插件。

它可以根據(jù) .proto 文件生成 gRPC 服務(wù)接口的定義和實現(xiàn),包括服務(wù)接口中的方法定義、請求和響應消息類型、以及服務(wù)端和客戶端的 stub 代碼等。

通過命令安裝
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 
應確保GOPATH/bin目錄在環(huán)境變量中

驗證安裝

xcode@MacBook-Pro-4 www % protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.2.0

由此看出,protoc 命令是基礎(chǔ)命令載體,protoc-gen-go 命令是解讀.proto文件生成序列化編碼解碼、操作數(shù)據(jù)的插件,protoc-gen-go-grpc 命令是做服務(wù)通訊方面的插件

基礎(chǔ)使用

命令執(zhí)行

protoc --go_out=./user --go_opt=paths=source_relative --go-grpc_out=./user --go-grpc_opt=paths=source_relative user.proto

參數(shù)含義

--go_out=.   使用 protoc-gen-go 插件將 user.proto 文件編譯為 Go 語言代碼,并將生成的代碼放置在 ./user目錄下
--go_opt=paths=source_relative   設(shè)置生成的 Go 代碼中的導入路徑為相對路徑
--go-grpc_out=.  使用 protoc-gen-go-grpc 插件將 user.proto 文件編譯為帶有 gRPC 服務(wù)的 Go 語言代碼,并將生成的代碼放置在./user目錄下
--go-grpc_opt=paths=source_relative  設(shè)置生成的帶有 gRPC 服務(wù)的 Go 代碼中的導入路徑為相對路徑

上面命令在user目錄下生成了兩個文件user_pb.go,user_grpc.pb.go

  1. user_pb.go 包含了 Protobuf 文件中定義的所有消息類型、服務(wù)以及相關(guān)的方法。服務(wù)端需要使用這個文件來實現(xiàn)服務(wù)器端的業(yè)務(wù)邏輯。
服務(wù)端、客戶端均可通過此文件中封裝好的方法,操作傳輸?shù)臄?shù)據(jù)
此文件不建議修改,已滿足大部分數(shù)據(jù)的增刪改查
  1. user_grpc.pb.go 包含了 gRPC 相關(guān)的代碼,比如客戶端需要使用的 Client 類型以及服務(wù)器端需要實現(xiàn)的 Server 接口??蛻舳诵枰褂眠@個文件來調(diào)用服務(wù)器端提供的服務(wù)
服務(wù)端、客戶端均可通過此文件中封裝好的方法,創(chuàng)建rpc服務(wù)端,rpc請求客戶端
此文件不建議修改

上述兩個文件,為最原始的gRPC調(diào)用方法

一個完整客戶端代碼如下

當我們客戶端拿到服務(wù)端定義的.proto文件后,通過protoc命令生成上面兩個核心代碼文件,調(diào)用其方法

    conn, _ := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())  // "google.golang.org/grpc"
    client := user.NewUserClient(conn)
    sendParams := &user.IdRequest{Id: 5}
    response, _ := client.GetUser(context.Background(), sendParams)

    fmt.Println(response)

這種方式是最簡單的例子,客戶端直鏈服務(wù)端的地址,未引入etcd服務(wù)發(fā)現(xiàn)等概念,下面,我們通過go-zero框架,重新來實現(xiàn)一個完整的調(diào)用

使用go-zero 依賴安裝

goctl是干嘛的不在做過多說明

goctl為我們提供了一鍵安裝方法,免去很多步驟,上訴的3個命令可通過下面一條命令執(zhí)行即可,無論之前是否安裝過,也不怕

goctl env check --install --verbose --force

檢查是否成功

goctl env check --verbose
[goctl-env]: preparing to check env

[goctl-env]: looking up "protoc"
[goctl-env]: "protoc" is installed

[goctl-env]: looking up "protoc-gen-go"
[goctl-env]: "protoc-gen-go" is not found in PATH

[goctl-env]: looking up "protoc-gen-go-grpc"
[goctl-env]: "protoc-gen-go-grpc" is installed

是不是感覺很方便,下面我們來做具體業(yè)務(wù)

使用go-zero框架開發(fā)gRPC

在工作目錄下,新建一個rpc項目 servjj

# 這個命令會新建一個空的gRPC項目,并為我們準備了一個最簡單的demo示例
goctl rpc new servjj

初始化項目

cd servjj && go mod tidy

為了避免混淆,我們把demo給刪掉,目錄結(jié)構(gòu)如下

├── etc          配置文件目錄,存放 .yaml文件
├── go.mod
├── go.sum
├── internal     業(yè)務(wù)文件夾,大部分編碼工作的地方
│   ├── config
│   │   └── config.go    配置文件加載類
│   ├── logic     業(yè)務(wù)邏輯層  
│   └── svc
│       └── servicecontext.go   項目初始化上下文類
└── proto    此目錄為自己建的,專門用來存放.proto文件
    └── user.proto   還是用上面的.proto文件做演示

這是個空項目,main文件和配置文件都沒有,通過下面的命令,可以初始化服務(wù)文件
需要注意的是,.proto 文件中package user;為整個微服務(wù)的名稱主體,goctl會生成一個對應的 user.go (main包)文件和一個 對應的 user.ymal配置文件,如項目服務(wù)過多,可通過import方式管理多個.proto文件

在go-zero框架中生成user.proto的業(yè)務(wù)操作代碼

goctl rpc protoc proto/user.proto --go_out=./core --go-grpc_out=./core --zrpc_out=. --style=goZero

生成代碼文件,將原生的protoc命令創(chuàng)建的操作protobuff的文件放入core文件夾,(--zrpc_out)go-zero進行封裝的代碼放入當前目錄下,代碼風格為小駝峰
user.proto文件中,定義了兩個調(diào)用方法getUser,getUserOauth,傳入用戶id返回用戶信息
現(xiàn)在的目錄結(jié)構(gòu)為

├── core
│   └── user              通過user.proto文件生成的原生方法
│       ├── user.pb.go
│       └── user_grpc.pb.go
├── etc
│   └── user.yaml    user服務(wù)配置文件
├── go.mod
├── go.sum
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic             user.proto中聲明的兩個外界調(diào)用方法,業(yè)務(wù)邏輯
│   │   ├── getUserLogic.go
│   │   └── getUserOauthLogic.go
│   ├── server
│   │   └── userServer.go     服務(wù)的調(diào)用方法handler方法,無需動此文件
│   └── svc
│       └── servicecontext.go
├── proto
│   └── user.proto
├── user.go        main主文件,創(chuàng)建服務(wù)端開啟運行
└── userclient       客戶端的(調(diào)用方法,需傳入客戶端鏈接具柄)封裝,本文演示的是服務(wù)端,無需關(guān)注
    └── user.go
配置文件示例

我們的項目名為servjj.rpc,rpc服務(wù)端監(jiān)聽的端口為8080

Name: servjj.rpc
ListenOn: 0.0.0.0:8080

go-zero框架中封裝了etcd注冊發(fā)現(xiàn)功能,僅僅需添加配置即可

Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  # User: root
  # Pass: "123456"

etcd的key為user.rpc,用來標識此項目產(chǎn)生的key,無論是否使用了etcd注冊,上面的ListenOn地址都是可以直鏈的

配置文件最終為 user.ymal

Name: servjj.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  # User: root
  # Pass: "123456"

如若不使用etcd服務(wù)注冊,配置文件如下

Name: servjj.rpc
ListenOn: 0.0.0.0:8080

配置文件加載類 config/config.go 無需做任何改動

type Config struct {
    zrpc.RpcServerConf
}

服務(wù)配置文檔如下 https://go-zero.dev/docs/tutorials/grpc/server/configuration

服務(wù)端邏輯編寫

internal/logic/getUserLogic.go

func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
    return &user.UserResponse{
        Id:     in.Id,
        Name:   fmt.Sprint("來自rpc服務(wù)器", l.svcCtx.Config.RpcServerConf.ListenOn, "返回的名字"),
        Gender: 1,
    }, nil
}

internal/logic/getUserOauthLogic.go

func (l *GetUserOauthLogic) GetUserOauth(in *user.IdRequest) (*user.UserOauthResponse, error) {
    return &user.UserOauthResponse{
        Id:       in.Id,
        Nickname: fmt.Sprint("來自rpc服務(wù)器", l.svcCtx.Config.RpcServerConf.ListenOn, "返回的名字"), // 注意,這里故意這樣寫,以方便演示,讓客戶端知道是哪個后端節(jié)點返回的請求
        Avatar:   "",
    }, nil
}

我們把這上面的user.proto定義的兩個遠程方法,邏輯已編寫完成,到目前為止,rpc服務(wù)端開發(fā)編碼工作已全部完成

修改user.proto文件,再添加一個方法
message UserNameResponse { // 新增
    // 用戶id
    int32 id = 1;
    // 用戶名稱
    string name = 2;
}

service User {
    rpc getUser(IdRequest) returns(UserResponse);
    rpc getUserOauth(IdRequest) returns(UserOauthResponse);
    rpc getUserName(IdRequest) returns(UserNameResponse);  // 新增
}

再次執(zhí)行命令,不會覆蓋之前的代碼

goctl rpc protoc proto/user.proto --go_out=./core --go-grpc_out=./core --zrpc_out=. --style=goZero

目錄結(jié)構(gòu)小微變化

├── core
│   └── user
│       ├── user.pb.go
│       └── user_grpc.pb.go // 代碼更新,新增getUserName方法實現(xiàn)
├── etc
│   └── user.yaml
├── go.mod
├── go.sum
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   ├── getUserLogic.go
│   │   ├── getUserNameLogic.go // 新增getUserName方法邏輯文件
│   │   └── getUserOauthLogic.go
│   ├── server
│   │   └── userServer.go  // 新增getUserName方法handler
│   └── svc
│       └── servicecontext.go
├── proto
│   └── user.proto
├── user.go
└── userclient
    └── user.go   // 新增getUserName方法

部署

開啟etcd服務(wù)

不在過多說明etcd的安裝開啟,參考如下

# 創(chuàng)建容器并啟動
docker run -d --name etcd-server --network app-tier --publish 2379:2379 --publish 2380:2380 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 bitnami/etcd:latest

推薦一個etcd的GUI客戶端 http://etcdmanager.io/

image.png

代碼部署

我們以同一個項目不同端口運行,來模擬多個節(jié)點運行
節(jié)點1:
修改user.yaml文件

ListenOn: 0.0.0.0:8081

新開1個終端,執(zhí)行 go run user.go
此刻,etcd存儲的值為

key value
user.rpc/7587872091812852488 10.0.89.75:8081

10.0.89.75 是我本機的局域網(wǎng)ip,等價于 127.0.0.1

節(jié)點2:
修改user.yaml文件

ListenOn: 0.0.0.0:8082

重新開1個終端,執(zhí)行 go run user.go
此刻,etcd存儲的值為

key value
user.rpc/7587872091812852488 10.0.89.75:8081
user.rpc/7587872091812852496 10.0.89.75:8082
image.png

到此,服務(wù)端工作已全部完畢~

測試

本文使用postman充當客戶端測試,不再使用程序,由于上面我們是通過etcd部署的,但postman不支持鏈接etcd服務(wù)發(fā)現(xiàn)功能,故使用postman直鏈測試

postman 10.16.0

  1. 創(chuàng)建一個grpc的文件夾


    image.png
  2. 在此文件夾下新建一個gRPC服務(wù)


    image.png
  3. 填入RPC服務(wù)端地址,倒入user.proto文件

    image.png

    image.png

  4. 選中指定的User服務(wù)

    image.png

    選中User選項,點擊Import as API按鈕
    image.png

  5. 選擇一個具體rpc方法發(fā)送請求


    image.png

本文使用了etcd進行后端部署,下文中,將介紹客戶端通過etcd進行服務(wù)發(fā)現(xiàn)調(diào)用rpc方法

源碼 https://gitee.com/qq_connect-60293/servjj

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

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

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