gRPC是高性能,开源,通用远程调用框架。它可以有效地连接数据中心内部和数据中心之间的服务,并为负载平衡、跟踪、运行状况检查和身份验证提供可插拔支持。
一、服务定义
gRPC 主要围绕着定义服务,指定方法,参数,返回值,默认 gRPC 使用 ProtoBuf 作为 IDL 来描述服务接口和参数结构,例如:
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
gRPC 有以下 4 种服务定义方式:
1. Unary RPC
客户端向服务器发送单个请求并返回单个响应,就像普通函数调用一样。
2. Server streaming RPC
客户端发送一个请求到服务端,客户端可以通过一个流读取序列的消息回来。客户端从流中读取直到没有消息返回。 gRPC 保证单个请求的返回的消息顺序是有序的。
3. Client streaming RPC
客户端写一序列的消息并且发送到服务端,同时使用一个提供的流。一旦客户端完成了写消息,客户端将会等待服务端读取并返回响应。 在独立的 RPC 调用 gRPC 保证的消息的有序。
4. Bidirectional streaming RPC
客户端和服务端都可以使用读写流发送一系列消息。
Deadlines/Timeouts
在 RPC 被终止或者错误前,gRPC 允许客户端指定长度的时间去等待一个 RPC 的完成。
二、代码示例
开发环境:
golang 1.20.1
protoc libprotoc 3.20.3
代码目录结构
├── com
│ └── example
│ ├── label
│ │ ├── label.pb.go
│ │ └── label_grpc.pb.go
│ ├── proto
│ │ └── main.go
│ ├── rpc
│ │ ├── client.go
│ │ ├── client2.go
│ │ ├── client3.go
│ │ ├── client4.go
│ │ └── server.go
│ └── service
│ └── label.go
├── go.mod
├── go.sum
└── label.proto
1. Unary RPC
label.proto
syntax = "proto3";
package label;
option go_package = "com/example/label";
import "google/protobuf/wrappers.proto";
service LabelService{
rpc CreateLabel(CreateLabelRequest) returns (CreateLabelResponse){}
}
message CreateLabelRequest{
string name = 1;
optional string color = 2;
optional string description = 3;
}
message CreateLabelResponse{
int32 code = 1;
string message = 2;
optional Label item = 3;
}
message Label{
int64 id = 1;
string node_id = 2;
string url = 3;
string name = 4;
string description = 5;
string color = 6;
bool default = 7;
}
服务端 label.go
package service
import (
"context"
"example.com/com/example/label"
"google.golang.org/protobuf/types/known/wrapperspb"
"io"
"log"
)
type LabelServiceImpl struct {
label.LabelServiceServer
}
func (s *LabelServiceImpl) CreateLabel(ctx context.Context, req *label.CreateLabelRequest) (*label.CreateLabelResponse, error) {
log.Print("CreateLabel request::", req.String())
resp := &label.CreateLabelResponse{
Code: 200,
Message: "OK",
Item: &label.Label{
Id: 1,
NodeId: "abc123",
Url: "https://www.github.com",
Name: req.Name,
Description: "hello world",
Color: "#5f5f5f",
Default: false,
},
}
return resp, nil
}
服务端 server.go
package main
import (
"example.com/com/example/label"
"example.com/com/example/service"
"google.golang.org/grpc"
"log"
"net"
)
func main() {
server := grpc.NewServer()
label.RegisterLabelServiceServer(server, &service.LabelServiceImpl{})
listen, err := net.Listen("tcp", ":8080")
if err != nil {
log.Print(err.Error())
}
log.Print("listen on port:8080 >>>>")
if er := server.Serve(listen); er != nil {
log.Print(er.Error())
}
}
客户端 client.go
package main
import (
"context"
"example.com/com/example/label"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"time"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
defer conn.Close()
client := label.NewLabelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := client.CreateLabel(ctx, &label.CreateLabelRequest{Name: "feature-2023-10-01"})
if err != nil {
log.Print(err.Error())
return
}
log.Print("create label::", resp.String())
pageNum := int32(3)
rep, err := client.ListLabel(ctx, &label.ListLabelRequest{
Owner: "zhang san",
Repo: "example",
MilestoneNumber: 1,
Page: &pageNum,
})
if err != nil {
log.Print(err.Error())
return
}
log.Print("list label::", rep.String())
}
2. Server streaming RPC
label.proto
syntax = "proto3";
package label;
option go_package = "com/example/label";
import "google/protobuf/wrappers.proto";
rpc ListLabel2(ListLabelRequest) returns (stream Label){}
message Label{
int64 id = 1;
string node_id = 2;
string url = 3;
string name = 4;
string description = 5;
string color = 6;
bool default = 7;
}
message ListLabelRequest{
string owner = 1;
string repo = 2;
int32 milestone_number = 3;
optional int32 per_page = 4;
optional int32 page = 5;
}
message ListLabelResponse{
int32 code = 1;
string message = 2;
repeated Label items = 3;
}
服务端 label.go
func (s *LabelServiceImpl) ListLabel2(req *label.ListLabelRequest, server label.LabelService_ListLabel2Server) error {
page := req.GetPage()
for i := int32(0); i < page; i++ {
err := server.Send(&label.Label{
Id: int64(i),
NodeId: "abc123",
Url: "https://www.github.com",
Name: req.Owner,
Description: req.Repo,
Color: "#5f5f5f",
Default: false,
})
if err != nil {
log.Print(err.Error())
}
}
return nil
}
客户端 client2.go
package main
import (
"context"
"example.com/com/example/label"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"io"
"log"
"time"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
defer conn.Close()
client := label.NewLabelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
pageNum := int32(3)
respStream, err := client.ListLabel2(ctx, &label.ListLabelRequest{
Owner: "zhang san",
Repo: "example",
MilestoneNumber: 1,
Page: &pageNum,
})
if err != nil {
log.Print(err.Error())
return
}
for {
v, err := respStream.Recv()
if err == io.EOF {
break
}
log.Print("Server Stream label ::" + v.String())
}
}
3. Client streaming RPC
label.proto
syntax = "proto3";
package label;
option go_package = "com/example/label";
import "google/protobuf/wrappers.proto";
service LabelService{
rpc CreateLabel2(stream CreateLabelRequest) returns (google.protobuf.StringValue);
}
message CreateLabelRequest{
string name = 1;
optional string color = 2;
optional string description = 3;
}
服务端 label.go
func (s *LabelServiceImpl) CreateLabel2(stream label.LabelService_CreateLabel2Server) error {
labelName := "create Name::"
for {
req, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&wrapperspb.StringValue{Value: labelName})
}
log.Print("recv labelName:", req.Name)
labelName += req.Name + ","
}
}
客户端 client3.go
package main
import (
"context"
"example.com/com/example/label"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"strconv"
"time"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
defer conn.Close()
client := label.NewLabelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
stream, err := client.CreateLabel2(ctx)
if err != nil {
panic(err)
}
for i := 0; i < 3; i++ {
color := "#fffff" + strconv.Itoa(i)
err := stream.Send(&label.CreateLabelRequest{
Name: "hello" + strconv.Itoa(i),
Color: &color,
})
if err != nil {
panic(err)
}
}
resp, err := stream.CloseAndRecv()
if err != nil {
panic(err)
}
log.Print(resp)
}
4. Bidirectional streaming RPC
label.proto
syntax = "proto3";
package label;
option go_package = "com/example/label";
import "google/protobuf/wrappers.proto";
service LabelService{
rpc CreateLabel3(stream CreateLabelRequest) returns (stream Label){}
}
message CreateLabelRequest{
string name = 1;
optional string color = 2;
optional string description = 3;
}
message Label{
int64 id = 1;
string node_id = 2;
string url = 3;
string name = 4;
string description = 5;
string color = 6;
bool default = 7;
}
服务端 label.go
func (s *LabelServiceImpl) CreateLabel3(stream label.LabelService_CreateLabel3Server) error {
batch := 2
cnt := 0
nameList := ""
for {
req, err := stream.Recv()
if err != nil {
return err
}
cnt++
nameList += req.Name + ","
if cnt == batch {
err := stream.Send(&label.Label{
Id: 123,
NodeId: "123",
Url: "https://www.github.com",
Name: nameList,
Description: "bidirectional stream rpc example",
Color: *req.Color,
Default: false,
})
if err != nil {
return err
}
cnt = 0
nameList = ""
}
}
}
客户端 client4.go
package main
import (
"context"
"example.com/com/example/label"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"io"
"log"
"strconv"
"time"
)
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
defer conn.Close()
client := label.NewLabelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
stream, err := client.CreateLabel3(ctx)
if err != nil {
panic(err)
}
go func() {
for i := 0; i < 7; i++ {
color := "#fffff" + strconv.Itoa(i)
err := stream.Send(&label.CreateLabelRequest{
Name: "rpc example" + strconv.Itoa(i),
Color: &color,
})
if err != nil {
panic(err)
}
}
}()
for {
v, err := stream.Recv()
if err == io.EOF || v == nil {
break
}
log.Print(v.Name)
}
}
主要是对 gRPC 的 4 种服务定义方式的了解和代码实现。