gRPC(Remote Procedure Calls)
概述
GRPC是一個(gè)高性能、通用的開(kāi)源RPC框架,基于底層HTTP/2協(xié)議標(biāo)準(zhǔn)
和協(xié)議層Protobuf序列化協(xié)議
開(kāi)發(fā),支持眾多的開(kāi)發(fā)語(yǔ)言。
gRPC 也是基于以下理念:定義一個(gè)服務(wù),指定其能夠被遠(yuǎn)程調(diào)用的方法(包含參數(shù)和返回類型)。在服務(wù)端實(shí)現(xiàn)這個(gè)接口,并運(yùn)行一個(gè) gRPC服務(wù)器來(lái)處理客戶端調(diào)用。在客戶端擁有一個(gè)存根能夠像服務(wù)端一樣的方法。
gRPC使用protocol buffers
作為接口描述語(yǔ)言(IDL)以及底層的信息交換格式
優(yōu)點(diǎn)
- 基于 HTTP/2 之上的二進(jìn)制協(xié)議(Protobuf 序列化機(jī)制);
- 一個(gè)連接上可以多路復(fù)用,并發(fā)處理多個(gè)請(qǐng)求和響應(yīng);
- 多種語(yǔ)言的類庫(kù)實(shí)現(xiàn);
- 服務(wù)定義文件和自動(dòng)代碼生成(.proto 文件和 Protobuf 編譯工具)。
- RPC 還提供了很多擴(kuò)展點(diǎn),用于對(duì)框架進(jìn)行功能定制和擴(kuò)展,例如,通過(guò)開(kāi)放負(fù)載均衡接口可以無(wú)縫的與第三方組件進(jìn)行集成對(duì)接(Zookeeper、域名解析服務(wù)、SLB 服務(wù)等)
原理
https://www.colabug.com/4616436.html
通信方式
Simple RPC
簡(jiǎn)單rpc ,這就是一般的rpc調(diào)用,一個(gè)請(qǐng)求對(duì)象對(duì)應(yīng)一個(gè)返回對(duì)象
proto語(yǔ)法:
rpc simpleHello(Person) returns (Result) {}
Server-side streaming RPC
服務(wù)端流式rpc,一個(gè)請(qǐng)求對(duì)象,服務(wù)端可以傳回多個(gè)結(jié)果對(duì)象
proto語(yǔ)法:
rpc serverStreamHello(Person) returns (stream Result) {}
Client-side streaming RPC
客戶端流式rpc 客戶端傳入多個(gè)請(qǐng)求對(duì)象,服務(wù)端返回一個(gè)響應(yīng)結(jié)果
proto語(yǔ)法:
rpc clientStreamHello(stream Person) returns (Result) {}
Bidirectional streaming RPC
雙向流式rpc,結(jié)合客戶端流式rpc和服務(wù)端流式rpc,可以傳入多個(gè)對(duì)象,返回多個(gè)響應(yīng)對(duì)象
proto語(yǔ)法:
rpc biStreamHello(stream Person) returns (stream Result) {}
安裝
golang
版本的grpc
要求go
版本要在1.6
以上
install gRPC
使用go get
命令安裝grpc
包
$ go get -u google.golang.org/grpc
由于某些不可逆原因,上面命令會(huì)報(bào)連接超時(shí),可以到
github
上將項(xiàng)目clone
到$GOPATH/src/google.golang.org/
下
$ cd $GOPATH/src/google.golang.org
$ git clone git@github.com:grpc/grpc-go.git grpc
install Protocol Buffers v3
$ apt install protobuf-compiler
install protoc plugin
安裝golang
版本對(duì)應(yīng)的protobuf
生成工具
$ go get -u github.com/golang/protobuf/protoc-gen-go
運(yùn)行demo
進(jìn)入example
目錄
$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld
刪除原來(lái)的helloworld.pb.go
文件,并使用protoc
生成自己生成一個(gè)
$ rm helloworld/helloworld.pb.go // 刪除原來(lái)的helloworld.pb.go文件
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld // 根據(jù) .proto 文件生成對(duì)應(yīng)的.go文件
編寫grpc
接口時(shí),在.proto
文件定義接口通信數(shù)據(jù)格式和接口信息,然后通過(guò)protoc
自動(dòng)生成對(duì)應(yīng)的go
代碼,大大方便了開(kāi)發(fā)
-
-I PATH
:specify the directory in which to search for imports. May be specified multiple times; directories will be searched in order. If not given, the current working directory is used. -
--go_out
:指定輸出go
代碼 -
plugins=grpc
:.proto
中的service
是grpc
擴(kuò)展的功能,需要使用grpc
插件進(jìn)行解析才能生成對(duì)應(yīng)的接口定義代碼。
運(yùn)行 grpc server
和 grpc client
$ go run greeter_server/main.go // 啟動(dòng)grpc server
$ go run greeter_client/main.go // 啟動(dòng)grpc client
如果helloworld下面沒(méi)有g(shù)reeter_server和greeter_client文件夾執(zhí)行下列兩種其中一種命令
$ go get -u google.golang.org/grpc/examples/helloworld/greeter_client
$ go get -u google.golang.org/grpc/examples/helloworld/greeter_server
$ go get -u google.golang.org/grpc
實(shí)踐
開(kāi)發(fā)一個(gè)求和的grpc
接口
定義.proto文件
在proto
目錄下創(chuàng)建接口描述文件sum.proto
文件:
Message命名采用駝峰命名方式,字段命名采用小寫字母加下劃線分隔方式
Enums類型名采用駝峰命名方式,字段命名采用大寫字母加下劃線分隔方式
即便業(yè)務(wù)上不需要參數(shù)也必須指定一個(gè)請(qǐng)求消息,一般會(huì)定義一個(gè)空message。
syntax = "proto3"; // 使用 proto3,沒(méi)寫默認(rèn)proto2。建議grpc配合proto3使用,兼容性更高
// java生成選項(xiàng)
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package proto; // 生成的go所屬的package
// 需要序列化的結(jié)構(gòu)體
message SumResp {
int64 sum = 1;
}
message SumReq {
int64 a = 1;
int64 b = 2;
}
service CalcSvc {
// 每個(gè)rpc接口聲明都必須有且一個(gè)參數(shù)和一個(gè)返回值
rpc Sum(SumReq) returns (SumResp) {}
}
根據(jù)接口描述文件生成源碼
進(jìn)入proto
目錄,執(zhí)行
$ protoc sum.proto --go_out=plugins=grpc:.
可以看到,在本目錄下生成sum.pb.go
文件,且package
為proto
開(kāi)發(fā)服務(wù)端接口
首先查看生成的sum.pb.go
文件,可以看到根據(jù)sum.proto
文件中的CalcSvc
接口定義生成了對(duì)應(yīng)的接口:
// CalcSvcServer is the server API for CalcSvc service.
type CalcSvcServer interface {
// 每個(gè)rpc接口聲明都必須有且一個(gè)參數(shù)和一個(gè)返回值
Sum(context.Context, *SumReq) (*SumResp, error)
}
開(kāi)發(fā)服務(wù)端接口只要就是根據(jù)這些接口定義實(shí)現(xiàn)具體的業(yè)務(wù)邏輯
在項(xiàng)目下創(chuàng)建service/main.go
:
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"grpc-demo/proto" //路徑可能不同,看自己的proto包放在哪個(gè)目錄下
"log"
"net"
)
// 類型斷言
var _ proto.CalcSvcServer = new(CalcSvc)
type CalcSvc struct{}
func (CalcSvc) Sum(ctx context.Context, req *proto.SumReq) (resp *proto.SumResp, err error) {
// 建議使用GetA,不要直接使用req.A,可能存在req=nil的情況
a := req.GetA()
b := req.GetB()
log.Println("request coming ...")
return &proto.SumResp{
Sum: a + b,
}, err
}
func main() {
lis, err := net.Listen("tcp", ":8888")
if err != nil {
log.Fatal(err)
}
// 注冊(cè)服務(wù)到gRPC
s := grpc.NewServer()
proto.RegisterCalcSvcServer(s, &CalcSvc{})
// 啟用Server Reflection,可以使用gRPC CLI去檢查services
// https://github.com/grpc/grpc-go/blob/master/Documentation/server-reflection-tutorial.md
reflection.Register(s)
// 啟動(dòng)服務(wù)
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}
客戶端訪問(wèn)
在項(xiàng)目下創(chuàng)建client/main.go
:
package main
import (
"context"
"google.golang.org/grpc"
"grpc-demo/proto" //路徑可能不同,看自己的proto包放在哪個(gè)目錄下
"log"
)
func main() {
// 創(chuàng)建gRPC連接
// WithInsecure option 指定不啟用認(rèn)證功能
conn, err := grpc.Dial(":8888", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
// 創(chuàng)建gRPC client
client := proto.NewCalcSvcClient(conn)
// 請(qǐng)求gRPC server
resp, err := client.Sum(context.Background(), &proto.SumReq{
A: 5,
B: 10,
})
if err != nil {
log.Fatal(err)
}
log.Printf("5 + 10 = %d", resp.GetSum())
}
運(yùn)行
$ go run service/main.go &
$ go run client/main.go