GRPC Java源码解析

GRPC Java源码解析

  • 引言
  • 1 服务端
    • 1.1 逻辑架构
      • 1.1.1 概述
      • 1.1.2 服务管理(Server Pack)
      • 1.1.3 服务注册(Registry Pack)
      • 1.1.4 传输逻辑(Transport Pack)
      • 1.1.5 网络处理(Handler Pack(io.grpc.netty))
        • 1.1.5.1 netty领域
      • 1.1.6 网络流(Stream Pack)
      • 1.1.7 方法调用(Call Pack)
    • 1.2 运行时
      • 1.2.1 服务构建
      • 1.2.2 服务启动
      • 1.2.3 连接处理
        • 1.2.3.1 接收消息头
        • 1.2.3.2 消息体处理
    • 1.3 代码解读
  • 2 客户端
    • 2.1 逻辑架构
      • 2.1.1 概述
      • 2.1.2 上层通道(Channel Pack)
      • 2.1.3 拦截器(Interceptor Pack)
      • 2.1.4 方法调用(Call Pack)
      • 2.1.5 逻辑传输层(Transport Pack)
      • 2.1.6 网络流(Steam Pack)
      • 2.1.7 网络通道(SubChannel Pack)
      • 2.1.8 网络处理(Handler Pack(io.grpc.netty))
    • 2.2 运行时
      • 2.2.1 通道构建
      • 2.2.2 发起调用
      • 2.2.3 启动DNS
    • 2.2.4 域名解析完成
      • 2.2.5 构建真实流
    • 2.3 代码解读
  • 3 心得总结
    • 3.1 分析过程
      • 3.1.1 先划分合理的边界
      • 3.1.2 以更高的视角阅读源码
      • 3.1.3 分析推导很重要

引言

GRPC是谷歌推出的多语言支持的远程调用框架,其Java版本总体架构如下图。共分为四层,从上到下,第一层为服务端/客户端实现(Server/Client Codes),由GRPC的使用者去实现业务功能;第二层为服务端/客户端桩代码(GRPC Protobuf Server/Client Stub),这一层是服务端和客户端定的应用接口的实现,负责方法参数的序列化和反序列化以及与GRPC的交互(使用过Protobuf的朋友应该不难理解其功能);第三层为GRPC核心框架层(GRPC Frame),这一层是GRPC的功能核心,由几个关键的源码包组成;第四层为GRPC传输实现层(Transport Impl),由于GRPC核心框架层以接口方式抽象了底层数据传输逻辑,所以实际的底层数据传输可以有不同的实现去替换,目前有基于Netty的网络实现和同进程的精简实现两个版本。
GRPC Java源码解析_第1张图片
本文重点解读GRPC核心框架(GRPC Frame)这一层的实现,以GRPC Netty Impl作为传输层实现场景(GRPC Inprocess Impl功能与之类似)。后面我将先剖析其逻辑架构和运行时,然后再解读其关键的代码实现。
注1 本次解读的GRPC Java版本为1.25.0,对应的Netty版本为4.1.42。
注2 在剖析逻辑架构的过程中,我会用包图去划分它的功能,所以图中的包图不是源码包,它只是逻辑包。在实际的源码中这些不同逻辑包中的类可能属于同一个源码包,同一个逻辑包中的类也可能分散在不同的源码包中。逻辑包可理解为只是按照功能划分的逻辑单元,希望读者不要与源码包混淆。
注3 后面的类图分为三个颜色,黄色为GRPC核心框架(GRPC Frame)这一层的类;黑灰色为GRPC Netty impl这一层的类;红色为Netty库中的类。
注4 对于只想了解原理或者概念的人而言,肯定是不喜欢我后面画的这些逻辑类图,运行交互图等等,所以本文的目的是对它整个逻辑和运行原理做深入剖析,而不是简单的一个GRPC介绍。同时,以类图和运行交互图方式也方便在我们理清思路外,对它的大致细节做一个记录。特别是对于庞大的项目代码,你是不可能今日理清后面就能全面死记硬背下来。利用这个图,当过了许久需要来深究或修改其中某个细节的时候,可帮助我们一眼快速深入。

1 服务端

1.1 逻辑架构

1.1.1 概述

  • 总体架构
    GRPC Java源码解析_第2张图片

