美文网首页grpc
grpc一次请求处理的过程

grpc一次请求处理的过程

作者: 码农崛起 | 来源:发表于2018-04-24 22:20 被阅读0次

    1, 先从protobuf开始吧。
    protobuf是一个高效的序列化协议,protobuf分两部分,一部分是用c++编写的protoc编译器,用于把proto文件编译为java/c#/go/c++等语言的支持序列化反序列化的对象。


    protoc.png

    protoc -I [proto文件路径] --java_out [生成的类文件路径] [proto文件路径]
    生成的java类都继承自protobuf-java中的类。

    以后有时间分析一下protoc的源码!!!

    2, 如正题,先从源码中的helloword开始。


    helloword.png

    下面开始分析server启动过程以及一个请求的处理过程。
    3, 从类名就可以看出使用的构造器设计模式,此模式特别适用于配置参数特别多的类。lombok中的@Builder注解非常方便。


    spi加载ServerProvider.png spi加载ServerProvider.png

    从源码中可以看到,此处通过jdk中的spi加载ServerProvider,最终加载到的是NettyServerProvider类。NettyServerProvider的builderForPort返回NettyServerBuilder类。


    ServerProvider类层次结构.png

    ServerProvider里的方法不多,用于注册服务,添加拦截器,初始化server状态。
    最后调用build构造出server对象。构造的过程如下:


    构造server.png 构造server.png

    ServerImpl维护着整个server的状态,最关键的属性如下:
    1,ObjectPool<? extends Executor> executorPool:执行请求的服务方法的线程池。
    2,InternalHandlerRegistry registry:服务方法主注册表,运行时不能修改。
    3,HandlerRegistry fallbackRegistry:fallback注册表,运行时可以动态修改。
    4,List<ServerTransportFilter> transportFilters:监听client连接ready和terminal事件。
    5, ServerInterceptor[] interceptors:服务拦截器,添加顺序和执行顺序相反,服务方法执行时以装饰器设计模式嵌入执行流程。
    6, InternalServer transportServer:NettyServer。
    7, Collection<ServerTransport> transports:accept到的client连接。
    8, DecompressorRegistry decompressorRegistry:解码器注册表。
    9, CompressorRegistry compressorRegistry:编码器注册表。
    10, BinaryLogProvider binlogProvider:记录log。
    11, Channelz channelz:记录server,channel的状态。
    12, CallTracer serverCallTracer:统计rpc调用次数。

    4, server构造之后开始调用start方法启动。


    startup.png

    此处的重点是启动传输层时传入了ServerListener,用来client连接建立或终结时从传输层回调server。


    netty.png

    netty启动过程,重点是childHandler设置的处理client请求的pipeline。

    设置childHandler.png

    此处的重点是ServerTransportListener,在连接建立和收到client请求数据时从传输层回调server。

    设置childHandler.png 设置childHandler.png

    洗尽铅华,发现最终处理client的handler只有NettyServerHandler而已。


    NettyServerHandler.png

    从类层次上看以看到NettyServerHandler只是一个解码器decoder,具体点就是Http2ConnectionHandler,由此可见,grpc的传输协议是http2,相比与http1.1效率有很大提升,据说主要在两方面:利用IO多播使一个连接可以请求多个资源,header压缩,有空详细分析http2协议。
    在NettyServerHandler的构造过程中有一句至关重要: decoder().frameListener(new FrameListener())。熟悉netty codec机制的一看就明白,就是根据http2协议,每次解析到一帧都会通知。


    Http2FrameListener.png
    重点是onDataRead,onHeadersRead,onSettingsRead(tcp握手完成)顾名思义。
    FrameListener.png

    现在万事具备了,server已经启动,启动的过程主要在注册各种服务,添加服务拦截器等,初始化netty。

    当有请求到来时,ByteToMessageDecoder会按http2协议解析帧,当收到http header时,


    收到http header.png

    此处最关键的时构造了NettyServerStream,此类的作用是一个请求完成后把响应信息返回给client,最重要的属性是WriteQueue writeQueue,响应信息的队列,缓存响应信息,等全部处理完,调用flush把所有响应信息返回给client。
    另一个重点是通过listener回调通知server已经收到了数据包,第一个数据包永远都是header。header都有啥?


    header.png
    最关键的是method,服务方法的全限定名。

    现在的流程从传输层转到了server。


    设置listener.png

    此处设置了一个非常关键的JumpToApplicationThreadServerStreamListener,当IO线程收齐http body之后回调。

    查找方法.png

    很明显,找到请求的服务,调用它。很神奇,只收到header就开始调用服务!!!netty的io是全异步的,调用的过程中已经在接收http body啦。

    调用服务.png 调用服务.png

    装饰器设计模式的应用,套了一层又一层,此处很容易明白为啥服务拦截器添加顺序和执行顺序相反啦。

    终于开始调用方法了,根据请求的方法的类型调用对应的ServerCallHandler。


    服务方法类型.png

    以最简单又最常见的UNARY为例吧,一个请求跟随一个响应。


    UnaryServerCallHandler.png

    此处的call.request(2)至关重要,2表示应该收到2个帧,注释里解释里原因。

    UnaryServerCallListener.png

    当收到http body时,通过一大串listener的通知,最后到达onMessage,此时的request已经经过里protobuf的反序列化,转成里请求对象。

    根据tcp协议,client发送完请求后,如果不是长连接,会关闭输入端,等待响应。一旦输入端关闭,就知道所有请求都发送完啦,终于可以调用服务方法啦。
    那这个UnaryServerCallListener是怎么被调用呢。
    就在上面的call.request(2)这句啦,


    request.png deliver.png 循环读.png

    一直循环直到读完所有数据,但是这个循环本身并不直接操作IO,它只是把CompositeReadableBuffer unprocessed里面的内容收起起来判断是否读到了需要的数据。

    毫无疑问,unprocessed里的内容是IO线程读出来的。

    此时FrameListener收到header之后正忙着收http body。


    收到http body.png

    通知listener收到了新的数据包

    收到http body.png 收到http body.png

    把收到的数据都放到CompositeReadableBuffer unprocessed缓存起来。

    一个在监听帧,然后读数据放到unprocessed里缓存,一个是循环汇总unprocessed里的数据,终于交织在一起啦。

    等http body读完之后,开始通知之前建立的一大串listener。中间会经过protobuf反序列化的过程,最终到达UnaryServerCallListener.onMessage。


    通知listener.png

    然后等待netty收到输入关闭事件,通过一串listener,最终到达UnaryServerCallListener.onHalfClose事件。然后真正的到了执行服务方法的时候,method.invoke(request, responseObserver);服务方法执行完,得到的结果会回调responseObserver.onNext方法,


    触发onNext.png 触发onNext.png protobuf序列化.png writeFrame.png 响应队列.png 触发channel的flush.png

    最后触发channel的flush,返回响应信息。

    5, 再缕一下整个请求处理流程。
    1,server在启动netty时传入了ServerListenerImpl,用于接收client连接建立的回调通知。
    2, netty通知server client连接建立时,server返回给netty的是ServerTransportListenerImpl,用于netty在收到http请求帧时回调通知server。
    3,netty收到http header时创建NettyServerStream,然后通过回调通知server,server根据http header里的method属性查询请求的服务方法,给NettyServerStream又设置一个JumpToApplicationThreadServerStreamListener,用于当收到http body时回调。
    4, 收到http header时开始startCall,UnaryServerCallHandler.startCall时通过调用ServerCall.request方法,进入MessageDeframer的request方法,然后一直循环检查是否收到了指定帧数的http body,UnaryServerCallHandler.startCall返回UnaryServerCallListener,返回的listener又被wrap到ServerStreamListenerImpl里,wrap之后又被set到JumpToApplicationThreadServerStreamListener里。
    5,整个listener链已经设置好了,就等着netty收到http body啦。
    6, netty最终收齐了http body。然后第4步中startCall里那个循环检测到http body收齐了,结束循环。
    7, 然后开始沿着listener链通知下去,请求信息在protobuf反序列化之后最终到达UnaryServerCallListener.onMessage保存起来
    8, 等待netty收到输入端关闭的事件,然后执行服务方法。
    9, 服务方法执行完,响应信息通过ServerCallStreamObserverImpl.onNext方法protobuf序列化之后,触发netty channel的flush事件,返回响应给client。

    从请求到响应的整个流程结束!!!

    相关文章

      网友评论

        本文标题:grpc一次请求处理的过程

        本文链接:https://www.haomeiwen.com/subject/hdajlftx.html