Dubbo原理解析,彻底搞懂dubbo (下)

      最后更新:2022-07-29 05:26:14 手机定位技术交流文章

      服务发现

      服务发现流程

      全局服务消费原则

      Dubbo框架还将服务消费分为两个主要类别, 第一步通过持有远程服务实例生成Invoker,这个Invoker 在客户端是核心的远程代理对象 。第二步是通过动态代理转换调用器到实现用户界面的动态代理参考。

      服务消费者引用的服务蓝初始化链,时间序列

      源码分析应用

      引用入口:ReferenceBean 的getObject 方法,该方法定义在Spring 的FactoryBean 接口中,ReferenceBean 实现了这个方法。

      Dubbo提供了一个丰富的配置,用于调整和优化帧行为、性能等。 Dubbo首先检查和处理这些配置,在引用或进口服务时,以确保它们是正确的。

      此方法代码很长,主要完成配置装载,检查,并创建引用代理对象。这从 createProxy 开始。从字面意思上来看,createProxy似乎只用于创建代理对象。但实际上并非如此,该方法还调用其他方法来构建和合并调用器实例。具体细节如下。

      折叠

      上面有很多代码,但逻辑更清晰。
      如果调用是本地,则jvm协议将实例直接从内存中获取
      2、如果只有一个注册中心,直接通过Protocol 自适应拓展类构建Invoker 实例接口
      3、如果有多个注册中心,此时先根据url 构建Invoker。然后再通过Cluster 合并多个Invoker,最后调用ProxyFactory 生成代理类

      创建客户端

      在服务消费方,Invoker 用于执行远程调用。Invoker 是由Protocol 实现类构建而来。Protocol 实现类有很多,这里分析DubboProtocol

      上述方法看起来更简单,创建一个 DubboInvoker。将远程调用的客户端对象通过构造方法传递.默认情况下,Dubbo使用NettyClient来沟通。接下来,让我们看看 getClients方法的逻辑。

      这里根据connections 数量决定是获取共享客户端还是创建新的客户端实例,getSharedClient 方法中也会调用initClient 方法,因此下面我们一起看一下这个方法。

      initClient 方法首先获取用户配置的客户端类型,默认为netty。下面我们分析一下Exchangers 的connect 方法。

      如上,getExchanger 会通过SPI 加载HeaderExchangeClient 实例,这个方法比较简单,大家自己看一下吧。接下来分析HeaderExchangeClient 的实现。

      这里的调用比较多,我们这里重点看一下Transporters 的connect 方法。如下:

      如上,getTransporter方法返回自适应扩展类,该类将根据运行时的客户端类型加载指定的Transporter实现类。如果用户没有配置客户端类型,默认情况下,NettyTransporter是加载的,调用该类的连接方法。如下:

      注册

      这里就已经创建好了NettyClient对象。关于DubboProtocol 的refer 方法就分析完了。接下来,继续分析RegistryProtocol 的refer 方法逻辑。

      上面代码首先为url 设置协议头,然后根据url 参数加载注册中心实例。然后获取group 配置,根据group 配置决定doRefer 第一个参数的类型。这里的重点是doRefer 方法,如下:

      如上,doRefer 方法创建一个RegistryDirectory 实例,然后生成服务商-消费者链接,并向登记中心登记。注册完毕后,紧接着订阅providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于服务可以部署在多个服务器上,这在提供者中生成多个节点,此时,Cluster需要将多个服务节点合并为一个,并生成一个调用器。

      创建代理对象

      Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为ProxyFactory 的getProxy,接下来进行分析。

      如上,上面大段代码都是用来获取interfaces 数组的,我们继续往下看。getProxy(Invoker, Class<?>[]) 这个方法是一个抽象方法,让我们在下面的 JavassistProxyFactory类中查看该方法的实现代码。

      上面代码并不多,首先是通过Proxy 的getProxy 方法获取Proxy 子类,然后创建InvokerInvocationHandler对象,并将该对象传给newInstance 生成Proxy 实例。InvokerInvocationHandler实现JDK的InvocationHandler接口,具体应用是拦截接口类调用.下面是一个org.Apache.dubbo.demo。例如,DemoService接口,让我们看看接口代理类代码一般是什么样子(忽略EchoService接口)。

      好吧,这里分析了生成代理类的逻辑,整个过程相当复杂,我们需要耐心。

      总结

      1. 从登记中心找到参考服务:在登记中心,如透过登记中心找到提供者地址,ReferenceConfig分析了URL格式: registry://registryhost:/org.Apache.Registry.Registry Service?refer=URL.encode("conumerhost/com.foo.FooService?version=1.0.0") 。
      2. 将调用RegistryProtocol#refer()方法以通过URL识别Registry://协议头条
      3. 查询提供商的URL,如 dubbo://service-host/com.Foo.FooService?Version 1.0.0 获取注册中心
      4. 创建一个RegistryDirectory 实例并设置注册中心和协议
      5. 生成conusmer 连接,在consumer 目录下创建节点,向注册中心注册
      6. 注册完毕后,订阅providers,configurators,routers 等节点的数据
      7. 通过URL 的 dubbo:// 协议头识别,调用 DubboProtocol#refer() 方法,创建一个
        ExchangeClient客户端和返回DubboInvoker实例
      8. 由于服务可以部署在多个服务器上,这在提供者中生成多个节点,这样您还可以获得多个 Dubbo Invoker实例,需要RegistryProtocol调用Cluster以掩盖多个服务提供者节点,并返回一个调用器
      9. Invoker 创建完毕后,调用ProxyFactory 为服务接口生成代理对象,返回提供者引用

      网络通信

      在之前的内容中,我们分析了消费者端服务发现与提供者端服务暴露的相关内容,同时也知道消费者端通过内置的负载均衡算法获取合适的调用invoker进行远程调用。接下来我们再研究下远程调用过程即网络通信。

      网络通信位于移除模块中:
      Remoting 实现是Dubbo 协议的实现,如果你选择RMI 协议,整个Remoting 都不会用上;
      Remoting 内部再划为 Transport 传输层 和 Exchange 信息交换层 ;
      Transport 层只负责单向消息传输,是对Mina, Netty, Grizzly 的抽象,它也可以扩展UDP 传输;
      Exchange 层是在传输层之上封装了Request-Response 语义;
      网络通信的问题:
      客户服务连通问题
      粘包拆包问题
      非同步多线性数据连贯问题

      通信协议

      dubbo内置,dubbo协议 ,rmi协议,希斯协定,http协议,网络服务协议,thrift协议,rest协议,grpc协议,备存协议,10 个通信协议, 例如 redis 协议.各个协议特点如下

      dubbo协议
      Dubbo默认协议使用单长连接和非同步NIO通信,适用于大量数据的服务调用,且服务消费机的数量远远超过服务提供机的数量。
      缺失的协议,使用tbremoting相互作用的mina 1.1.7和Hessian 3.2.1。
      连接个数:单连接
      连接方式:长连接
      传输协议:TCP
      传输方法:非线性异时传输
      序列化:Hessian 二进制序列化
      范围:输入和输出参数包较小(建议少于100K),用户比提供者多,单个用户不能填补提供者,并尽可能不使用 dubbo协议传输大文件或超大字符串。
      适用场景:传统远程服务方法调用
      rmi协议
      RMI协议使用JDK标准 java.rmi。*使用阻塞短连接和JDK标准序列方法实现。
      连接个数:多连接
      连接方式:短连接
      传输协议:TCP
      传输方式:同步传输
      序列:Java标准二进制序列
      适用范围: 输入和输出参数包大小混合,用户和供应商相似,文件可以传输。
      适用场景:传统远程服务方法调用,与原生RMI服务互操作
      hessian协议
      Hessian协议用于整合Hessian服务,Hessian子级使用HTTP通信,Servlet暴露服务,
      Dubbo默认将Jetty嵌入为服务器实现。
      Dubbo 的Hessian 协议可以和原生Hessian 服务互操作,即提供者使用Dumbo的Hessian协议曝光服务,消费者直接使用标准 Hessian接口电话或提供标准 Hessian曝光服务,消费者使用Dubbo的Hessian协议来电话。
      连接个数:多连接
      连接方式:短连接
      传输协议:HTTP
      传输方式:同步传输
      序列化:Hessian二进制序列化
      应用范围: 输入和输出参数的包是较大的, 提供者比消费者多, 提供者更受压力, 文件是可传输的.
      可适用的场景:页面转移,文件转移,或与本地语服务的交互
      http协议
      基于HTTP表格的远程调用协议,与Spring的HTTP调用器实现
      连接个数:多连接
      连接方式:短连接
      传输协议:HTTP
      传输方式:同步传输
      序列化:表单序列化
      应用范围:输入输入参数数据包大小混合,提供者比消费者多,浏览器可查看,表格或URL输入参数可用,暂时不支持传输文件。
      适用场景: 由应用程序和浏览器JS同时使用的服务。
      网络服务协议
      基于WebService 的远程调用协议,基于Apache CXF 实现]( http://dubbo.apache.org/zh-cn/docs/us
      er/references/protocol/webservice.html#fn2)。
      可以与原来的WebService服务进行交互,例如,提供商使用Dubbo的WebService协议来披露服务,消费者直接使用标准的Web服务接口电话,或为客户提供标准网页服务曝光服务,用户使用Dubbo的WebService协议调用。
      连接个数:多连接
      连接方式:短连接
      传输协议:HTTP
      传输方式:同步传输
      序列: SOAP文本序列(http + xml)
      可适用的场景:系统集成,多语调用
      thrift协议
      当前dubbo 支持[1]的thrift 协议是对thrift 原生协议[2] 的扩展,在原生协议的基础上添加了一些额外的头信息,比如service name,magic number 等。
      rest协议
      基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持
      grpc协议
      2.7版本的Dubbo开始支持gRPC协议,对于HTTP/2通信的计划使用,或者想利用gRPC 带来的Stream、反压、Reactive 编程等能力的开发者来说,你可以考虑启用gRPC协议。

      为期望使用gRPC协议的用户提供服务管理能力,并为方便访问杜博系统,用户可以使用杜博式、基于接口的编程风格来定义和使用远程服务
      备存协议
      基于Memcached的RPC协议
      redis协议
      基于Redis的RPC协议

      序列化

      序列是将对象转换为节点流,用于网络传输,以及将节点流转换为对象,用于接收字节流数据后恢复对象。序列有许多优点,例如,更好的安全、跨平台等。我们知道 dubbo是基于网络通信的,在NettyClient上,doOpen()方法允许您查看Netty的相关类

      然后查看内特尔库达帕特类的 encodeRequest 方法,最后输入 ExchangeCodec类,如下:

      是的,就是Serialization接口,默认是Hessian2Serialization序列化接口。

      Dubbo序列支持java,compactedjava,nativejava,fastjson,dubbo,fst,hessian2,kryo,Protostuff 默认为 tohessian2.其中java、compactedjava、nativejava属于原生java的序列化。

      dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它。
      Hessian2序列: Hessian是一个多语言高效的二进制序列方法,但这不是原来的Hessian2序列,而是Ali修改的序列,这是 dubbo RPC的默认启用序列方法。
      json序列:目前有两个实现,一个是阿里的fastjson图书馆,另一个方法是使用简单的json库,你可以在 dubbo中实现,但它的实现并不特别成熟,此外,Json的文本序列性能一般比上述两个二进制序列低。
      Java序列:Java序列的实现主要是基于JDK的自驱动,并且性能并不理想。

      网络通信

      Dubbo数据格式

      通常有三个方法来解决数据包在接口中卸载的问题
      长度协议(数据包长度一致性)
      长期协议意味着协议内容的长度是固定的,例如,协议字节长度是50,当你从网络上读出50字节时,解码解码操作.当长度协议读写时,效率比较高,因为数据缓存的大小基本上是决定的,就好比数组一样,缺陷是适应能力不足,以RPC场景为例,很难估计一个固定长度有多长。
      特殊终止器(数据尾:以特殊字符标识#)
      相比定长协议,如果可以定义一个特殊字符作为每个协议单元末端的指示,能够以更长的方式交流,从而平衡数据传输和效率,例如,使用特殊字符n。特殊端点方法的问题是,对于协议传输的进程是太简单了。要处理协议单元,它必须完全读入。此外, 必须 防止 用户 所 发送 的 数据 与 终点 相同,否则就会出现紊乱。
      变量协议(协议头+装载模式)
      这种一般是自定义协议,会以定长加不定长的部分组成,其中定长的部分需要描述不定长的内容长度。
      dubbo是一个使用这个格式的数据传输格式

      Dubbo包分为消息头条和消息体,消息头被用来存储一些元数据,例如,魔术,数据包类型(Request/Response),消息体长度(Data Length)等。消息体用于存储特定调用消息,比如方法名称,参数列表等。下面是标题内容的简要列表。

      偏移量(Bit) 字段 取值

      0~7等离子高点0xda00

      8~15等离子低0xbb

      16 数据包类型 0 - Response, 1 - Request

      17 调用模式只有效,如果第16位设置为1,0 - 单程调用,1 - 双程调用

      18 事件标识符 0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包

      19~23 Serializer No. 2 - Hessian2Serialization
      3 - JavaSerialization
      4 - CompactedJavaSerialization
      6 - FastJsonSerialization
      7 - NativeJavaSerialization
      8 - KryoSerialization
      9 - FstSerialization

      24 ~ 31 状态 20 - OK 30 - CLIENT_TIMEOUT 31 - SERVER_TIMEOUT 40 - BAD_REQUEST 50 - BAD_RESPONSE..

      32~95号请求总共8字节在运行时生成

      96~127消息体长运行时间计算

      消费端发送请求

      dubbo用户自动生成代码对象如下:

      在InvokerInvocationHandler中的Invoker成员变量类型叫做MockClusterInvoker,而服务降级逻辑则包含在MockClusterInvoker内。

      鉴于前面的节详细分析了FailoverClusterInvoker,本节简要地回顾FailoverClusterInvoker并直接分析 DubboInvoker。

      上面的代码来自AbstractInvoker 类,大多数代码用于将信息添加到 RpcInvocation#attachment变量中,添加完毕后,Invoke doInvoke执行后续调用。doInvoke是一个抽象的方法,需要由子类实现,下面看看 DubboInvoker.

      最后, 进入 HeaderExchangeChannel#request 方法, 拼写 Request 并发送请求

      请求编码如何做的?

      在网格的开头,我们设置了一个解码器,其中解码工作可以通过 ExchangeCodec进行如下:

      折叠

      以上是请求对象的编码过程,该过程首先通过一个 bit 操作将消息标题写入标题阵列中。然后对Request 对象的data 字段执行序列化操作,在序列化后, 数据最终存储在 ChannelBuffer中.序列操作完成后,数据序列后可获得长度镜头,接下来, 将镜头写到指定的标题位置.最后将消息标题序列组标题写入ChannelBuffer中,整个编码过程结束。本节的最后,我们再来看一下Request 对象的data 字段序列化过程,也就是encodeRequestData 方法的逻辑,如下:

      至此,关于服务消费方发送请求的过程就分析完了,接下来我们来看一下服务提供方是如何接收请求的。

      提供方接受请求

      请求如何解码?

      这里我们直接分析了请求数据的解码逻辑,忽略了中间过程,如下:

      折叠

      通过检测消息标题中的魔法数是否等于指定的魔法数来实现上述方法,预截断非标准包,例如,数据包通过一个电话网命令行发送。然后看看消息体长,和检测可读节点的数目。最后,为后续的解码工作调用解码Body方法,decodeBody方法在 ExchangeCodec中实现,但是由于它的子类 DubboCodec重写了方法,因此,DubboCodec中的decodeBody方法将在运行时调用。让我们看下面的方法的代码。

      折叠

      如上,decodeBody来理解一些字段的代码,解码的字段将被封入请求中。然后将调用DecodeableRpcInvocation的解码方法进行后续解码。此工作完成后,可将调用方法名、attachment、以及调用参数解析出来。

      调用服务

      解码器将数据包解析成Request 对象后,NettyHandler 的messageReceived 方法紧接着会收到这个对象,并将这个对象继续向下传递。整个调用栈如下:

      这里我们直接分析了调用堆中的第一个和最后的调用方法逻辑,如下:
      考虑到篇幅,以及很多中间调用的逻辑并非十分重要,所以这里就不对调用栈中的每个方法都进行分析了。这里我们直接分析最后一个调用方法逻辑。如下:

      如上,请求和响应消息的频率明显高于其他类型的消息。所以这里是针对这种类型的信息的判断 ChannelEventRunnable只是一个继电器站,它的运行方法没有特定的调用逻辑,仅用于传递参数给其他 ChannelHandler 对象进行处理,对象类型为DecodeHandler

      DecodeHandler 主要是包含了一些解码逻辑,完全解码后的Request 对象会继续向后传递

      折叠

      如上课程所述,完成后,所有服务暴露都保存到exporterMap.这里就是通过serviceKey获取exporter之后获取Invoker,并通过Invoker 的invoke 方法调用服务逻辑

      如上,doInvoke是一个抽象的方法,这个需要由具体的Invoker 实例实现。Invoker 实例是在运行时通过JavassistProxyFactory 创建的,创建逻辑如下:

      编写者是一个抽象的类,InvokeMethod是一种抽象的方法。Dubbo在运行时通过Javaassist框架生成了Wrapper的实现类,并实现invokeMethod 方法,该方法最终将根据电话信息调用特定服务。以DemoServiceImpl为例,Javassist是生成的代理类。

      在此,分析了整个服务调用过程。 最后,将调用过程粘贴如下:

      提供方返回调用结果

      服务提供者要求指定的服务后,调用结果将被装入响应对象中,并把物品归还给服务消费者。服务提供方也是通过NettyChannel 的send 方法将Response 对象返回,这不是一次重复的分析。

      消费方接收调用结果

      收到答复数据后,服务消费者,首先是解码响应数据,接收响应对象。然后将对象传递到下一个站内处理器中,输入处理器是NettyHandler。接下来,NettyHandler将继续将这个对象递给下,最后, AllChannelHandler的接收方法将接收这个对象,将此对象发送到线程池中。这个过程与服务提供者收到请求的过程相同,因此,分析没有重复

      小结

      在这个阶段,整个杜博核心过程原理及其源代码,我们已经完成分析,整个过程思考并不复杂,但有许多细节,首先了解它的思想,或者多长时间仔细地回顾它。

      本文由 在线网速测试 整理编辑,转载请注明出处,原文链接:https://www.wangsu123.cn/news/30724.html

          热门文章

          文章分类