服务管理(Server Pack)负责逻辑服务ServerImpl和监听服务NettyServer的构建与启动。它是整个服务端逻辑的功能工厂;
传输逻辑(Transport Pack)负责构建真正的底层IO数据传输和相应的事件监听器;
网络流(Stream Pack)是对方法调用的网络会话的封装,即一次方法调用为一个流;
方法调用(Call Pack)为真正的方法调用逻辑,最终它会调用到我们实现的服务接口对应的方法;
服务注册(Registry Pack)负责对服务描述、接口描述等信息进行注册,以供方法调用模块查询;

  • 核心逻辑

服务端整个宏观逻辑图如下。看到这张图,有人可能会觉得太复杂,但是这只是其核心类,还有大部分类没有画出。这时,有人就会觉得GRPC是不是太过度设计了,其实不然,一个好的代码必定有其精致的概念提取和逻辑抽象去隔离问题、降低复杂度,这也是其保持系统稳定与可扩展的关键,同时也是面向架构编程与面向功能编程的区别。概念创造也是架构设计的灵魂和创新的关键。
注1图中以聚合具体的类来展示整个宏观逻辑关系,但是在实现中很多都是聚合抽象的接口,在后面的各个子模块解读过程中会详细讲解。
注2图中类名带$表示后者是前者的内部类;{ }表示前面的类的某个方法中有一个匿名的类,这个匿名类的基类名称或接口名称是{}中的名称。
GRPC Java源码解析_第3张图片
整个服务端架构分为六大模块。服务管理(Server Pack)负责逻辑服务ServerImpl和监听服务NettyServer的构建与启动;服务注册(Registry Pack)负责对服务描述、接口描述等信息进行注册,以供方法调用模块查询;传输逻辑(Transport Pack)负责构建真正的底层IO数据传输和相应的事件监听器;网络处理(Handler Pack(io.grpc.netty))是基于Netty提供的异步回调机制实现IO事件处理器,最终会交给Transport Pack中的事件监听器或Stream Pack处理;网络流(Stream Pack)是对方法调用的网络会话的封装,即一次方法调用为一个流;方法调用(Call Pack)为真正的方法调用逻辑,最终它会调用到我们实现的服务接口对应的方法。

1.1.2 服务管理(Server Pack)

服务管理模块分为逻辑服务器构造器(ServerBuilder)、逻辑服务器(Server)、监听服务器(InternelServer)。ServerBuilder是Server和InternelServer的构建工厂;Server是真正的逻辑服务器,一个GRPC进程可以同时监听多个地址,每个监听地址对应一个InternelServer,所以可以把Server看成是InternelServer的管理器;InternelServer为真正的服务器,一个InternelServer只能处理一个地址。
GRPC Java源码解析_第4张图片
此外,Server的实现类ServerImpl还持有一个接口HandlerRegistry的实例(即InternelHandlerRegistry)去管理服务注册信息;ServerImpl会构建ServerTransportListenerImpl传递给InternelServer,InternelServer的实现类NettyServer在构建NettyServerTransport时就会将之设置为NettyServerTransport的监听器。

1.1.3 服务注册(Registry Pack)

服务注册包括注册管理器(HandlerRegistry)、服务定义(ServerServiceDefinition)、方法定义(ServerMethodDefinition)、服务描述(ServiceDescriptor)和方法描述(MethodDescriptor)。一个HandlerRegistry实现类InternalHandlerRegistry聚合了多个ServerServiceDefinition和ServerMethodDefinition;一个ServerServiceDefinition也聚合了多个ServerMethodDefinition和一个ServiceDescriptor;ServerMethodDefinition聚合了一个ServerCallHandler(其实现为UnaryServerCallHandler)实例和一个MethodDescriptor;一个ServiceDescriptor聚合多个MethodDescriptor;最后每个MethodDescriptor都有一个枚举属性MethodType。
GRPC Java源码解析_第5张图片

1.1.4 传输逻辑(Transport Pack)

传输逻辑相对简单,它分为传输通道(ServerTransport)和通道监听器(ServerTransportListener)。ServerTransport其实现类为NettyServerTransport,它与ServerTransportListener的实现类ServerTransportListenerImpl相互关联,ServerTransportListenerImpl是ServerImpl的内部类
GRPC Java源码解析_第6张图片

1.1.5 网络处理(Handler Pack(io.grpc.netty))

网络处理是Netty的channel处理逻辑。总共有四个Handler,WriteBufferingAndExceptionHandler,WaitUntilActiveHandler、GrpcNegotiationHandler和NettyServerHandler。他们依次聚合,都聚合抽象的接口类型;其中WaitUntilActiveHandler和GrpcNegotiationHandler由PlaintextProtocolNegotiator构建。NettyServerHandler在收到Header的时候会构建NettyServerStream调用ServerTransportListener(实现为ServerTransportListenerImpl)接口的streamCreated方法。
GRPC Java源码解析_第7张图片

