Dubbo3 源码解读-宋小生-12:全局视野来看Dubbo3的服务启动生命周期

简介: > Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。> 本文是
Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。

本文是 Dubbo 社区贡献者宋小生基于 Dubbo3 3.0.8 版本撰写的源码解析博客,在 Dubbo3 开源&内部技术栈统一的情况下,期望能对集团内的开发者了解 Dubbo3 背后的实现原理有所帮助。可点此查看 博客原文

本篇是宋小生系列 12/30 篇。同时,由 Dubbo3 团队领导的源码解读系列也正在进行中,感兴趣的同学可加入钉钉群了解详情: 28165003194

12.1 启动方法简介

在说启动方法之前先把视野拉回第一章《1-从一个服务提供者的Demo说起》我们的Demo代码,下面只贴一下核心代码:

public class Application {
    public static void main(String[] args) throws Exception {
            startWithBootstrap();
    }
    private static void startWithBootstrap() {
        //前面的文章都在说这个服务配置对象的创建,中间又说了分层域模型,扩展加载机制
        ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
        //为服务配置下服务接口和服务实现,下面两行用来初始化对象就不详细说了
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        //这一个篇章主要说这里:
        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        //初始化应用配置
        bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
        //初始化注册中心配置
            .registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
            //初始化协议配置
            .protocol(new ProtocolConfig(CommonConstants.DUBBO, -1))
            //初始化服务配置
            .service(service)
            //启动
            .start()
            .await();
    }
}

前面我们介绍了Dubbo启动器DubboBootstrap类型对象的创建,又介绍了为DubboBootstrap启动器初始化各种配置信息,这一个博客就开始到了分析启动方法的位置了,Dubbo启动器借助Deployer发布器来启动和发布服务,发布器的启动过程包含了启动配置中心,加载配置,启动元数据中心,启动服务等操作都是比较重要又比较复杂的过程,这里我们先来看下启动过程的生命周期来为后面的内容做好铺垫。

12.2 启动器启动方法的调用逻辑start()

这里我们就直接来看DubboBootstrap的start()方法:

 public DubboBootstrap start() {
         //调用重载的方法进行启动参数代表是否等待启动结束
        this.start(true);
        return this;
    }

我们再来看重载的start方法:

public DubboBootstrap start(boolean wait) {
        //这个发布器是在ApplicationModel对象创建之后初始化的时候进行初始化的具体类型为DefaultApplicationDeployer
        Future future = applicationDeployer.start();
      
        if (wait) {
            try {
              //等待异步启动的结果
                future.get();
            } catch (Exception e) {
                //启动失败则抛出一个异常
                throw new IllegalStateException("await dubbo application start finish failure", e);
            }
        }
        return this;
    }

12.3 应用程序发布器DefaultApplicationDeployer的启动方法

发布器是帮助我们发布服务和引用服务的,在Dubbo3中不论是服务提供者还是服务消费者如果想要启动服务都需要走这个启动方法的逻辑,所以务必重视

我们直接来看DefaultApplicationDeployer的start()代码:

@Override
    public Future start() {
        //启动锁,防止重复启动
        synchronized (startLock) {
            //发布器,状态已经设置为停止或者失败了就直接抛出异常
            if (isStopping() || isStopped() || isFailed()) {
                throw new IllegalStateException(getIdentifier() + " is stopping or stopped, can not start again");
            }

            try {
                // maybe call start again after add new module, check if any new module
                //可能在添加新模块后再次调用start,检查是否有任何新模块
                //这里遍历当前应用程序下的所有模块如果某个模块是PENDING状态则这里hasPendingModule的值为true
                boolean hasPendingModule = hasPendingModule();
                //发布器状态正在启动中
                if (isStarting()) {
                    // currently, is starting, maybe both start by module and application
                    // if it has new modules, start them
                    //存在挂器的模块
                    if (hasPendingModule) {
                         //启动模块
                        startModules();
                    }
                    // if it is starting, reuse previous startFuture
                    //模块异步启动中
                    return startFuture;
                }

                // if is started and no new module, just return
                //如果已启动且没有新模块,直接返回
                if (isStarted() && !hasPendingModule) {
                    return CompletableFuture.completedFuture(false);
                }

                // pending -> starting : first start app
                // started -> starting : re-start app
                //启动状态切换,将启动状态切换到STARTING(pending和started状态无需切换)
                onStarting();
                //核心初始化逻辑,这里主要做一些应用级别启动比如配置中心,元数据中心
                initialize();
                //启动模块(我们的服务提供和服务引用是在这个模块级别的)
                doStart();
            } catch (Throwable e) {
                onFailed(getIdentifier() + " start failure", e);
                throw e;
            }

            return startFuture;
        }
    }

