“ 分布式应用场景有高并发,高可扩展和高性能的要求。还涉及到,序列化/反序列化,网络,多线程以及设计模式的问题。幸好 Dubbo 框架将上述知识进行了封装,让程序员能够把注意力放到业务上。
概念和架构
- Provider:暴露服务的服务提供方
- Consumer:调用远程服务消费方
- Registry:服务注册与发现注册中心
- Monitor:监控中心和访问调用统计
- Container:服务运行容器
Dubbo 服务器注册与发现的流程?
- 服务容器Container负责启动,加载,运行服务提供者。
- 服务提供者Provider在启动时,向注册中心注册自己提供的服务。
- 服务消费者Consumer在启动时,向注册中心订阅自己所需的服务。
- 注册中心Registry返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者Consumer,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者Consumer和提供者Provider,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心Monitor。
Dubbo 分层
Dubbo 是一款高性能 Java RPC 架构。它实现了面向接口代理的 RPC 调用,服务注册和发现,负载均衡,容错,扩展性等等功能。
Dubbo 大致上分为三层,分别是:
- 业务层
- RPC 层
- Remoting 层
Dubbo 的三层结构
从上图中可以看到,三层结构中包含了 Dubbo 的核心组件。他们的基本功能如下,对于比较常用的组件,会在后面的篇幅中详细讲解。
组件功能列表
这里将这些组件罗列出来,能有一个感性的认识。具体开发的时候,知道运用哪些组件。
Dubbo 调用工作流
Dubbo 框架是用来处理分布式系统中,服务发现与注册以及调用问题的,并且管理调用过程。
上面介绍了 Dubbo 的框架分层,下图的工作流就展示了他们是如何工作的。
Dubbo 服务调用流程图
工作流涉及到服务提供者(Provider),注册中心(Registration),网络(Network)和服务消费者(Consumer):
- 服务提供者在启动的时候,会通过读取一些配置将服务实例化。
- Proxy 封装服务调用接口,方便调用者调用。客户端获取 Proxy 时,可以像调用本地服务一样,调用远程服务。
- Proxy 在封装时,需要调用 Protocol 定义协议格式,例如:Dubbo Protocol。
- 将 Proxy 封装成 Invoker,它是真实服务调用的实例。
- 将 Invoker 转化成 Exporter,Exporter 只是把 Invoker 包装了一层,是为了在注册中心中暴露自己,方便消费者使用。
- 将包装好的 Exporter 注册到注册中心。
- 服务消费者建立好实例,会到服务注册中心订阅服务提供者的元数据。元数据包括服务 IP 和端口以及调用方式(Proxy)。
- 消费者会通过获取的 Proxy 进行调用。通过服务提供方包装过程可以知道,Proxy 实际包装了 Invoker 实体,因此需要使用 Invoker 进行调用。
- 在 Invoker 调用之前,通过 Directory 获取服务提供者的 Invoker 列表。在分布式的服务中有可能出现同一个服务,分布在不同的节点上。
- 通过路由规则了解,服务需要从哪些节点获取。
- Invoker 调用过程中,通过 Cluster 进行容错,如果遇到失败策略进行重试。
- 调用中,由于多个服务可能会分布到不同的节点,就要通过 LoadBalance 来实现负载均衡。
- Invoker 调用之前还需要经过 Filter,它是一个过滤链,用来处理上下文,限流和计数的工作。
- 生成过滤以后的 Invoker。
- 用 Client 进行数据传输。
- Codec 会根据 Protocol 定义的协议,进行协议的构造。
- 构造完成的数据,通过序列化 Serialization 传输给服务提供者。
- Request 已经到达了服务提供者,它会被分配到线程池(ThreadPool)中进行处理。
- Server 拿到请求以后查找对应的 Exporter(包含有 Invoker)。
- 由于 Export 也会被 Filter 层层包裹
- 通过 Filter 以后获得 Invoker
- 最后,对服务提供者实体进行调用。
上面调用步骤经历了这么多过程,其中出现了 Proxy,Invoker,Exporter,Filter。
实际上都是调用实体在不同阶段的不同表现形式,本质是一样的,在不同的使用场景使用不同的实体。
例如 Proxy 是用来方便调用者调用的。Invoker 是在调用具体实体时使用的。Exporter 用来注册到注册中心的等等。
后面我们会对具体流程进行解析。如果时间不够无法阅读完全文,可以把上面的图保存。
服务暴露实现原理
上面讲到的服务调用流程中,开始服务提供者会进行初始化,将暴露给其他服务调用。服务消费者也需要初始化,并且在注册中心注册自己。
服务提供者和服务消费者暴露服务
首先来看看服务提供者暴露服务的整体机制:
服务提供者暴露服务流程
开篇的大图中列举了 Config 核心组件,在服务提供者初始化的时候,会通过 Config 组件中的 ServiceConfig 读取服务的配置信息。
这个配置信息有三种形式,分别是 XML 文件,注解(Annoation)和属性文件(Properties 和 yaml)。
在读取配置文件生成服务实体以后,会通过 ProxyFactory 将 Proxy 转换成 Invoker。
此时,Invoker 会被定义 Protocol,之后会被包装成 Exporter。最后,Exporter 会发送到注册中心,作为服务的注册信息。上述流程主要通过 ServiceConfig 中的 doExport 完成。
下面是针对多协议多注册中心进行源代码分析:
doExportUrls 方法
doExportUrlsFor1Protocol 方法-1
doExportUrlsFor1Protocol 方法-2
上面截取了服务提供者暴露服务的代码片段,从注释上看整个暴露过程分为七个步骤:
- 读取其他配置信息到 map 中,用来后面构造 URL。
- 读取全局配置信息。
- 配置不是 remote,也就是暴露本地服务。
- 如果配置了监控地址,则服务调用信息会上报。
- 通过 Proxy 转化成 Invoker,RegistryURL 存放的是注册中心的地址。
- 暴露服务以后,向注册中心注册服务信息。
- 没有注册中心直接暴露服务。
一旦服务注册到注册中心以后,注册中心会通过 RegistryProtocol 中的 Export 方法将服务暴露出去,并依次做以下操作:
- 委托具体协议进行服务暴露,创建 NettyServer 监听端口,并保持服务实例。
- 创建注册中心对象,创建对应的 TCP 连接。
- 注册元数据到注册中心。
- 订阅 Configurators 节点。
- 如果需要销毁服务,需要关闭端口,注销服务信息。
说完了服务提供者的暴露再来看看服务消费者。
服务消费者消费服务机制
服务消费者首先持有远程服务实例生成的 Invoker,然后把 Invoker 转换成用户接口的动态代理引用。
框架进行服务引用的入口点在 ReferenceBean 中的 getObject 方法,会将实体转换成 ReferenceBean&#