gRPC是什么?
gRPC是什么可以用官網的一句話來概括
A high-performance, open-source universal RPC framework
所謂RPC(remote procedure call 遠程過程調用)框架實際是提供了一套機制,使得應用程序之間可以進行通信,而且也遵從server/client模型。使用的時候客戶端調用server端提供的接口就像是調用本地的函數一樣。如下圖所示就是一個典型的RPC結構圖。
RPC通信
gRPC有什么好處以及在什么場景下需要用gRPC
既然是server/client模型,那么我們直接用restful api不是也可以滿足嗎,為什么還需要RPC呢?下面我們就來看看RPC到底有哪些優勢
gRPC vs. Restful API
gRPC和restful API都提供了一套通信機制,用於server/client模型通信,而且它們都使用http作為底層的傳輸協議(嚴格地說, gRPC使用的http2.0,而restful api則不一定)。不過gRPC還是有些特有的優勢,如下:
gRPC可以通過protobuf來定義接口,從而可以有更加嚴格的接口約束條件。
另外,通過protobuf可以將數據序列化為二進制編碼,這會大幅減少需要傳輸的數據量,從而大幅提高性能。
gRPC可以方便地支持流式通信(理論上通過http2.0就可以使用streaming模式, 但是通常web服務的restful api似乎很少這么用,通常的流式數據應用如視頻流,一般都會使用專門的協議如HLS,RTMP等,這些就不是我們通常web服務了,而是有專門的服務器應用。)
Protobuf是什么
Protobuf實際是一套類似Json或者XML的數據傳輸格式和規范,用於不同應用或進程之間進行通信時使用。通信時所傳遞的信息是通過Protobuf定義的message數據結構進行打包,然后編譯成二進制的碼流再進行傳輸或者存儲。
Protobuf的優點
相比較而言,Protobuf有如下優點:
- 足夠簡單
- 序列化后體積很小:消息大小只需要XML的1/10 ~ 1/3
- 解析速度快:解析速度比XML快20 ~ 100倍
- 多語言支持
- 更好的兼容性,Protobuf設計的一個原則就是要能夠很好的支持向下或向上兼容
安裝Protobuf
根據自己的系統版本下載指定的穩定版本文件
我這邊是 MAC 就下載 protoc-3.11.4-osx-x86_64.zip文件
-
添加可執行文件到環境變量
-
為了基礎
protobuf文件,使用protoc工具編譯出go相關文件,需要安裝一個插件
$ go get github.com/golang/protobuf/protoc-gen-go
GRPC示例
安裝grpc
$ go get -u google.golang.org/grpc
GRPC服務端
目錄結構
.
├── main.go // 入口文件
├── pbfile // protobuf 中間文件
│ └── Prob.proto
├── pbservices // 根據中間文件生成的go文件
│ └── Prob.pb.go
└── services // 根據生成的go文件的實現
└── Prob.go
protobuf 中間文件
// proto 文件版本
syntax = "proto3";
// 生成文件的包名
package services;
// 請求結構體
message ProbRequest {
int32 Pid = 1;
}
// 響應結構體
message ProbResponse {
float Price = 1;
}
// 定義rpc接口
service ProbService {
rpc GetProductPrice (ProbRequest) returns (ProbResponse)
}
使用中間文件生成go文件
$ cd pbfile/
$ protoc --go_out=plugins=grpc:../pbservices Prob.proto
實現生成的go文件的接口
// services/Prob.go
package services
import (
"context"
"pbservices"
)
type ProbService struct {
}
func (this *ProbService) GetProductPrice(context.Context, request *pbservices.ProbRequest) (*pbservices.ProbResponse, error) {
return &pbservices.ProbResponse{Price: 10}, nil
}
創建rpc服務文件
package main
import (
"pbservices"
"services"
"google.golang.org/grpc"
"log"
"net"
)
func main() {
// 服務端啟用安全證書
//cred, err := credentials.NewServerTLSFromFile("./keys/server.crt", "./keys/server.key")
//if err != nil {
// log.Fatal(err)
//}
// 創建服務
//rpcserver := grpc.NewServer(grpc.Creds(cred))
rpcserver := grpc.NewServer()
// 注冊
pbservices.RegisterProbServicesServer(rpcserver, &services.ProbService{})
// 啟動監聽
lis, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatal(err)
}
err = rpcserver.Serve(lis)
if err != nil {
log.Fatal(err)
}
}
GRPC客戶端
目錄結構
.
├── main.go // 入口文件
└── pbservices // 根據中間文件生成的go文件
└── Prob.pb.go
客戶端實現
package main
import (
"context"
"fmt"
"pbservices"
"google.golang.org/grpc"
"log"
)
func main() {
// 啟用安全證書
//cred, err := credentials.NewClientTLSFromFile("./keys/server.crt", "mazhichao.com")
//if err != nil {
// log.Fatal(err)
//}
//conn, err := grpc.Dial(":8081", grpc.WithTransportCredentials(cred))
// grpc.WithInsecure() 忽略安全
conn, err := grpc.Dial(":8081", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pbservices.NewProbServicesClient(conn)
req := &pbservices.ProbRequest{Pid: 1}
response, err := client.GetProductPrice(context.Background(), req)
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Price)
}
