本文使用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ǔ)依賴

gRPC通訊數(shù)據(jù)傳輸協(xié)議為 Protocol Buffers(簡稱 protobuf)
需安裝3個軟件包protoc,protoc-gen-go,protoc-gen-go-grpc,作用分別是:
- 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
- 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 確認安裝成功
- 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
- user_pb.go 包含了 Protobuf 文件中定義的所有消息類型、服務(wù)以及相關(guān)的方法。服務(wù)端需要使用這個文件來實現(xiàn)服務(wù)器端的業(yè)務(wù)邏輯。
服務(wù)端、客戶端均可通過此文件中封裝好的方法,操作傳輸?shù)臄?shù)據(jù)
此文件不建議修改,已滿足大部分數(shù)據(jù)的增刪改查
- 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/

代碼部署
我們以同一個項目不同端口運行,來模擬多個節(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 |

到此,服務(wù)端工作已全部完畢~
測試
本文使用postman充當客戶端測試,不再使用程序,由于上面我們是通過etcd部署的,但postman不支持鏈接etcd服務(wù)發(fā)現(xiàn)功能,故使用postman直鏈測試
postman 10.16.0
-
創(chuàng)建一個grpc的文件夾
image.png -
在此文件夾下新建一個gRPC服務(wù)
image.png -
填入RPC服務(wù)端地址,倒入
user.proto文件
image.png
image.png -
選中指定的User服務(wù)
image.png
選中User選項,點擊Import as API按鈕
image.png -
選擇一個具體rpc方法發(fā)送請求
image.png






