grpc streaming

775 阅读3分钟

介绍

grpc streaming是基于http/2的。

简单版的rpc存在以下问题

  • 数据包过大时造成的瞬时压力
  • 接收数据包时,需要所有数据包都接受成功且正确后,才能够回调响应,进行业务处理(无法客户端边发送,服务端边处理)

streaming rpc优点

  • 大规模数据包
  • 实时场景

目录结构

image.png

stream_server、stream_client存放服务端和客户端文件。

IDL

stream.proto文件内容

syntax = "proto3";

package proto;

service StreamService{
  //服务器端流式rpc
  rpc List(StreamRequest) returns (stream StreamResponse){};
  //客户端流式rpc
  rpc Record(stream StreamRequest)returns (StreamResponse){};
  //双向流式rpc
  rpc Route(stream StreamRequest)returns(stream StreamResponse){};

}
message StreamPoint{
  string name = 1;
  int32 value = 2;
}
message StreamRequest{
  StreamPoint pt = 1;
}
message StreamResponse{
  StreamPoint pt = 1;
}

基础模板

server

package main

import (
	"google.golang.org/grpc"
	"log"
	"net"
	pb "wsn-grpc/proto"
)

type StreamService struct {
}

func main() {
	port := ":9092"
	server := grpc.NewServer()
	pb.RegisterStreamServiceServer(server, &StreamService{})
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatal("net.listen err:%v", err)
	}
	server.Serve(lis)
}

func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
	return nil
}

func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
	return nil
}
func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
	return nil
}

client

package main

import (
	"google.golang.org/grpc"
	"log"
	pb "wsn-grpc/proto"
)

func main() {
	port := ":9092"
	conn, err := grpc.Dial("localhost"+port, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("grpc.Dial err:%v", err)
	}
	defer conn.Close()
	client := pb.NewStreamServiceClient(conn)
	err = printLists(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc list", Value: 2022}})
	if err != nil {
		log.Fatalf("printlists.err:%v", err)
	}
	err = printRecord(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc record", Value: 2022}})
	if err != nil {
		log.Fatalf("printrecord.err:%v", err)
	}
	err = printRoute(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "grpc route", Value: 2022}})
	if err != nil {
		log.Fatalf("printroute.err:%v", err)
	}

}

func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	return nil
}

func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	return nil
}
func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	return nil
}

代码实现

服务端流式RPC

服务端流式RPC,是单向流,指的是 server端为stream client端是普通的rpc请求。

server

func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
	for n := 0; n <= 6; n++ {
		time.Sleep(2*time.Second)
		err := stream.Send(&pb.StreamResponse{
			Pt: &pb.StreamPoint{
				Name:  r.Pt.Name,
				Value: r.Pt.Value + int32(n),
			},
		})
		if err != nil {
			return err
		}
	}
	return nil
}

client

func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	stream, err := client.List(context.Background(), r)
	if err != nil {
		return err
	}
	for {
		resp, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		log.Printf("resp:name:%s,value:%s", resp.Pt.Name, resp.Pt.Value)

	}
	return nil
}

RecvMsg会从流中读取完整的grpc消息体。

  • RecvMsg是阻塞等待的
  • RecvMsg当流成功/结束(调用close)时,会返回io.EOF
  • RecvMsg当流出现任何错误时,流会被中止,错误信息会包含rpc错误码。

默认的MaxReceiveMessageSize值为102410244,建议不要超出。

客户端流式RPC

客户端通过流式发起多次RPC请求给服务端,服务端发起一次响应给客户端。

server

func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
	for {
		r, err := stream.Recv()
		if err == io.EOF {
			return stream.SendAndClose(&pb.StreamResponse{Pt: &pb.StreamPoint{Name: "grpc server record", Value: 1}})
		}
		if err != nil {
			return err
		}
		log.Printf("stream recv name:%s value:%d", r.Pt.Name, r.Pt.Value)
		time.Sleep(1 * time.Second)
	}
	return nil
}

stream.SendAndClose作用:

我们对客户端发送的消息都进行了处理,当发现io.EOF(流关闭)后,需要将最终的响应结果发送给客户端,同时关闭正在另外一侧等待的Recv

client

func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	stream, err := client.Record(context.Background())
	for n := 0; n < 6; n++ {
		err := stream.Send(r)
		if err != nil {
			return err
		}
	}
	resp, err := stream.CloseAndRecv()
	if err != nil {
		return err
	}

	log.Printf("resp: name:%s value:%d", resp.Pt.Name, resp.Pt.Value)
	return nil
}

stream.CloseAndRecv和stream.SendAndClose是配套使用的流方法。

双向流式RPC

客户端与服务端都以同样的流式操作。

首个请求一定是client发起,但具体交互方式(谁先谁后、一次发多少、响应多少、什么时候关闭)根据程序编写的方式来确定。

server

func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
	n := 0
	for {
		err := stream.Send(&pb.StreamResponse{
			Pt: &pb.StreamPoint{
				Name:  "grpc stream client:route",
				Value: int32(n),
			},
		})
		if err != nil {
			return err
		}
		r, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		n++
		log.Printf("stream recv name:%s value:%d", r.Pt.Name, r.Pt.Value)
	}
	return nil
}

client

func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
	stream, err := client.Route(context.Background())
	if err != nil {
		return err
	}
	for n := 0; n <= 6; n++ {
		err := stream.Send(r)
		if err != nil {
			return err
		}
		resp, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		log.Printf("resp name:%s value:%d", resp.Pt.Name, resp.Pt.Value)
	}
	stream.CloseSend()
	return nil
}