这个启动方法逻辑不多 主要三个方法我们重点来看:

  • onStarting() 这个是启动之前的状态切换
  • initialize() 应用的初始化逻辑 比如配置中心,元数据中心的初始化
  • doStart() 启动模块比如启动我们的服务提供和服务引用的)

继续看后面的细节吧,代码胜千言。

12.4 应用程序发布器对应用级别的初始化逻辑

这个我们先来看DefaultApplicationDeployer的初始化方法initialize():

@Override
    public void initialize() {
        //状态判断 如果已经初始化过了就直接返回
        if (initialized) {
            return;
        }
        // Ensure that the initialization is completed when concurrent calls
        //启动锁,确保在并发调用时完成初始化
        synchronized (startLock) {
            //双重校验锁 如果已经初始化过了就直接返回
            if (initialized) {
                return;
            }
            // register shutdown hook
            //注册关闭钩子,这个逻辑基本每个中间件应用都必须要要做的事情了,正常关闭应用回收资源,一般没这个逻辑情况下容易出现一些异常,让我们开发人员很疑惑,而这个逻辑往往并不好处理的干净。
            registerShutdownHook();
            
            //启动配置中心,感觉Dubbo3耦合了这个玩意
            startConfigCenter();
            
            //加载配置,一般配置信息当前机器的来源:环境变量,JVM启动参数,配置文字
            loadApplicationConfigs();

            //初始化模块发布器 (发布服务提供和服务引用使用)
            initModuleDeployers();

            // @since 2.7.8
            //启动元数据中心
            startMetadataCenter();
            
            //初始化完成
            initialized = true;

            if (logger.isInfoEnabled()) {
                logger.info(getIdentifier() + " has been initialized!");
            }
        }
    }

这个是个生命周期整体概览的方法,将具体逻辑拆分到各个子方法中,是代码重构的一种策略,上面注释也很清楚了就不细说了,上面每个方法在后面会有单独的博客来分析。

12.5 应用下模块的启动(服务的发布与引用)

我们回过头来详细看DefaultApplicationDeployer的doStart()代码:

private void doStart() {
        // 启动模块
        startModules();

DefaultApplicationDeployer的 startModules()方法

private void startModules() {
        // ensure init and start internal module first
        //确保初始化并首先启动内部模块,Dubbo3中将模块分为内部和外部,内部是核心代码已经提供的一些服务比如元数据服务,外部是我们自己写的服务
        prepareInternalModule();

        // filter and start pending modules, ignore new module during starting, throw exception of module start
        //启动所有的模块 (启动所有的服务)
        for (ModuleModel moduleModel : new ArrayList<>(applicationModel.getModuleModels())) {
            //这个状态默认就是PENDING的
            if (moduleModel.getDeployer().isPending()) {
                //模块启动器,发布服务
                moduleModel.getDeployer().start();
            }
        }
    }

这个模块的启动其实就是用来启动服务的 先启动内部服务,再启动外部服务
内部服务有个元数据服务Dubbo3中每个服务都可以对外提供服务的元数据信息,来简化服务配置,不论是内部服务还是外部服务调用的代码逻辑都是模块发布器ModuleDeployer的start()方法,接下来我们详细看下模块发布器的生命周期函数。

12.6 模块发布器发布服务的过程

前面我们说到了所有的服务都是经过模块发布器,ModuleDeployer的start()方法来启动的,那我们接下来就来看看这个模块发布器的启动方法。

ModuleDeployer的start()方法代码:

@Override
    public synchronized Future start() throws IllegalStateException {
        //模块发布器已经停止或者启动失败则直接抛出异常返回
        if (isStopping() || isStopped() || isFailed()) {
            throw new IllegalStateException(getIdentifier() + " is stopping or stopped, can not start again");
        }

        try {
            //启动中或者已经启动了则直接返回一个Future对象 
            if (isStarting() || isStarted()) {
                return startFuture;
            }
            //切换模块启动状态为STARTING
            onModuleStarting();

            // initialize
            //如果应用未初始化则初始化(非正常逻辑)
            applicationDeployer.initialize();
            //模块发布器进行初始化
            initialize();

            // export services
            //暴露服务
            exportServices();

            // prepare application instance
            // exclude internal module to avoid wait itself
            if (moduleModel != moduleModel.getApplicationModel().getInternalModule()) {
                applicationDeployer.prepareInternalModule();
            }

            // refer services
            //引用服务
            referServices();

            // if no async export/refer services, just set started
            //非异步启动则直接切换状态为STARTED
            if (asyncExportingFutures.isEmpty() && asyncReferringFutures.isEmpty()) {
                onModuleStarted();
            } else {
            //如果是异步的则等待服务发布和服务引用异步回调
                frameworkExecutorRepository.getSharedExecutor().submit(() -> {
                    try {
                        // wait for export finish
                        waitExportFinish();
                        // wait for refer finish
                        waitReferFinish();
                    } catch (Throwable e) {
                        logger.warn("wait for export/refer services occurred an exception", e);
                    } finally {
                        //异步回调完成 所有服务都启动了,再切换状态
                        onModuleStarted();
                    }
                });
            }
        } catch (Throwable e) {
            onModuleFailed(getIdentifier() + " start failed: " + e, e);
            throw e;
        }
        return startFuture;
    }

好了整体的服务启动生命周期就如上代码,后续我们再详细来看每个细节。

12.7 发布器简介

前面主要说了应用和模块的发布器的启动和初始化,下面简单了解下它们的关系,如下所示

可以发布器主要包含

  • 应用的发布器ApplicationDeployer用于初始化并启动应用程序实例
  • 模块发布器ModuleDeployer 模块(服务)的导出/引用服务

两种发布器有各自的接口,他们都继承了抽象的发布器AbstractDeployer 封装了一些公共的操作比如状态切换,状态查询的逻辑。

另外我们再来看下发布过程的状态枚举DeployState如下:

public enum DeployState {
    /**
     * Unknown state
     */
    UNKNOWN,

    /**
     * Pending, wait for start
     */
    PENDING,

    /**
     * Starting
     */
    STARTING,

    /**
     * Started
     */
    STARTED,

    /**
     * Stopping
     */
    STOPPING,

    /**
     * Stopped
     */
    STOPPED,

    /**
     * Failed
     */
    FAILED
}

Dubbo这一块后续可以优化以下,这里的状态切换全部耦合在一起了,可以考虑使用状态机将状态与行为解耦。

目录
相关文章
|
1月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
81 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
3月前
|
JSON Dubbo Java
【Dubbo协议指南】揭秘高性能服务通信,选择最佳协议的终极攻略!
【8月更文挑战第24天】在分布式服务架构中,Apache Dubbo作为一款高性能的Java RPC框架,支持多种通信协议,包括Dubbo协议、HTTP协议及Hessian协议等。Dubbo协议是默认选择,采用NIO异步通讯,适用于高要求的内部服务通信。HTTP协议通用性强,利于跨语言调用;Hessian协议则在数据传输效率上有优势。选择合适协议需综合考虑性能需求、序列化方式、网络环境及安全性等因素。通过合理配置,可实现服务性能最优化及系统可靠性提升。
58 3
|
3月前
|
缓存 Dubbo Java
Dubbo服务消费者启动与订阅原理
该文章主要介绍了Dubbo服务消费者启动与订阅的原理,包括服务消费者的启动时机、启动过程以及订阅和感知最新提供者信息的方式。
Dubbo服务消费者启动与订阅原理
|
3月前
|
Dubbo 网络协议 Java
深入掌握Dubbo服务提供者发布与注册原理
该文章主要介绍了Dubbo服务提供者发布与注册的原理,包括服务发布的流程、多协议发布、构建Invoker、注册到注册中心等过程。
深入掌握Dubbo服务提供者发布与注册原理
|
3月前
|
负载均衡 Dubbo Java
Dubbo服务Spi机制和原理
该文章主要介绍了Dubbo中的SPI(Service Provider Interface)机制和原理,包括SPI的基本概念、Dubbo中的SPI分类以及SPI机制的实现细节。
Dubbo服务Spi机制和原理
|
3月前
|
C# 开发者 Windows
勇敢迈出第一步:手把手教你如何在WPF开源项目中贡献你的第一行代码,从选择项目到提交PR的全过程解析与实战技巧分享
【8月更文挑战第31天】本文指导您如何在Windows Presentation Foundation(WPF)相关的开源项目中贡献代码。无论您是初学者还是有经验的开发者,参与这类项目都能加深对WPF框架的理解并拓展职业履历。文章推荐了一些适合入门的项目如MvvmLight和MahApps.Metro,并详细介绍了从选择项目、设置开发环境到提交代码的全过程。通过具体示例,如添加按钮点击事件处理程序,帮助您迈出第一步。此外,还强调了提交Pull Request时保持专业沟通的重要性。参与开源不仅能提升技能,还能促进社区交流。
44 0
|
3月前
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
6月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
29天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
53 2
|
3月前
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
85 0