grpc是由google开发的一款语言中立、平台中立、开源的RPC系统
在grpc中客户端应用可以像调用本地对象一样直接调用另一台不同机器上服务端应用的方法,使得很容易创建分布式应用和服务。与许多RPC系统类似,grpc也是定义一个服务,指定能够被远程调用的方法,在服务端实现该接口,并允许grpc服务器来处理客户端调用。客户端拥有像服务端一样方法的stub。
grpc允许定义四种服务方法
- 单项RPC,即客户端发送一个请求给服务端,然后从服务端获取一个应答,就像一次普通的函数调用
- 服务端流式RPC,客户端发送一个请求给服务端,可获取一个数据流给客户端读取消息
- 客户端流式RPC,即用客户端提供的一个数据流写入并发送一系列消息给服务端,一旦客户端完成消息写入,就等待服务端读取这些消息并应答
- 双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
以下以一个简单的获取产品的场景来演示grpc的使用
使用protoc buffer定义服务
protoBuffer主要编写相应的数据结构以及接口方法定义
syntax="proto3";
package pd;
option go_package="/pd";
// 因为rpc方法参数不能为空,所以定义一个空message
message empty {
}
// 产品
message Prod {
string id = 1;
string name = 2;
}
// 因为所有参数都只能为message类型,所以对于prodId还是要定义为message
message ProdId {
int64 val = 1;
}
产品服务定义如下
syntax="proto3";
package pd;
option go_package="/pd";
import "pd/common.proto";
service ProdInfo{
rpc addProd(Prod) returns (ProdId);
rpc getProd(ProdId) returns (Prod);
// stream就是前文介绍的流
rpc listProds(empty) returns (stream Prod);
rpc listProdsByIds(stream ProdId) returns (stream Prod);
}
当我们运行protoc命令之后,就会为我们自动生成pb.go文件
# --go-grpc_out 为grpc插件,可以通过
protoc -I ../ --go-grpc_out=../ ../pd/prod.proto
protoc -I ../ --go_out=../ ../pd/common.proto
实现服务
以下是实现过程
import (
"context"
"io"
"learn/grpc-demo/pd"
)
type prodServer struct {
pd.UnsafeProdInfoServer
prodMap map[int64]*pd.Prod
index int64
}
func (s *prodServer) GetProd(ctx context.Context, id *pd.ProdId) (*pd.Prod, error) {
return s.prodMap[id.Val], nil
}
func (s *prodServer) ListProds(empty *pd.Empty, server pd.ProdInfo_ListProdsServer) error {
for _, prod := range s.prodMap {
if err := server.Send(prod); err != nil {
return err
}
}
return nil
}
func (s *prodServer) ListProdsByIds(server pd.ProdInfo_ListProdsByIdsServer) error {
for {
in, err := server.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
pid := in.Val
if prod, ok := s.prodMap[pid]; ok {
if err = server.Send(prod); err != nil {
return err
}
}
}
}
func (s *prodServer) AddProd(ctx context.Context, p *pd.Prod) (resp *pd.ProdId, err error) {
s.prodMap[s.index] = p
s.index++
return &pd.ProdId{Val: s.index - 1}, nil
}
值得注意的是出现了一个很奇怪的pd.UnsafeProdInfoServer,在新版protoc-gen-grpc-go
编译器中,Server实现必须向前兼容
当然也可以通过以下命令不生成向前兼容service
protoc --go-grpc_out=require_unimplemented_servers=false:.
编写服务端和客户端
服务端监听
conn,err:=net.Listen("tcp",":8888")
if err != nil {
t.Fatalf("fail to listen for %s",err)
}
s :=grpc.NewServer()
pd.RegisterProdInfoServer(s,&prodServer{
prodMap: make(map[int64]*pd.Prod),
index: 0,
})
err=s.Serve(conn)
if err != nil {
t.Fatal("fail to serve")
}
客户端请求,并可以通过grpc.WithInsecure()取消认证
conn,err:=grpc.Dial(":8888",grpc.WithInsecure())
if err != nil {
t.Fatal("fail to conn server")
}
defer conn.Close()
c:=pd.NewProdInfoClient(conn)
pid,err:=c.AddProd(context.Background(),&pd.Prod{
Id: "x",
Name: "we",
})
if err != nil {
t.Fatal("add prod failed")
}
t.Logf("add prod %d",pid.Val)
Ref
- https://doc.oschina.net/grpc?t=58008
- https://blog.gopheracademy.com/advent-2017/go-grpc-beyond-basics/
- https://grpc.io/docs/guides/auth/
- https://shijuvar.medium.com/writing-grpc-interceptors-in-go-bf3e7671fe48
- https://stackoverflow.com/questions/65079032/grpc-with-mustembedunimplemented-method