如何解决 gRPC 中的 "received message larger than max"

1,646 阅读3分钟

问题描述

在gRPC进行服务间通信时,发送了一个较大的消息,出现 "grpc: received message larger than max" 的错误。

原因

gRPC 客户端和服务端都有一个默认的最大消息大小限制,默认为4MB, gRPC在发送或接收的消息大小超过最大限制时,就会抛出这个错误,导致通信失败。其相關源碼如下:

func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byte, err error) {
   ...
   if int(length) > maxReceiveMessageSize {
      return 0, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max (%d vs. %d)", length, maxReceiveMessageSize)
   }
   ...

问题重现

  1. 创建一个greeter.proto,参考链接 https://github.com/grpc/grpc-go/tree/master/examples/helloworld
syntax = "proto3";

package helloworld.v1;

import "google/api/annotations.proto";

option go_package = "helloworld/api/helloworld/v1;v1";
option java_multiple_files = true;
option java_package = "dev.kratos.api.helloworld.v1";
option java_outer_classname = "HelloworldProtoV1";

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/helloworld/{name}"
    };
  }
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
  1. 实现grpc服务端,创建文件grpc-server/main.go,详细如下:
package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "helloworld/api/helloworld/v1"
)

var (
    port = flag.Int("port", 9000, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    if err != nil {
       log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
       log.Fatalf("failed to serve: %v", err)
    }
}

启动grpc server

server listening at [::]:9000

实现grpc客户端,创建文件grpc-client/main.go,发送的消息大小超过了默认的最大限制(4MB),详细如下

package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    pb "helloworld/api/helloworld/v1"
)

func main() {
    conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
       log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    //name := "world"
    name := make([]byte, 10*1024*1024) // 10MB 的数据

    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: string(name)})
    if err != nil {
       log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

發送一個10MB的信息,執行输出:

could not greet: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (10485765 vs. 4194304)

可預見問題重現了。

如何解决

通过修改增加 gRPC 客户端和服务端启动option (分别为grpc.ServerOptiongrpc.WithDefaultCallOptions)来修改的最大消息大小限制

  1. grpc server的修改如下
grpcOpts := []grpc.ServerOption{
    grpc.MaxRecvMsgSize(20 * 1024 * 1024), // 最大接收消息大小为 20MB
    grpc.MaxSendMsgSize(20 * 1024 * 1024), // 最大发送消息大小为 20MB
}
s := grpc.NewServer(grpcOpts...) 
  1. grpc client的修改
conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), grpc.WithBlock(),
    grpc.WithDefaultCallOptions(
       grpc.MaxCallRecvMsgSize(20*1024*1024), // 设置最大接收消息为 20MB
       grpc.MaxCallSendMsgSize(20*1024*1024), // 设置最大发送消息为 20MB
    ))

再次發送消息,输出如下:

 Greeting: Hello 

説明成功解决最大消息大小限制的问题。

kratos框架里如何修改grpc传输数据上限

kratos可以通过Options(opts ...grpc.ServerOption) ServerOption配置一些额外的 grpc.ServerOption

import (
    "github.com/go-kratos/kratos/v2/transport/grpc"
    ggrpc "google.golang.org/grpc"
    ...
)

grpcOpts := []grpc.ServerOption{
    grpc.Options(
       ggrpc.MaxRecvMsgSize(20*1024*1024), // 最大接收消息大小为 20MB
       ggrpc.MaxSendMsgSize(20*1024*1024), // 最大发送消息大小为 20MB
    ),
}

grpcClient可以修改grpc.CallOption来实现

opts := []grpc.CallOption{
grpc.MaxCallRecvMsgSize(20 * 1024 * 1024), // 最大接收消息大小为 20MB
grpc.MaxCallSendMsgSize(20 * 1024 * 1024), // 最大发送消息大小为 20MB
}

小结

本文主要介绍如何解决grpc通信时抛出grpc: received message larger than max 的错误,原因是消息大小超过了客户端和服务端默认的最大消息大小限制, 文中通过修改gRpc服务端、客户端的例子演示了如何復現、解决该问题。当然,在实际开发中,我们应该注意:1. 最大消息大小要根据业务场景,不宜过大或太小,避免浪费资源。2. 如果需要传输大数据,可以考虑流式Rpc来分块传输。 另外,在kratos中,可以通过配置一些额外的 grpc.ServerOption grpc.CallOption 来修改。 希望对您有所帮助。

参考

  1. grpc.io grpc.io/docs/langua…
  2. github.com/grpc/grpc-go github.com/grpc/grpc-g…
  3. kratos grpc 配置 go-kratos.dev/docs/compon…