1.1.5.1 netty领域

在网络处理中关于netty库中的接口和抽象类的关系如下:
GRPC Java源码解析_第8张图片

1.1.6 网络流(Stream Pack)

网络流分为流对象(Stream)和流监听器(StreamListener)两块。Stream这边是实现类为NettyStream,它会聚合抽象的StreamListener(其实现为JumpToApplicationThreadServerStreamListener);StreamListener的实现类分为JumpToApplicationThreadServerStreamListener(它为ServerImpl的内部类)和ServerStreamListennerImpl(它为ServerCallImpl的内部类),前者聚合后者的接口类型;ServerStreamListennerImpl又会聚合一个ServerCallImpl和一个ServerCall的内部接口Listener(其实现为UnaryServerCallListener,该类为调用真正的接口实现方法的包装器或者说入口)。
GRPC Java源码解析_第9张图片

1.1.7 方法调用(Call Pack)

方法调用模块分为调用处理器(ServerCallHandler)、方法接口(UnaryRequestMethod)、调用监听器(Listener)、响应处理(StreamObserver)和调用对象(ServerCall)。ServerCallHandler有两个子类,InterceptCallHandler和UnaryServerCallHandler,前者服务业务实现者按需设置的方法调用拦截器,用作方法调用的预处理,后者是真正的方法调用的执行者,它会调用Protobuf桩代码中的接口实现方法;UnaryRequestMethod是提供给Protobuf桩代码实现方法调用的接口;Listener(它的实现类为UnaryServerCallHandler的内部类)主要负责监听方法调用执行响应的处理;StreamObserver提供给服务业务实现者的接口,让其可以返回方法的返回值;ServerCall是方法调用的边界,负责与stream交互数据流,查询方法注册信息等。
GRPC Java源码解析_第10张图片

1.2 运行时

1.2.1 服务构建

服务构建功能主要由NettyServerBuilder、InternalHandlerRegistry、InternalHandlerRegistry $ Builder、ServerImpl四个类协同完成。当然,还有众多其他的细节,请参照源码阅读。
GRPC Java源码解析_第11张图片

1.2.2 服务启动

服务启动流程:

  • 首先由ServerImpl的start方法去构建一个ServerImpl $ ServerListenerImpl。然后将ServerImpl $ ServerListenerImpl作为参数去调用每个监听InternalServer的start函数;
  • 在NettyServer的start函数中会构建ServerBootstrap(该类为netty的服务启动工厂),然后再构建一个局部的ChannelInitializer作为服务连接处理器;
  • 当有连接到来时,会异步调用局部ChannelInitializer的initChannel方法。
    GRPC Java源码解析_第12张图片

1.2.3 连接处理

连接处理流程:

  • 首先,构建NettyServerTransport,将其作为参数调用ServerImpl $ ServerListenerImpl的transportCreated方法。在transportCreated中会构建ServerImpl $ ServerTransportListenerImpl,返回抽象的ServerTransportListener。然后,将抽象的ServerTransportListener作为参数去调用新构建的NettyServerTransport的start方法;
  • 在NettyServerTransport的start方法中,它会构建一个NettyServerHandler,将之作为参数调用ProtocolNegotiator的newHandler方法,然后再将返回negotiationHandler作为委托参数构建一个WriteBufferingAndExceptionHandler,并将WriteBufferingAndExceptionHandler设置为这个连接channel的Netty处理器;
  • 最后,当有HTTP2.0头送达的时候,最终会异步调用NettyServerHandler的OnHeadersRead方法;同理,当有HTTP2.0帧送达的时候,会异步调用NettyServerHandler的OnDataRead方法。
    GRPC Java源码解析_第13张图片
1.2.3.1 接收消息头

当接收到消息头的时候,会new一个NettyServerStream类型的stream对象,再将stream作为参数调用ServerImpl $ ServerTransportListenerImpl的streamCreated方法。在streamCreated方法中会去构建一个ServerImpl $ JumpToApplicationThreadServerStreamListener类型的jumpListener对象,并将之作为参数调用stream对象的setListener方法。最后再构建一个ServerImpl $ StreamCreated,并异步调用而最终执行到runInternal方法,在runInternal中会执行系列关于调用对象和监听器的构建和关联的功能。

