Dubbo学习笔记(三)——实现原理
1.框架设计
Dubbo的整体框架入下图所示。
其中各层的说明如下:
- service服务层:用户编写的服务接口和服务实现类。这是框架中,唯一用户可见的。
- config配置层:对外的配置接口,封装配置文件中的配置信息,以 ServiceConfig, ReferenceConfig 为中心,分别表示服务提供者和消费者的配置。
- proxy服务代理层:用于生成服务端或者消费者的代理对象,通过代理对象来调用服务。
- registry注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,用于服务的发现和注册。
- cluster路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,选择调用。
- Monitor监控层:每次的调用信息都会发送给监控层,然后由监控层进行记录RPC 调用次数和调用时间监控,以 Statistics 为中心。
- protocol远程调用层:封装RPC调用。
- exchange信息交换:封装请求响应模式,同步转异步。
- transport网络传输层:抽象mina和netty为统一接口。创建客户端和服务端,建立通信连接,是真正传输的数据的地方,基于netty框架实现。
- serialize数据序列化层:将数据进行序列化了再进行传输,传输得到的数据再反序列化。可复用一些工具。
其实大致可以分为三层:用户层、远程调用层和数据网络传输层。
2.启动解析、加载配置信息
流程如下:
- 容器启动。
- DubboNameSpaceHandler解析配置文件,并创建Dubbo标签解析器。
- 标签解析器挨个解析Dubbo每一个标签,每个表标签就是一个bleanclass,解析完成之后封装到每个标签指定的配置对象当中,例如ApplicationConfig、RegistryConfig...
- 其中Service标签对应的是 ServiceBean,Reference标签对应的是ReferenceBean,需要涉及服务暴露的问题。
3.服务暴露流程
- 完成ServiceBean容器创建初始化之后,由onApplicationEvent时间响应方法监听,该方法会收到Spring上下文刷新事件后执行服务导出操作。然后会触发ContextRefreshEvent事件,该事件来决定是否导出服务。
- ServiceConfig会调用doExportUrls来导出服务。主要做的事情就是加载注册中心连接,然后遍历每个协议进行后续的导出服务。
- 配置检查完毕之后,就是根据配置来组装URL。首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。
- 完成了前置工作,就会创建Invoker,最后会进行服务导出,服务导出分为导出到本地和导出到远程。Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。ProxyFactory调用getInvoker创建Invoker,通过invoker的invokerMethod方法最终会调用目标方法。也会将服务信息保存到注册中心。
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
4.服务引用流程
流程和上面的服务暴露流程有很多相似之处。
- ReferenceBean是实现了FactoryBean接口,当我们从容器中自动注入服务对象的时候,它实现其中的getObject方法,就会返回服务对象。
- 在引用服务时,首先对配置进行检查和处理,然后创建代理对象。
- 创建代理对象中,首先更具配置检查是否为本地调用,则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例。若不是,则读取直连配置项,或注册中心 url,并将读取到的 url 存储到 urls 中。然后根据 urls 元素数量进行后续操作。若 urls 元素数量为1,则直接通过 Protocol 自适应拓展类构建 Invoker 实例接口。若 urls 元素数量大于1,即存在多个注册中心或服务直连 url,此时先根据 url 构建 Invoker。然后再通过 Cluster 合并多个 Invoker,最后调用 ProxyFactory 生成代理类。
- 创建Invoker,Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。
- 其中DubboProtocol需要获取客户端进行通信,默认采用NettyClient。initClient 方法首先获取用户配置的客户端类型,默认为 netty。然后检测用户配置的客户端类型是否存在,不存在则抛出异常。最后根据 lazy 配置决定创建什么类型的客户端。这里的 LazyConnectExchangeClient 代码并不是很复杂,该类会在 request 方法被调用时通过 Exchangers 的 connect 方法创建 ExchangeClient 客户端。底层都是调用Netty创建客户端。
- 然后由Protocol调用refer方法引用远程服务。doRefer方法订阅服务,创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker。
- 这样代理对象就创建完毕了,可以进行远程调用了。
5.服务调用流程
Dubbo服务调用的过程如图所示。
上面的引用之后,产生了一个代理对象,代理对象如何调用服务。
调用链如图所示。
6.其他
RPC原理
RPC的调用原理示意图如下:
一次完整的RPC调用流程如下:
- 消费者调用以本地调用方式调用服务;
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务器地址,并将消息发送到服务端;
- server stub受到消息后进行解码;
- server stub根据解码结果调用本地的服务;
- 本地服务执行并将结果放回给server stub;
- server stub将返回结果打包成消息并发送给消费方;
- client stub接受到消息,并进行解码;
- 消费者得到最终的结果。
Netty通信原理
Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它极大地简化了TCP和UDP套接字服务器等网络编程。