springboot-grpc最大传输上限问题探索

场景:

基于grpc搭建的微服务,在调用的时候一个批次传输数据量太大导致服务器报如下错误:
搭建微服务 https://www.jianshu.com/p/2207011c0164

2019-03-06 12:46:07.544  WARN 2188 --- [-worker-ELG-3-7] io.grpc.netty.NettyServerStream          : Exception processing message

io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: io.grpc.netty.NettyServerStream$TransportState: Frame size 10311685 exceeds maximum: 4194304. 
    at io.grpc.Status.asRuntimeException(Status.java:517) ~[grpc-core-1.10.0.jar:1.10.0]
    at io.grpc.internal.MessageDeframer.processHeader(MessageDeframer.java:391) ~[grpc-core-1.10.0.jar:1.10.0]
    at io.grpc.internal.MessageDeframer.deliver(MessageDeframer.java:271) ~[grpc-core-1.10.0.jar:1.10.0]
    at io.grpc.internal.MessageDeframer.request(MessageDeframer.java:165) ~[grpc-core-1.10.0.jar:1.10.0]
    at io.grpc.internal.AbstractStream$TransportState.requestMessagesFromDeframer(AbstractStream.java:202) ~[grpc-core-1.10.0.jar:1.10.0]
    at io.grpc.netty.NettyServerStream$Sink$1.run(NettyServerStream.java:100) [grpc-netty-1.10.0.jar:1.10.0]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163) [netty-common-4.1.29.Final.jar:4.1.29.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) [netty-common-4.1.29.Final.jar:4.1.29.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404) [netty-common-4.1.29.Final.jar:4.1.29.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:446) [netty-transport-4.1.29.Final.jar:4.1.29.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) [netty-common-4.1.29.Final.jar:4.1.29.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.29.Final.jar:4.1.29.Final]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_05]

代码复现:

HelloController

@RestController
public class HelloController {
    @Autowired
    private HelloService service;
    @GetMapping("/hello")
    public String sayHello(String name) {
        //此处构造超长消息
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 1024*2014; i++) {
            sb.append(name);
        }
        return service.sendMessage(sb.toString());
    }
}

HelloService

@Service(value = "helloService")
public class HelloService extends HelloServiceGrpc.HelloServiceImplBase {
    @GrpcClient("local-grpc-server")
    private Channel channel;
    public String sendMessage(String name) {
        HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel);
        HelloResponse response = stub.sayHello(HelloRequest.newBuilder().setName(name).build());
        return response.getMessage();
    }
}

服务端

@GrpcService(HelloServiceGrpc.class)
public class HelloService extends HelloServiceGrpc.HelloServiceImplBase {
    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
        String name = request.getName();
        System.out.println("received name: "+name);
        //此处为了不影响页面响应,直接返回一个字符串,不把全部消息返回
        HelloResponse response = HelloResponse.newBuilder().setMessage("welcome to gRPC").build();
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

调用:
http://localhost:8080/hello?name=world

结果:

出现如片段1的报错信息,接下来对问题进行详细探索并且给出解决方案

探索:

step1:阅读原有日志

io.grpc.netty.NettyServerStream$TransportState: Frame size 10311685 exceeds maximum: 4194304.
稍微翻译一下: 帧长度为10311685,超过最大值 4194304

step2:grpc官方状态码解释:

grpc官方状态码解释(grpc官方资源)
HTTP2错误码解释(资源来源于网络)

那么我们的错误就很明显了:
RESOURCE_EXHAUSTED...并且运行时提供有额外的错误详情,表示耗尽资源是带宽
那么通过上面各种巴拉巴拉一大堆,说人话,就是我们的消息字符串超常了,最大4m,我们传过去10m,报错了

找解决方案:

思路整理:

1:通过调整最大传输上限参数
客户端具体参数查看:
net.devh.springboot.autoconfigure.grpc.client.GrpcChannelProperties
类,其中有以下参数:

@Data
public class GrpcChannelProperties {

    public static final String DEFAULT_HOST = "127.0.0.1";
    public static final Integer DEFAULT_PORT = 9090;

    public static final GrpcChannelProperties DEFAULT = new GrpcChannelProperties();

    private List<String> host = new ArrayList<String>() {
        private static final long serialVersionUID = -8367871342050560040L;

        {
            add(DEFAULT_HOST);
        }
    };
    private List<Integer> port = new ArrayList<Integer>() {
        private static final long serialVersionUID = 4705083089654936515L;

        {
            add(DEFAULT_PORT);
        }
    };

    private boolean plaintext = true;

