【gRPC】消息流模式

384 阅读3分钟

srteam 顾名思义 就是 一种 流,可以源源不断的 推送 数据,很适合 传输一些大数据,或者 服务端 和 客户端 长时间 数据交互,比如 客户端 可以向 服务端 订阅 一个数据,服务端 就 可以利用 stream ,源源不断地 推送数据。

一、介绍

1.服务端数据流

这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

2.客户端数据流

与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。典型的例子是物联网终端向服务器报送数据。

3.双向数据流

顾名思义,这是客户端和服务端都可以向对方发送数据流,这个时候双方的数据可以同时互相发送,也就是可以实现实时交互。典型的例子是聊天机器人。

二、代码示例

1.proto文件

syntax = "proto3";

option go_package = ".;proto";

service Greeter{
rpc GetStream (ReqData) returns(stream ResData);//服务端推送流
rpc PutStream(stream ReqData) returns (ResData);//客户端推送流
rpc AllStream(stream ReqData) returns (stream ResData);//双向推送流

}

message ReqData{
string data = 1;
}

message ResData{
string data = 1;
}

2.服务端

package main

import (
    "fmt"
    "google.golang.org/grpc"
    "grpc_demo01/stream/proto"
    "log"
    "net"
    "sync"
    "time"
)

type server struct {
}

func (*server) GetStream(reqData *proto.ReqData, res proto.Greeter_GetStreamServer) error {
    for i := 0; i < 10; i++ {
        err := res.Send(&proto.ResData{Data: fmt.Sprintf("%v", time.Now().Unix())})
        if err != nil {
            return err
        }
        time.Sleep(1 * time.Second)
    }
    return nil
}

func (*server) PutStream(res proto.Greeter_PutStreamServer) error {
    for {
        recv, err := res.Recv()
        if err != nil {
            log.Println(err)
            break
        }
        log.Println(recv)
    }
    return nil
}
func (*server) AllStream(allStr proto.Greeter_AllStreamServer) error {
    wg := sync.WaitGroup{}
    wg.Add(2)
    go func() {
        defer wg.Done()
        for {
            recv, err := allStr.Recv()
            if err != nil {
                log.Fatal(err)
            }
            log.Println(recv)
        }

    }()

    go func() {
        defer wg.Done()
        for i := 0; i < 10; i++ {

            err := allStr.Send(&proto.ResData{Data: fmt.Sprintf("服务端发送", i, "条信息")})
            time.Sleep(1 * time.Second)
            if err != nil {
                log.Fatal(err)
                return
            }
        }
    }()
    wg.Wait()
    return nil
}

func main() {
    //监听端口
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
        return
    }
    //创建一个grpc 服务器
    s := grpc.NewServer()
    //注册事件
    proto.RegisterGreeterServer(s, &server{})
    //处理链接
    err = s.Serve(lis)
    if err != nil {
        panic(err)
    }
}

3.客户端

package main

import (
	"context"
	"google.golang.org/grpc"
	"grpc_demo01/stream/proto"
	"log"
	"time"
)

const (
	ADDRESS = "localhost:8080"
)

func main() {
	//通过grpc 库 建立一个连接
	conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
	if err != nil {
		return
	}
	defer conn.Close()
	//通过刚刚的连接 生成一个client对象。
	c := proto.NewGreeterClient(conn)
	//调用服务端推送流
	reqstreamData := &proto.ReqData{Data: "aaa"}
	res, _ := c.GetStream(context.Background(), reqstreamData)
	for {
		aa, err := res.Recv()
		if err != nil {
			log.Println(err)
			break
		}
		log.Println(aa)
	}

	//客户端 推送 流

	putRes, err := c.PutStream(context.Background())
	i := 1
	for {
		i++
		err := putRes.Send(&proto.ReqData{Data: "ss"})
		if err != nil {
			log.Println(err)
			return
		}
		time.Sleep(time.Second)
		if i > 10 {
			break
		}
	}

	//服务端 客户端 双向流
	allStr, _ := c.AllStream(context.Background())
	go func() {
		for {
			data, _ := allStr.Recv()
			log.Println(data)
		}
	}()

	go func() {
		for {
			err := allStr.Send(&proto.ReqData{Data: "ssss"})
			if err != nil {
				log.Println(err)
				return
			}
			time.Sleep(time.Second)
		}
	}()

	select {}
}

三、出现问题处理方式

gRPC 是一个高性能、开源的通用 RPC 框架,用于在不同的服务中实现通信。在 gRPC 流中途传输发生错误时,需要采取相应的错误处理策略。以下是处理 gRPC 流中途传输错误的一些建议:

  1. 错误检查:在发送和接收数据时,始终检查错误。发送和接收数据的函数通常返回一个错误,如果出现问题,该错误将不为nil。例如,在客户端中,当调用 stream.Send()stream.Recv() 时,始终检查返回的错误。
resp, err := stream.Recv()
if err != nil {
    if err == io.EOF {
        // 流结束,处理流结束的情况
    } else {
        // 处理其他错误
    }
}
  1. 错误处理:当检测到错误时,可以执行以下操作之一或多个:
    • 记录错误:使用日志库记录错误信息,以便在调试或分析问题时使用。
    • 重试:如果错误是暂时的,可以尝试重新连接或重新发送数据。注意要设置重试次数和延迟,以避免无限循环。
    • 返回错误:将错误返回给上游调用者,让其决定如何处理。
    • 自定义错误处理:根据错误类型和具体场景执行自定义的错误处理操作。
  1. 使用 gRPC 中间件:可以使用 gRPC 中间件(例如 grpc-middleware)来实现全局的错误处理和重试策略。这可以帮助减少代码重复和提高代码可读性。
  2. 超时和取消:使用 gRPC 的超时和取消功能,确保请求在预期时间内完成。如果操作未在预期时间内完成,可以取消请求并处理超时错误。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

stream, err := client.SomeStreamingRPC(ctx)
  1. 使用健康检查:可以使用 gRPC 健康检查(例如 grpc-health-probe)来检查服务的可用性。这样可以在传输发生错误之前识别潜在的问题。

通过遵循以上建议,您可以在 gRPC 流中途传输错误时更好地处理错误和异常情况。根据具体的应用场景和需求,可以根据需要调整错误处理策略。