拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Dubbo 远程通信模块源码解析:dubbo-remoting 整体架构与核心接口设计

Dubbo 远程通信模块源码解析dubbo-remoting 整体架构与核心接口设计【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunterDubbo 服务治理框架大致可划分为服务通信与服务管理两大部分服务管理对应注册中心Registry服务通信则对应远程通讯模块dubbo-remoting。本文以 docs/Dubbo/remote/Dubbo远程通信模块简析.md 为主体深入解析 dubbo-remoting 模块的整体工程结构、dubbo-remoting-api 的包划分以及最外层源码中 Endpoint、Channel、ChannelHandler、Client、Server、Codec2、Dispatcher、Transporter 等核心接口的设计思想与实现细节帮助读者建立对 Dubbo 远程通讯层端 → 通道 → 客户端/服务端 → 编解码 → 调度 → 传输完整抽象链条的源码级认知。一、服务治理框架中的通信与管理从整体架构看一个服务治理框架大致分为服务通信和服务管理两部分。服务管理对应注册中心模块服务提供者 Provider 向注册中心注册服务服务消费者 Consumer 从注册中心订阅关注的服务并在服务地址变更时得到通知其详细分析可参见 docs/Dubbo/registry/Dubbo注册中心模块简析.md。而服务通信对应远程通讯模块它是 RPC 调用的底层基础——Consumer 要调用 Provider 的远程方法必须通过远程通讯实现。在 Dubbo 项目中远程通讯由 dubbo-remoting 子项目承担它提供了多种客户端和服务端通信的功能。在对 NIO 框架的选型上Dubbo 将选择权交由用户它集成了 mina、netty、grizzly 等各类 NIO 框架来搭建 NIO 服务器和客户端并且利用 Dubbo 的 SPI 扩展机制让用户可以自定义选择。dubbo-remoting 的工程结构如下图所示。从工程结构可以看到dubbo-remoting 是一个聚合模块向下拆分为多个子模块其中最核心的是定义远程通信 API 的 dubbo-remoting-api其余子模块如基于 Netty、Mina 等 NIO 框架的实现都依赖并实现该 API。dubbo-remoting-api 的项目结构如下图所示。二、dubbo-remoting-api远程通信的核心 APIdubbo-remoting-api 定义了远程通信模块最核心的 API对于它的解读可以分为如下五个部分buffer 包缓冲在 NIO 框架中是很重要的存在各个 NIO 框架都实现了自己相应的缓冲操作。这个包下包括了缓冲区的接口以及抽象类exchange 包信息交换层其中封装了请求响应模式在传输层之上重新封装了 Request-Response 语义为了满足 RPC 的需求。这一层可以认为专注在 Request 和 Response 携带的信息上是 RPC 调用的通讯基础之一telnet 包Dubbo 支持通过 telnet 命令来进行服务治理该包下封装了这些通用指令的逻辑实现transport 包网络传输层它只负责单向消息传输是对 Mina、Netty、Grizzly 的抽象它也可以扩展 UDP 传输该层同样是 RPC 调用的通讯基础之一最外层的源码也就是接下来要重点解析的部分。结合 dubbo-remoting-api 模块的外层类和包划分我们看看 Dubbo 官方整体架构图。红框标注的部分是 Dubbo 整体架构中的远程通讯架构其中 Exchange 组件和 Transport 组件在框架设计中起到了很重要的作用也是支撑 Remoting 的核心。补充关于 Exchange 组件信息交换层与 Transport 组件网络传输层的专项源码解析可参见 docs/Dubbo/remote/Exchange组件.md 与 docs/Dubbo/remote/Transport组件.md基于 Netty 的通信实现可参考 docs/Dubbo/remote/基于Netty实现远程通信.md基于 HTTP 的实现可参考 docs/Dubbo/remote/基于HTTP实现远程通信.md。三、最外层源码解析从端到传输的抽象链条3.1 Endpoint 接口一切端点的基础Dubbo 抽象出了一个端的概念也就是 Endpoint 接口。端就是一个点而点与点之间可以双向传输。在端的基础上再衍生出通道、客户端以及服务端的概念也就是下面要介绍的 Channel、Client、Server 三个接口。在传输层Client 和 Server 的区别只是语义上的区别并不区分请求和应答职责而在交换层Client 和 Server 是有方向的端点所以区分了明确的请求和应答职责。两者都具备发送的能力只是客户端和服务端所关注的事情不一样而 Endpoint 接口抽象的方法就是它们共同拥有的方法——这也就是它们都能被抽象成端的原因。/** * Endpoint. (API/SPI, Prototype, ThreadSafe) * * Endpoint 接口 */ public interface Endpoint { /** * get url. */ URL getUrl(); /** * get channel handler. * * 获得通道处理器 */ ChannelHandler getChannelHandler(); /** * get local address. */ InetSocketAddress getLocalAddress(); /** * send message. */ void send(Object message) throws RemotingException; /** * send message. * * param sent already sent to socket? */ void send(Object message, boolean sent) throws RemotingException; /** * close the channel. */ void close(); /** * Graceful close the channel. */ void close(int timeout); void startClose(); /** * is closed. */ boolean isClosed(); }从源码可以看出Endpoint 抽象了所有通信端点的共性能力获得配置地址URL、获得通道处理器、获得本地地址、发送消息、优雅关闭与关闭状态查询。其中的URL在 Dubbo 中是总线模式的载体配置信息都被放在 URL 上传递随时可以取得相关配置信息这一设计在注册中心模块中体现得尤为明显可参见 docs/Dubbo/registry/Dubbo注册中心模块简析.md。3.2 Channel 接口信息传输的载体Channel 是通道接口通道是信息传输的载体。Channel 可读可写并且可以异步读写是 client 和 server 之间的数据传输桥梁。Channel 和 client 是一对一的一个 client 对应一个 Channel而 Channel 和 server 则是多对一的一个 server 可以对应多个 Channel。/** * Channel. (API/SPI, Prototype, ThreadSafe) * * 通道接口 * 可以看到 Channel 继承了 Endpoint也就是端抽象出来的方法也同样是 channel 所需要的 */ public interface Channel extends Endpoint { /** 获得远程地址 */ InetSocketAddress getRemoteAddress(); /** 判断通道是否连接 */ boolean isConnected(); /** 判断是否有该key的值 */ boolean hasAttribute(String key); /** 获得该key对应的值 */ Object getAttribute(String key); /** 设置属性 */ void setAttribute(String key, Object value); /** 删除属性 */ void removeAttribute(String key); }Channel 在继承 Endpoint 的基础上补充了远程地址、连接状态判断以及基于 key-value 的属性存取能力hasAttribute/getAttribute/setAttribute/removeAttribute这些属性机制为上层传递通道级上下文信息提供了便利。3.3 ChannelHandler 接口通道逻辑处理器/** * ChannelHandler. (API, Prototype, ThreadSafe) * * 通道处理器接口 * 该接口负责Channel中的逻辑处理可以看到这个接口有SPI注解是个可扩展接口 */ SPI public interface ChannelHandler { /** 连接该通道 */ void connected(Channel channel) throws RemotingException; /** 断开该通道 */ void disconnected(Channel channel) throws RemotingException; /** 发送给这个通道消息 */ void sent(Channel channel, Object message) throws RemotingException; /** 从这个通道内接收消息 */ void received(Channel channel, Object message) throws RemotingException; /** 从这个通道内捕获异常 */ void caught(Channel channel, Throwable exception) throws RemotingException; }ChannelHandler 负责 Channel 中的逻辑处理定义了通道生命周期中的五个核心事件回调连接connected、断开disconnected、发送sent、接收received和异常捕获caught。值得注意的是该接口带有SPI注解是一个可扩展接口——关于 Dubbo SPI 扩展机制的详细原理ExtensionLoader、Adaptive 自动适配等可参见 docs/Dubbo/SPI/Dubbo与Java的SPI机制.md。3.4 Client 与 Resetable 接口客户端的语义/** * Remoting Client. (API/SPI, Prototype, ThreadSafe) * * 客户端接口可以看到它继承了 Endpoint、Channel 和 Resetable接口继承Endpoint的原因上面已经提到过了 * 客户端和服务端其实只是语义上的不同客户端就是一个点。继承 Channel 是因为客户端跟通道是一一对应的 * 所以做了这样的设计还继承了 Resetable接口 是为了实现 reset方法该方法已经打上 Deprecated注解不推荐使用。 * 除了这些客户端就只需要关注一个重连的操作。 */ public interface Client extends Endpoint, Channel, Resetable { /** 重连 */ void reconnect() throws RemotingException; /** 重置不推荐使用 */ Deprecated void reset(com.alibaba.dubbo.common.Parameters parameters); } public interface Resetable { // 用于根据新传入的 url 属性重置自己内部的一些属性 void reset(URL url); }Client 接口继承了三个接口继承Endpoint客户端就是一个点天然具备端的能力继承Channel因为客户端与通道是一一对应的所以做了这样的设计继承Resetable用于实现reset方法该方法可根据新传入的 URL 属性重置自身内部属性不过带参Parameters的重载已打上Deprecated注解不推荐使用。除此之外客户端最独特的操作是reconnect()——当连接断开时支持重连。3.5 Server 接口服务端的语义/** * Remoting Server. (API/SPI, Prototype, ThreadSafe) * * 服务端接口继承了 Endpoint 和 Resetable继承 Endpoint 是因为服务端也是一个点 * 继承 Resetable接口 是为了继承 reset方法。除了这些以外服务端独有的是检测是否启动成功 * 以及获得连接到该服务端上的所有Channel这里获得所有Channel其实就是获取所有连接该服务器的客户端 */ public interface Server extends Endpoint, Resetable { /** 是否绑定本地端口提供服务。即是否启动成功可连接接收消息等 */ boolean isBound(); /** 获得连接到服务端的通道们客户端 */ CollectionChannel getChannels(); /** 通过远程地址获得该地址对应的通道 */ Channel getChannel(InetSocketAddress remoteAddress); Deprecated void reset(com.alibaba.dubbo.common.Parameters parameters); }Server 继承了 Endpoint 和 Resetable服务端同样是一个点。除继承的方法外服务端独有的是isBound()判断是否绑定本地端口提供服务即是否启动成功、可连接、可接收消息getChannels()获得连接到该服务端的所有 Channel即所有连接该服务器的客户端getChannel(InetSocketAddress remoteAddress)通过远程地址获得该地址对应的通道。这一设计恰好呼应了Client 与 Channel 一对一、Server 与 Channel 多对一的通道关系模型。3.6 Codec2 接口编解码器与 TCP 粘拆包在网络中进行传输的数据都是原始的字节序列这就需要发送端使用编码器把要传输的有意义的信息序列化成字节序列接收端再使用解码器把字节序列反序列化成有效信息而同时具备这两种功能的单一组件就叫编解码器。在 Dubbo 中 Codec 是老编解码器接口而 Codec2 是新编解码器接口并且 Dubbo 已经用 CodecAdapter 把 Codec 适配成 Codec2 了所以这里只介绍 Codec2 接口。/** * 编解码器接口需要注意的是 * 1、Codec2 有 SPI注解是一个可扩展接口 * 2、用到了 Adaptive机制首先去 url 中找 key 为 codec 的 value来加载 url 携带的配置中指定的 codec的实现 * 3、该接口中有个枚举类型 DecodeResult因为解码过程中需要解决 TCP 拆包、粘包的场景所以增加了这两种解码结果 * 关于TCP 拆包、粘包的场景可用看一下Netty源码解析中的内容 */ SPI public interface Codec2 { /** 编码 */ Adaptive({Constants.CODEC_KEY}) void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException; /** 解码 */ Adaptive({Constants.CODEC_KEY}) Object decode(Channel channel, ChannelBuffer buffer) throws IOException; /** 解码结果 */ enum DecodeResult { /** 需要更多输入 */ NEED_MORE_INPUT, /** 忽略一些输入 */ SKIP_SOME_INPUT } }Codec2 接口有三个值得注意的设计点可扩展接口SPI注解表明它是一个可扩展接口Adaptive 机制encode和decode方法上的Adaptive({Constants.CODEC_KEY})注解意味着调用时会先去 URL 中找 key 为codec的 value再根据该配置加载指定的 Codec2 实现DecodeResult 枚举解码过程需要解决 TCP 拆包、粘包的场景因此增加了两种解码结果——NEED_MORE_INPUT需要更多输入和SKIP_SOME_INPUT忽略一些输入。关于 TCP 拆包、粘包的具体场景可参考 Netty 源码解析相关文档。3.7 Decodeable 接口可解码对象/** * 可解码的接口该接口有两个作用第一是在调用真正的 decode方法 实现的时候会有一些校验 * 判断是否可以解码并且对解码失败会有一些消息设置第二个是被用来 message核对用的。 * 后面看具体的实现会更了解该接口的作用。 */ public interface Decodeable { /** 解码 */ void decode() throws Exception; }Decodeable 是可解码接口有两个作用第一在调用真正的decode方法实现的时候会有一些校验判断是否可以解码并且对解码失败会有一些消息设置第二被用来做 message 核对用。3.8 Dispatcher 接口消息调度到线程池/** * 调度器接口不同的调度器实现将操作转发到对应的线程池。 * 其中 dispatch 是线程池的调度方法需要注意的是 * 1、该接口是一个可扩展接口并且默认实现AllDispatcher也就是所有消息都派发到线程池 * 包括请求响应连接事件断开事件心跳等 * 2、用了 Adaptive注解也就是按照 URL中的配置来加载实现类后面两个参数是为了兼容老版本 * 如果这是三个key对应的值都为空就选择AllDispatcher来实现。 */ SPI(AllDispatcher.NAME) public interface Dispatcher { /** dispatch the message to threadpool. */ Adaptive({Constants.DISPATCHER_KEY, dispather, channel.handler}) // The last two parameters are reserved for compatibility with the old configuration ChannelHandler dispatch(ChannelHandler handler, URL url); }Dispatcher 是调度器接口不同的调度器实现会将操作转发到对应的线程池。dispatch是线程池的调度方法需要注意该接口是一个可扩展接口默认实现为AllDispatcher即所有消息都派发到线程池包括请求、响应、连接事件、断开事件、心跳等方法上的Adaptive({Constants.DISPATCHER_KEY, dispather, channel.handler})表示按照 URL 中的配置来加载实现类后面两个参数dispather、channel.handler是为了兼容老版本配置如果这三个 key 对应的值都为空就选择 AllDispatcher 来实现。3.9 Transporter 接口网络传输抽象/** * 网络传输接口需要注意的是 * 1、该接口是一个可扩展接口并且默认实现 NettyTransporter * 2、用了 dubbo SPI扩展机制中的Adaptive注解加载对应的bind方法使用url携带的server或者transporter属性值 * 加载对应的connect方法使用url携带的client或者transporter属性值 */ SPI(netty) public interface Transporter { /** 绑定一个服务器 */ Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind(URL url, ChannelHandler handler) throws RemotingException; /** * 连接一个服务器即创建一个客户端 * * param url server url 服务器地址 * param handler 通道处理器 * return client 客户端 * throws RemotingException 当连接发生异常时 */ Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }Transporter 是网络传输接口它是对底层 NIO 框架的统一抽象可扩展接口默认实现 NettyTransporterSPI(netty)表明默认使用 Netty 作为传输实现Adaptive 适配bind方法使用 URL 携带的server或transporter属性值加载对应的服务端实现connect方法使用 URL 携带的client或transporter属性值加载对应的客户端实现。这正体现了文章开篇所述在对 NIO 框架选型上dubbo 交由用户选择的机制——用户只要在 URL 中配置client、server或transporter属性即可切换 Netty、Mina、Grizzly 等不同的 NIO 实现。3.10 Transporters 类外观模式的门面/** * 1、该类用到了设计模式的外观模式通过该类的包装隐藏了内部具体的实现细节降低了程序的复杂度 * 也提高了程序的可维护性。比如它包装了调用各种实现 Transporter接口 的方法 * 通过 getTransporter 来获得 Transporter 的实现对象具体实现哪个实现类取决于url中携带的配置信息 * 如果url中没有相应的配置则默认选择 SPI 中的默认值 netty。 * 2、bind 和 connect方法分别有两个重载方法其中的操作只是把字符串的url转化为URL对象。 * 3、静态代码块中检测了一下jar包是否有重复。 */ public class Transporters { static { // 检查重复的 jar包 Version.checkDuplicate(Transporters.class); Version.checkDuplicate(RemotingException.class); } private Transporters() { } public static Server bind(String url, ChannelHandler... handler) throws RemotingException { return bind(URL.valueOf(url), handler); } public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException { if (url null) { throw new IllegalArgumentException(url null); } if (handlers null || handlers.length 0) { throw new IllegalArgumentException(handlers null); } // 创建 handler ChannelHandler handler; if (handlers.length 1) { handler handlers[0]; } else { handler new ChannelHandlerDispatcher(handlers); } // 调用Transporter的实现类对象的bind方法。 // 例如实现NettyTransporter则调用NettyTransporter的connect并且返回相应的server return getTransporter().bind(url, handler); } public static Client connect(String url, ChannelHandler... handler) throws RemotingException { return connect(URL.valueOf(url), handler); } public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException { if (url null) { throw new IllegalArgumentException(url null); } // 创建 handler ChannelHandler handler; if (handlers null || handlers.length 0) { handler new ChannelHandlerAdapter(); } else if (handlers.length 1) { handler handlers[0]; } else { handler new ChannelHandlerDispatcher(handlers); } // 调用Transporter的实现类对象的connect方法。 // 例如实现NettyTransporter则调用NettyTransporter的connect并且返回相应的client return getTransporter().connect(url, handler); } public static Transporter getTransporter() { return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension(); } }Transporters 类用到了设计模式中的外观模式Facade是 Dubbo 远程通信对外暴露的统一入口其设计要点有三隐藏实现细节通过getTransporter()获得 Transporter 的实现对象具体实现哪个实现类取决于 URL 中携带的配置信息如果 URL 中没有相应配置则默认选择SPI中的默认值netty。getTransporter()内部通过ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension()获得自适应扩展对象这正是 Dubbo SPI 扩展机制的典型用法重载方法bind和connect方法分别有两个重载方法字符串版本的重载只是把字符串形式的 URL 转化为URL.valueOf(url)的 URL 对象重复 jar 检测静态代码块中通过Version.checkDuplicate检测了 jar 包是否有重复避免同包多版本导致的类冲突。此外bind与connect内部对多个 handler 的处理也体现了细节当传入多个 handler 时会包装为ChannelHandlerDispatcher进行统一调度connect在未传入 handler 时会默认使用ChannelHandlerAdapter空实现保证接口的健壮性。3.11 远程通信的异常类RemotingException、ExecutionException 和 TimeoutException 是远程通信的异常类内容比较简单这里简单梳理一下继承体系RemotingException继承了Exception类是远程通信的基础异常ExecutionException继承了 RemotingException是远程通信的执行异常TimeoutException继承了 RemotingException是超时异常。三者构成了远程通信层的异常体系基础异常 → 执行异常 / 超时异常为上层 RPC 调用提供了统一的异常语义。四、总结远程通信模块的整体抽象脉络综合以上源码分析可以梳理出 dubbo-remoting-api 最外层源码的抽象链条Endpoint端是最基础的抽象抽象出 URL、通道处理器、本地地址、发送、关闭等共性能力Channel通道在端之上补充远程地址、连接状态与属性存取作为 client 与 server 之间的数据传输桥梁ChannelHandler通道处理器通过connected、disconnected、sent、received、caught五个回调承载通道上的业务逻辑且为SPI可扩展接口Client / Server在语义上区分客户端与服务端Client 与 Channel 一对一独有重连能力Server 与 Channel 多对一独有启动状态检测与通道集合获取Codec2编解码器解决字节序列与有效信息之间的序列化/反序列化其DecodeResult枚举支撑 TCP 拆包、粘包场景Dispatcher调度器将各类消息事件派发到对应线程池默认 AllDispatcherTransporter传输器作为对 Netty、Mina、Grizzly 等 NIO 框架的统一抽象通过SPI(netty)默认使用 NettyTransporters门面以外观模式封装底层细节配合 ExtensionLoader 的自适应扩展机制实现URL 中一个配置即可切换传输实现的插件化能力。这一抽象体系与 Dubbo 整体架构中远程通讯架构遥相呼应Exchange 组件负责 Request-Response 语义的信息交换Transport 组件负责单向消息传输两者共同支撑起 RPC 调用的通讯基础。如需继续深入可进一步阅读 docs/Dubbo/architectureDesign/Dubbo整体架构.md 了解整体架构以及 docs/Dubbo/remote/Buffer组件.md、docs/Dubbo/remote/Exchange组件.md、docs/Dubbo/remote/Transport组件.md 了解各子模块的详细实现。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门