1.Dubbo源码解析:网络通信
2.Java教程:dubbo源码解析-网络通信
3.TPRC-cpp 发送包流程剖析
4.开源RPC项目Apache Thrift
Dubbo源码解析:网络通信
<dubbo源码解析:深入理解网络通信
在之前的议源章节中,我们已经了解了消费者如何通过服务发现和负载均衡机制找到提供者并进行远程调用。议源本章将重点解析网络通信的议源实现细节。
网络通信主要在Dubbo的议源Remoting模块中进行,涉及多种通信协议,议源包括dubbo协议、议源studio小项目源码RMI、议源Hessian、议源HTTP、议源WebService、议源Thrift、议源REST、议源gRPC、议源Memcached和Redis等。议源每个协议都有其特定的议源优缺点,如Dubbo协议适用于高并发场景,而RMI则使用标准JDK序列化。
Dubbo的序列化机制支持多种方式,如Hessian2、Kryo、FST等。近年来,高效序列化技术如Kryo和FST的出现,可提升性能,只需在配置中简单添加即可优化。
关于数据格式和粘包拆包问题,Dubbo采用私有RPC协议,消息头存储元信息,源码投资上海如魔法数和数据类型,消息体则包含调用信息。消费者发送请求时,会通过MockClusterInvoker封装服务降级逻辑,然后通过序列化转换为网络可传输的数据格式。
服务提供方接收请求时,首先对数据包进行解码,确认其格式正确性,然后调用服务逻辑。提供方返回调用结果时,同样经过序列化和编码,最后通过NettyChannel发送给消费者。
在心跳检测方面,Dubbo采用双向心跳机制,客户端和服务端定期发送心跳请求以维持连接。此外,还通过定时任务处理重连和断连,确保连接的稳定性和可靠性。
总的来说,Dubbo的网络通信模块精细且灵活,通过多种协议和优化技术确保服务调用的高效和可靠性。
Java教程:dubbo源码解析-网络通信
在之前的内容中,我们探讨了消费者端服务发现与提供者端服务暴露的相关内容,同时了解到消费者端通过内置的负载均衡算法获取合适的调用invoker进行远程调用。接下来,我们聚焦于远程调用过程,即网络通信的挂机帮源码细节。
网络通信位于Remoting模块中,支持多种通信协议,包括但不限于:dubbo协议、rmi协议、hessian协议、ty进行网络通讯,NettyClient.doOpen()方法中可以看到Netty的相关类。序列化接口包括但不限于:Serialization接口、Hessian2Serialization接口、Kryo接口、FST接口等。
序列化方式如Kryo和FST,性能往往优于hessian2,能够显著提高序列化性能。这些高效Java序列化方式的引入,可以优化Dubbo的序列化过程。
在配置Dubbo RPC时,引入Kryo和FST非常简单,只需在RPC的XML配置中添加相应的属性即可。
关于服务消费方发送请求,Dubbo框架定义了私有的RPC协议,消息头和消息体分别用于存储元信息和具体调用消息。消息头包括魔数、数据包类型、消息体长度等。消息体包含调用消息,如方法名称、vb源码绘图参数列表等。请求编码和解码过程涉及编解码器的使用,编码过程包括消息头的写入、序列化数据的存储以及长度的写入。解码过程则涉及消息头的读取、序列化数据的解析以及调用方法名、参数等信息的提取。
提供方接收请求后,服务调用过程包含请求解码、调用服务以及返回结果。解码过程在NettyHandler中完成,通过ChannelEventRunnable和DecodeHandler进一步处理请求。服务调用完成后,通过Invoker的invoke方法调用服务逻辑。响应数据的编码与请求数据编码过程类似,涉及数据包的构造与发送。
服务消费方接收调用结果后,首先进行响应数据解码,获得Response对象,并传递给下一个处理器NettyHandler。处理后,响应数据被派发到线程池中,此过程与服务提供方接收请求的过程类似。
在异步通信场景中,Dubbo在通信层面为异步操作,通信线程不会等待结果返回。默认情况下,凹音源码RPC调用被视为同步操作。Dubbo通过CompletableFuture实现了异步转同步操作,通过设置异步返回结果并使用CompletableFuture的get()方法等待完成。
对于异步多线程数据一致性问题,Dubbo使用编号将响应对象与Future对象关联,确保每个响应对象被正确传递到相应的Future对象。通过在创建Future时传入Request对象,可以获取调用编号并建立映射关系。线程池中的线程根据Response对象中的调用编号找到对应的Future对象,将响应结果设置到Future对象中,供用户线程获取。
为了检测Client端与Server端的连通性,Dubbo采用双向心跳机制。HeaderExchangeClient初始化时,开启两个定时任务:发送心跳请求和处理重连与断连。心跳检测定时任务HeartbeatTimerTask确保连接空闲时向对端发送心跳包,而ReconnectTimerTask则负责检测连接状态,当判定为超时后,客户端选择重连,服务端采取断开连接的措施。
TPRC-cpp 发送包流程剖析
TRPC官网对客户端发送包的流程有简要描述,但细节不够清晰。以下将通过分析Hello World示例中的源代码,来详细解析发送流程,不做深入分析,主要目的是梳理流程。
一、官网描述
二、源代码追踪
客户端代码位于TRPC-CPP/examples/helloworld/test/ fiber_client.cc文件中,核心代码如下:
1、解析文件获取配置信息
对应函数为ParseClientConfig,配置信息主要分为三种:
2、RunInTrpcRuntime
主要完成的是日志初始化以及执行FiberRuntime程序,对应代码如下:
3、RunInFiberRuntime
主要做的工作就是初始化框架运行环境InitFrameworkRuntime以及开启协程进行插件初始化以及传入的Run函数执行。
InitFrameworkRuntime主要代码如下,主要完成的工作是设定运行环境类型(使用Fiber)、内存池创建、时间轮创建并开启、初始化Fiber环境(fiber调度组的初始化并启动协程模型)、配置调度组对应的Reactor。
RegisterPlugins主要代码如下,主要完成的就是Naming、Tracing、Loging、Codec等等插件的注册、初始化以及开启运行。
4、请求构建与发送
status = func();调用main函数中传入的Run函数,请求构建与发送,代码如下:
核心在于调用proxy->SayHello进行请求的发送,那么我们就再深入一层
原来是调用了UnaryInvoke函数,那么continue
这部分代码是在ServiceProxy中,也就对应的官网的流程,这部分主要将请求写入上下文对象中,并且运行过滤器(这里其实就是文档中所说的过滤器埋点,究其原理其实就是运行已注册再当前埋点的函数),然后调用了UnaryInvokeImp,继续追踪~~~
这部分代码将所使用的协议写入了上下文对象,然后又调用了ServiceProxy::UnaryInvoke,Come on !
同样此函数首先执行了过滤器(埋点是CLIENT_PRE_SEND_MSG),然后又调用了UnaryTransportInvoke,在已知传输数据、传输协议的情况下,进行下一步^_^
使用之前已经注册的codec编码器对请求内容进行编码与请求头封装,进入codec_->ZeroCopyEncode,编码完成后使用transport对象进行发送与接收transport_->SendRecv
通过目标IP地址以及端口寻找到对应的FiberConnectorGroup组,通过调用组的SendRecv,进行发送
获取connection对象(可深入追踪,分为短链接和长连接连接池复用),获取成功调用SendReqMsg进行发送
首先进行了用户过滤器判定,有的话进行用户过滤器调用,后面进行IoMessage信息封装,调用Send进行信息发送
状态判定居多,核心在于FlushWritingBuffer函数
同样进行了处理,核心在于FlushTo
其他复杂的处理暂时不关注,这里发送的核心函数是io->Writev
到此,调用系统调用将信息写入Fd,即发出完成。
整个过程确实比较长,一层有一层的嵌套封装,进而实现解耦,这里其实并不仅仅是发送,也有接收,最开始的UnaryInvoke>(context, request, response);函数已经将response以指针的形式传递进取,后续发送数据并收到对方发来的数据是,进行层层赋值,最终得到了我们接收到的返回信息。
发送信息层层函数递进,接收信息层层函数退出。
大概就是这样,下面去看下tcp连接池的设计~~~~。
开源RPC项目Apache Thrift
Apache Thrift是一个用于开发跨平台、跨语言服务的软件框架。它提供了一个代码生成引擎,构建的服务可在多种语言间无缝高效运行,支持如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml, 和 Delphi等语言。Thrift的精髓在于其代码生成能力,使得服务开发完成后,可自动转换生成对应语言的源代码,便于多种语言间的调用。
安装和使用Thrift非常简单,对于使用Mac系统的用户,可以通过命令行使用`brew install thrift`完成安装。创建Thrift文件是使用Thrift的基本方式,定义服务接口和数据类型。执行命令后,Thrift生成的源代码能够被多种语言的客户端和服务器直接使用。例如,生成的Java代码中,一个简单的Thrift文件可以自动转换为包含数百行代码的类文件,如`UserProfile.java`,包含UserProfile结构的完整实现。
Thrift提供了丰富的序列化和反序列化功能,这在RPC(远程过程调用)和网络通信中尤为重要。Thrift定义了一套自定义的协议和结构,以支持跨语言服务的通信。这些结构和协议的生成是基于语言无关的设计,确保了Thrift的灵活性和兼容性。Thrift的服务接口由TBase继承,提供基础方法,TStruct对应结构体,TField用于描述字段,而TTransport和TProtocol则分别负责处理输入输出和协议处理。
Thrift中的序列化实现是其关键特性之一,通过TProtocol类及其子类,实现了对Thrift类型和Java类型的序列化和反序列化。这使得Thrift能够跨语言传输数据,无需考虑底层数据格式的差异。在Thrift中,序列化和反序列化过程由Scheme接口及其实现(如StandardScheme和TupleScheme)来负责。SchemeFactory接口则用于获取适当的序列化方案。
Thrift的使用不局限于Java语言,Python、C#等语言同样支持Thrift服务的开发和调用。以Python为例,Thrift生成的代码需要依赖第三方包,但Thrift的通用接口(如TBase)确保了与语言无关的交互方式。Thrift的Schema接口定义了序列化和反序列化的基本逻辑,通过不同实现(如StandardScheme和TupleScheme)提供不同的优化策略,如在读取时先确定字段列表以减少读取字节数。
Thrift在实际应用中,如Apache Hive的MetaStore和Server2服务中得到了广泛使用。在Hive中,Thrift接口通过特定的实现(如ThriftBinaryCLIService)来支持服务调用。通过Thrift接口,Hive能够提供对外的REST服务或RPC服务,使外部应用程序能够通过标准协议(如HTTP或TCP)与Hive进行交互。
理解Thrift的关键在于其对代码生成的支持和对序列化、反序列化的高效处理,使得跨语言、跨平台的服务开发和调用变得简单而高效。Thrift不仅提供了强大的序列化能力,还为服务提供了一套统一的协议和结构定义,促进了不同语言服务的互操作性。