    private boolean enableKeepAlive = false;

    private boolean keepAliveWithoutCalls = false;

    private long keepAliveTime = 180;

    private long keepAliveTimeout = 20;

    private int maxInboundMessageSize;
}
#客户端参数优化
grpc.client.local-grpc-server.maxInBoundMessageSize=20971520

服务端参数查看:
net.devh.springboot.autoconfigure.grpc.server.GrpcServerProperties
参数如下:

@Data
@ConfigurationProperties("grpc.server")
public class GrpcServerProperties {
   
    private int port = 9090;

    private String address = "0.0.0.0";
    
    private int maxMessageSize;

    private final Security security = new Security();

    @Data
    public static class Security {

        private Boolean enabled = false;

        private String certificateChainPath = "";

        private String certificatePath = "";
    }
}
#服务端参数优化
grpc.server.max-message-size=20971520

产生结果:


调用结果
`能够正常返回值`
`但是会导致响应变慢,资源损耗较大`
`有时候无法确定最大传输量级,不能正确给设置正确的参数,需要反复增加数字`

2:grpc有流式传输,stream消息
进行此方法前,为了验证,请注掉上面的参数优化步骤!

# grpc.server.max-message-size=20971520

消息定义:

service HelloService {
    rpc SayHello (stream HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
    bytes name = 1;
}

message HelloResponse {
    string code = 1;
    string message = 2;
}

服务端代码:

@GrpcService(HelloServiceGrpc.class)
public class HelloService extends HelloServiceGrpc.HelloServiceImplBase {


    private static final Logger logger = LoggerFactory.getLogger(HelloService.class);

    @Override
    public StreamObserver<HelloRequest> sayHello(StreamObserver<HelloResponse> responseObserver) {
        return new StreamObserver<HelloRequest>() {
            @Override
            public void onNext(HelloRequest value) {
                String name = value.getName().toStringUtf8();
                logger.info("received name :" + name);
            }

            @Override
            public void onError(Throwable t) {
                logger.warn("throw an error :", t);
            }

            @Override
            public void onCompleted() {
                 responseObserver.onNext(HelloResponse.newBuilder().setMessage("welcome to gRPC").build());
                 responseObserver.onCompleted();
            }
        };
    }
}

客户端调用:
-- controller

@RestController
public class HelloController {

    @Autowired
    private HelloService service;

    @GetMapping("/hello")
    public String sayHello(String name) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 1024; i++) {
            sb.append(name);
        }
        return service.sendMessage(sb.toString());
    }
}

-- grpcConfig可以配置多个ServiceStub

@Component
public class GrpcConfig {

    @GrpcClient(value = "local-grpc-server")
    private Channel channel;


    @Bean("helloServiceStub")
    public HelloServiceGrpc.HelloServiceStub getHelloServiceStub() {
        return HelloServiceGrpc.newStub(channel);
    }
}

service调用grpc

@Service(value = "helloService")
public class HelloService{


    @Autowired
    private  HelloServiceGrpc.HelloServiceStub helloServiceStub;

    public String sendMessage(String name) {
        //构造一个Request观察者对象,需要的参数是Response的观察者对象,需要重写以下方法
        StreamObserver<HelloRequest> helloRequestStreamObservers = helloServiceStub.sayHello(
                new StreamObserver<HelloResponse>() {
            @Override
            public void onNext(HelloResponse value) {
                System.out.println("onNext : " + value.getMessage());
            }

            @Override
            public void onError(Throwable t) {
                System.out.println(this.getClass().getName() + " onError :" + t.getMessage());
            }

            @Override
            public void onCompleted() {
            }
        });
        for (int i = 0; i < 1024; i++) {
            //此处循环使用流的方式发送消息,消息长度是controller里面给过来的,但是重复再发送1024次
            //和上面发送的消息总长度一样
            helloRequestStreamObservers.onNext(
                    HelloRequest.newBuilder()
                            .setName(ByteString.copyFrom(name,Charset.forName("UTF-8")))
                            .build()
            );
        }
        helloRequestStreamObservers.onCompleted();
         //此处好多人可能疑惑怎么把server端返回的message给前台,后续补充这个
        return null;
    }
}

总结:
看到网上有很多go语言和python写成的demo,有关最大传输上限的改动的,也有用数据流的,也有分块传输的,这篇是本渣根据官方demo http://doc.oschina.net/grpc?t=60134,改出来的,希望大家多多交流指导。结果具体大家可以去测试,但是至少这个是安全的,不会因为下次更大的数据量导致的再次崩溃。
原创帖,转载请注明出处!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容