GRPC Java源码解析_第14张图片

1.2.3.2 消息体处理
  • 当接收到消息体的时候,会调用该stream的NettyServerStream $ TransportState对象的inboundDataRecved方法。在该inboundDataRecved中首先会执行deframe去处理数据,然后当endOfStream参数为true的时候表示是最后一帧,那么就会调用closeDeframe方法。
  • 当调用closeDeframe,最终会触发帧关闭事件,于是会调用NettyServerStream $ TransportState的deframerClosed方法,然后依次调用ServerImpl $ JumpToApplicationThreadServerStreamListener和ServerCalls $ UnaryServerCallHandler $ UnaryServerCallListener的halfclose方法(halfclose为半关闭的意思,即只关闭接收,发送数据还开着,待方法调用返回后送给客户端)。最终,会执行Protobuf中调用方法的桩代码实现。
  • 此外,最后当网络数据返回后会触发帧完成事件,最终会关闭整个流。
    GRPC Java源码解析_第15张图片

1.3 代码解读

前面的逻辑图和运行时还算比较详细,关键代码的解读后面抽时间再补充。

2 客户端

由于时间关系,对于客户端部分,我将整理出来的逻辑图贴出来,就不作文字解读了。有兴趣的朋友可以结合源码查看。

2.1 逻辑架构

2.1.1 概述

  • 总体架构
    GRPC Java源码解析_第16张图片

逻辑传输(Transport Pack)是数据传输的功能单元。分为Pending和Real两个层次,Real的Transport真正与netty进行交互,进行数据传输。Pending的Transport是对调用流的缓存;
网络流(Stream Pack)是对方法调用过程的网络会话的封装,分为逻辑流PendingStream和真实的流NettyClientStream。PendingStream会先缓存业务对流的操作,待逻辑传输准备好后再执行;
方法调用(Call Pack)模块是客户端调用远程服务的逻辑包装或者说功能入口;
上层通道(Channel Pack)是客户端与服务端的连接逻辑通道,不涉及具体的数据传输;

  • 核心逻辑

整个GRPC客户端部分的宏观逻辑如下图。共分为七大逻辑模块。上层通道(Channel Pack)是客户端与服务端的连接逻辑通道,不涉及具体的数据传输;拦截器(Interceptor Pack)负责上层通道添加创建真实客户端调用远程服务的逻辑包装或者说功能入口;逻辑传输(Transport Pack)是数据传输的功能单元,真正与netty进行交互,进行数据传输;网络通道(SubChannel Pack)是真正的网络通道而非逻辑通道,负责创建逻辑传输层,处理逻辑传输事件;网络流(Stream Pack)是对方法调用过程的网络会话的封装,分为逻辑流PendingStream和真实的流NettyClientStream(PendingStream会先缓存业务对流的操作,待逻辑传输准备好后再执行);网络处理(Handler Pack(io.grpc.netty))是基于Netty提供的异步回调机制实现IO事件处理器,最终会交给Transport Pack中的事件监听器或Stream Pack处理;方法调用(Call Pack)是对客户端端方法调用的封装对象。
GRPC Java源码解析_第17张图片

2.1.2 上层通道(Channel Pack)

GRPC Java源码解析_第18张图片

2.1.3 拦截器(Interceptor Pack)

GRPC Java源码解析_第19张图片

2.1.4 方法调用(Call Pack)

GRPC Java源码解析_第20张图片

2.1.5 逻辑传输层(Transport Pack)

GRPC Java源码解析_第21张图片

2.1.6 网络流(Steam Pack)

GRPC Java源码解析_第22张图片

2.1.7 网络通道(SubChannel Pack)

GRPC Java源码解析_第23张图片

2.1.8 网络处理(Handler Pack(io.grpc.netty))

GRPC Java源码解析_第24张图片

2.2 运行时

2.2.1 通道构建

GRPC Java源码解析_第25张图片

2.2.2 发起调用

GRPC Java源码解析_第26张图片

2.2.3 启动DNS

GRPC Java源码解析_第27张图片

2.2.4 域名解析完成

GRPC Java源码解析_第28张图片

2.2.5 构建真实流

GRPC Java源码解析_第29张图片

2.3 代码解读

3 心得总结

3.1 分析过程

分析源码和替换底层逻辑,心得总结如下:

3.1.1 先划分合理的边界

3.1.2 以更高的视角阅读源码

3.1.3 分析推导很重要

你可能感兴趣的:(技术大杂烩)