dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。每个服务封装成一个ServiceBean。每个ServiceBean代表一个服务。
服务导出:即调用每个ServiceBean的export()方法

一、导出流程简述

导出方法入口:ServiceBean中export()方法。实现了Spring的监听器,当服务启动完成后,发布事件触发export()。
所谓服务导出主要做的事情:

  1. 确认服务参数信息。(参数来源:注解、配置文件、配置中心、启动参数。进行合并、继承、覆盖)
    1. 确认服务的配置参数
    2. 确认服务支持的协议
  2. 根据服务参数信息,启动对应的网络服务器(netty、tomcat、jetty等),接收消费者网络请求。
    1. 根据服务支持的协议,启动网络服务。
  3. 根据服务参数信息,生成服务的URL,并注册到注册中心
    1. 构造服务最终URL
    2. 将服务URL注册到注册中心
  4. 监听服务参数动态修改事件。

    1. 服务导出时,绑定监听器。参数修改后,重新导出。

      dubbo服务

      dubbo服务示例:
      http://127.0.0.1:80/org.apache.dubbo.demo.MockService?timeout=3000&version=1.0.1&application=dubbo-demo-annotation-provider
      dubbo服务是拥有参数,比如协议、IP、超时时间、版本号、所属应用等。所以导出前需确认服务参数

      二、服务导出前步骤回顾

  5. 解析配置文件,并生成对应的配置实例。

  6. 扫描dubbo指定包路径下服务类。生成两个BeanDefinition。
  7. 完成依赖注入。
  8. 注册DubboApplicationListenerRegistrar。(继承ApplicationContextAware

    1. 向Spring容器注册 DubboBootstrapApplicationListener 监听器(监听Spring启动(或关闭)完成事件)。
      1. 启动dubbo容器。(DubboBootstrap#start)
      2. 设置容器状态标识。(启动标识:started=true。销毁标识:destroyed=false,准备标识:ready=false)
      3. 执行初始化。
        1. 启动配置中心
        2. 加载远端 注册信息、协议信息。
        3. 校验配置信息(应用、元数据、提供者、消费者、监控等)。
      4. 执行服务导出。DubboBootstrap#exportServices。
    2. 向Spring容器注册 DubboLifecycleComponentApplicationListener 监听器。

      三、服务参数信息确认

      Dubbo参数配置

      1. 系统环境变量。在启动应用程序时,通过-D的方式来指定参数,在Dubbo的源码中叫:SystemConfiguration
      2. 分布式配置中心。2.7版本后支持分布式配置中心,可以在Dubbo-Admin操作配置中心,分布式配置中心就相当于远程的dubbo.properties文件。
        1. 支持按应用配置。按应用配置:AppExternalConfiguration
        2. 支持全局配置。全局配置:ExternalConfiguration
      3. @DubboService注解。在Dubbo的源码中叫:AbstractConfig
      4. dubbo.properties文件。dubbo读取文件的内容作为服务的参数,Dubob的源码中叫做:PropertiesConfiguration

        配置参数优先级

        参数优先级从高到低如下:系统环境变量—>分布式配置中心—>@DubboService注解—>dubbo.properties

        配置参数继承

        如果服务本身没有配置timeout参数,但是服务所属的应用的配置timeout,那么应用下的服务都会继承timeout配置

        服务参数确认

        协议是参数中的一种。
        在服务参数确认过程中,会进行参数合并、覆盖、继承(高优先级覆盖低优先级)

        四、根据参数生成服务URL并注册

  9. ServiceBean.export()流程调起。每个服务都发起自己的服务导出

  10. 获取服务参数。读取并更新配置信息,即刷新ServiceBean参数。
  11. 遍历协议URL,生成最终服务URL,并将URL注册到注册中心
    1. 聚合服务元数据(应用、服务、鉴权相关)
    2. 初始化服务元数据
    3. 根据元数据生成原始服务URL
      1. 递归注册中心。
      2. 将服务URL作为入参,生成Invoke对象[包括服务实现信息(ref)、服务接口信息、注册中心信息]。
      3. 封装Invoke、当前服务配置信息(ServiceConfig),生成可以导出的服务对象(DelegateProviderMetaDataInvoker)。
    4. 针对每个协议(利用SPI实现),进行服务URL(导出)注册(注册本地、远端)
      1. 简化原始服务URL。生成最终服务URL
      2. 将最终的URL写入注册中心(在网络服务器启动完成后,执行写入)。
  12. 发布注册完成事件
    1. 如果用户配置了多个注册中心,将服务URL注册到每个注册中心去

提供者同时完成监听配置中心(3个监听任务,老配置平台1个,新配置平台2个),当配置发生变化,则进行url重置(删除、重新注册),重新发布。
配置修改后,不用重启web容器,仅更新心跳任务。

五、启动网络服务器

根据配置的协议(比如Http协议、Dubbo协议),启动web服务容器。
Http协议就启动Tomcat、Jetty。 Dubbo协议就启动Netty。
不仅启动Server,还需要绑定一个RequestHandler,用来处理请求。
比如,Http协议对应的就是:InternalHandler。Dubbo协议对应的就是:ExchangeHandler

1.所有dubbo服务是否复用同一个netty服务

所有dubbo协议的服务接口共用一个netty服务。
具体框架逻辑。

  1. private void openServer(URL url) {
  2. // find server.
  3. //获取注册中心地址信息(ip+port),作为key.缓存netty服务实例
  4. String key = url.getAddress();
  5. //client can export a service which's only for server to invoke
  6. boolean isServer = url.getParameter(IS_SERVER_KEY, true);
  7. if (isServer) {
  8. ProtocolServer server = serverMap.get(key);
  9. // 如果已经存在 netty服务实例。则不在进行启动
  10. if (server == null) {
  11. synchronized (this) {
  12. server = serverMap.get(key);
  13. if (server == null) {
  14. //双重锁,保证完成一次【启动netty服务】
  15. serverMap.put(key, createServer(url));
  16. }
  17. }
  18. } else {
  19. server.reset(url);
  20. }
  21. }
  22. }

2.Dubbo协议启动的Server过程。

  1. 调用DubboProtocol的openServer(URL url)方法启动Server
    1. 获取注册中心地址信息(ip+port),作为key,缓存netty服务实例。
  2. 调用DubboProtocol的createServer(url)方法。
  3. 调用Exchangers.bind(url, requestHandler),绑定请求处理器;并生成ExchangeServer实例。
    1. requestHandler表示请求处理器,用来处理dubbo调用请求
    2. Exchangers.bind(url, requestHandler)中,根据URL得到一个Exchanger,@SPI设置默认:HeaderExchanger
    3. HeaderExchanger中包括HeaderExchangeClient、HeaderExchangeServer
    4. HeaderExchangeClient负责发送心跳,HeaderExchangeServer负责接收心跳,如果超时则会关闭channel
  4. 构造HeaderExchangeServer前,调用Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))方法,生成RemotingServer实例 。
    1. Transporters#bind(URL, ChannelHandler…),使用getTransporter获得Transporter实例。并调用Transporter#bind
    2. NettyTransporter#bind方法中实例化new NettyServer(url, handler)。所以RemotingServer默认就是NettyServer
    3. 构造NettyServer时,会调用ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))再构造一个ChannelHandler。
    4. wrap中的handler是经过包装的上面requestHandler。[new DecodeHandler(new HeaderExchangeHandler(handler))]
    5. wrap方法调用new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)));构造一个ChannelHandler
  5. 完成ChannelHandler构造后,调用AbstractServer抽象类的doOpen方法,真正开启Server。
  6. NettyServer中具体实现doOpen方法。
    1. 调用new NettyHandler(getUrl(), this)构造一个NettyHandler,并bind地址
  7. DubboProtocol协议的启动Server流程完成。

    六、提供者的监听动态配置

    监听配置中心(3个监听任务,老配置平台1个,新配置平台2个),当配置发生变化,重写URL,并重新发布注册。(不会重启Netty服务,仅更新定时检测任务)