二、Golang gRPC 服务调用

55 阅读4分钟

  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 种服务定义方式的了解和代码实现。