美文网首页
SOFAArk启动源码分析

SOFAArk启动源码分析

作者: 摆渡时光 | 来源:发表于2019-12-06 11:57 被阅读0次

    一、背景

    首先来个SOFAArk官方介绍:SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要提供类隔离和应用(模块)动态部署能力;基于 Fat Jar 技术,可以将多个应用(模块)打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot/SOFABoot 应用;访问网址进入快速开始并获取更多详细信息;

    所以SOFAArk其实是提供一个类隔离能力,那么具体可以解决什么问题?举个栗子,我们知道springboot引用的jar包都放在同一个pom.xml文件中。当工程比较小的时候,一切ok,但如果工程是由多个业务方和团队协作开发时,就会带来不少风险和工作量。比如工程中有两个独立的功能FunA和FunB都依赖了C这个jar包,现在FunA需要升级,那么就需要排查FunB是否兼容,不兼容的代码改动就带来了额外的风险,拔出萝卜带出泥,甚至涉及long long ago的祖传代码。。。

    image

    SOFAArk不仅仅可以解决这么一个问题,还有包冲突、动态装载卸载模块、合并部署等。但总体来说核心就是类隔离。

    二、术语

    SOFAArk框架中最重要的三个术语Ark Container, Ark Plugin 和 Ark Biz,看下官方的图示。简单来说SOFAArk框架运行时分为三层:

    底层是容器,干啥的呢?就是启动并加载所有plugin和biz模块,然后将他们都部署起来,它会执行一个pipeline按顺序先部署plugin,然后部署biz模块。

    中间层是插件,一般是可以共享的中间件和通用服务,例如常见的消息中间件、rpc等,如果你需要提供一个给各个上层业务方使用的通用能力,可以定义在这一层。

    最上层就是biz模块层,就是具体的业务代码了。

    另外提一点,biz层可以使用plugin的所有导出类,但plugin不能依赖biz层的类。其实原因也很好理解,因为biz的模块是支持动态加载和卸载的,如果plugin对biz有依赖,那么biz一次动态卸载和加载,依赖的类就混乱了。

    image

    三、类加载器模型

    首先SOFAArk容器通过监听springboot启动事件来启动自己,启动类SofaArkBootstrap是由AppClassLoader加载的,然后会创建一个新的ContainerClassLoader来加载容器相关的类。容器启动过程中会为每一个plugin和biz模块都创建一个对应的PluginClassLoader和BizClassLoader,注意是每一个plugin都有一个独立的PluginClassLoader加载,biz模块也一样。这样就做到了类隔离。

    因此SOFAArk启动后,会有一个ContainerClassLoader,若干个PluginClassLoader和BizClassLoader。

    image

    四、启动分析

    4.1 源码分析

    4.1.1 通过监听springboot的启动事件,来启动ark容器

    public void onApplicationEvent(SpringApplicationEvent event) {
        。。。
        startUpArk(event);
        。。。
    }
    
    public void startUpArk(SpringApplicationEvent event) {
        if (LAUNCH_CLASSLOADER_NAME.equals(this.getClass().getClassLoader().getClass().getName())) {
            // zy 1 启动sofaark
            SofaArkBootstrap.launch(event.getArgs());
        }
    }
    

    4.1.2 SofaArkBootstrap的launch方法通过一个单线程启动容器

    public static void launch(String[]args) {
    try {
        if (!isSofaArkStarted()) {
            entryMethod =new EntryMethod(Thread.currentThread());
            IsolatedThreadGroup threadGroup =new IsolatedThreadGroup(
            entryMethod.getDeclaringClassName());
            // zy 2 启动的runner方法, MAIN_ENTRY_NAME = "remain"
            LaunchRunner launchRunner =new LaunchRunner(SofaArkBootstrap.class.getName(),
    MAIN_ENTRY_NAME,args);
            Thread launchThread =new Thread(threadGroup, launchRunner, entryMethod.getMethodName());
            // zy 3 单线程启动sofaark
            launchThread.start();
            LaunchRunner.join(threadGroup);
            threadGroup.rethrowUncaughtException();
            System.exit(0);
        }
    }catch (Throwable e) {
        throw new RuntimeException(e);
    }
    }
    

    4.1.3 线程里面调用了这个LaunchRunner.run方法,当前这个类LaunchRunner由AppClassLoader加载生成。代码中注释可以看到最终又去执行了SofaArkBootstrap.remain方法

    public void run() {
        。。。
        // zy 4 sun.misc.Launcher$AppClassLoader
        ClassLoader classLoader = thread.getContextClassLoader();
        try {
            Class startClass = classLoader.loadClass(this.startClassName);
            Method entryMethod;
            try {
                entryMethod = startClass.getMethod(startMethodName,String[].class);
            }catch (NoSuchMethodException ex) {
            entryMethod = startClass.getDeclaredMethod(startMethodName,String[].class);
        }
        if (!entryMethod.isAccessible()) {
        entryMethod.setAccessible(true);
        }
        // zy 5 执行SofaArkBootstrap.remain方法
        entryMethod.invoke(null,new Object[] {this.args });
        。。。
    }
    

    4.1.4 回到SofaArkBootstrap类的remain方法,在第7步执行了ClasspathLauncher.launch方法

        private static void remain(String[] args) throws Exception {// NOPMD
            AssertUtils.assertNotNull(entryMethod, "No Entry Method Found.");
            // zy 6 系统环境变量目录
            URL[] urls = getURLClassPath();
            // zy 7 lunch启动sofaark
            new ClasspathLauncher(new ClassPathArchive(entryMethod.getDeclaringClassName(),
                entryMethod.getMethodName(), urls)).launch(args, getClasspath(urls),
                entryMethod.getMethod());
        }
    

    4.1.5 下面代码再AbstractLauncher.launch方法的第8步,创建了一个ContainerClassLoader类加载器,后面会用来加载SOFAArk相关的类

        // zy 参数method是SofaArkBootstrap.remain方法
        public Object launch(String[] args, String classpath, Method method) throws Exception {
            。。。
            // zy 8 ContainerClassLoader的父cls是null
            ClassLoader classLoader = createContainerClassLoader(getContainerArchive());
            。。。
            return launch(attachArgs.toArray(new String[attachArgs.size()]), getMainClass(),
                classLoader);
        }
    

    上面代码调用了下面的第二个launch方法,最终createMainMethodRunner方法返回的是MainMethodRunner这个工具类的实例,MainMethodRunner只是一个执行器,它实际是为了执行ArkContainer类的main方法。

        /** zy
         * mainClass是com.alipay.sofa.ark.container.ArkContainer
         * classLoader是ContainerClassLoader
         */
        protected Object launch(String[] args, String mainClass, ClassLoader classLoader)
                                                                                         throws Exception {
            // zy 9 old是sun.misc.Launcher$AppClassLoader
            ClassLoader old = Thread.currentThread().getContextClassLoader();
            try {
                // zy 10 classLoader是ContainerClassLoader, 其父cls是null
                Thread.currentThread().setContextClassLoader(classLoader);
                return createMainMethodRunner(mainClass, args).run();
            } finally {
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    
        protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args) {
            return new MainMethodRunner(mainClass, args);
        }
    

    我们看下MainMethodRunner类的代码,类里面我已经分场景注释了,也就是后面的代码通过这个工具类最终调用了ArkContainer.main()

        /** zy com.alipay.sofa.ark.container.ArkContainer */
        private final String   mainClassName;
        private final String[] args;
    
        public MainMethodRunner(String mainClass, String[] args) {
            this.mainClassName = mainClass;
            this.args = (args == null ? null : args.clone());
        }
    
        public Object run() throws Exception {
            /** zy
             * arkContainer启动时, mainClass是ContainerClassLoader
             * biz module启动时, mainClass是BizClassLoader
             */
            Class<?> mainClass = Thread.currentThread().getContextClassLoader()
                .loadClass(this.mainClassName);
            Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
            /** zy
             * arkContainer启动时, 执行com.alipay.sofa.ark.container.ArkContainer.main
             * biz module启动时, 执行模块里面的main方法
             */
            return mainMethod.invoke(null, new Object[] { this.args });
        }
    

    4.1.6 回到AbstractLauncher的第二个launch方法,可以看到,在第11步调用了ArkContainer.main方法

        /** zy
         * mainClass是com.alipay.sofa.ark.container.ArkContainer
         * classLoader是ContainerClassLoader
         */
        protected Object launch(String[] args, String mainClass, ClassLoader classLoader)
                                                                                         throws Exception {
            // zy 9 old是sun.misc.Launcher$AppClassLoader
            ClassLoader old = Thread.currentThread().getContextClassLoader();
            try {
                // zy 10 classLoader是ContainerClassLoader, 其父cls是null
                Thread.currentThread().setContextClassLoader(classLoader);
                // zy 11 执行ArkContainer.main
                return createMainMethodRunner(mainClass, args).run();
            } finally {
                Thread.currentThread().setContextClassLoader(old);
            }
        }
    

    4.1.7 进入ArkContainer.main方法,在第13步执行启动容器代码

        /** zy 当前类属于ContainerClassLoader, 其父cls是null */
        public static Object main(String[] args) throws ArkRuntimeException {
            if (args.length < MINIMUM_ARGS_SIZE) {
                throw new ArkRuntimeException("Please provide suitable arguments to continue !");
            }
            。。。
            // zy 12 解析bizJar,className,methodName,classpath,profile等参数
            LaunchCommand launchCommand = LaunchCommand.parse(args);
            if (launchCommand.isExecutedByCommandLine()) {
                。。。
                // zy 13 启动容器; 当前属于ContainerClassLoader, 其父cls是null
                return new ArkContainer(executableArchive, launchCommand).start();
            }
            。。。
        }
    

    4.1.8 进入ArkContainer.start方法,依次添加了jvm钩子、初始化配置、初始化log配置、启动ArkServiceContainer以及执行Pipeline。接下来依次详解ArkServiceContainer启动流程和Pipeline执行流程

        public Object start() throws ArkRuntimeException {
            。。。
            if (started.compareAndSet(false, true)) {
                // zy 14 JVM关闭时, 优雅的关闭arkContainer
                Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                    @Override
                    public void run() {
                        stop();
                    }
                }));
                // zy 15 读取bootstrap.properties以及bootstrap-%s.properties中的属性, 并放入到ArkConfigs.CFG
                prepareArkConfig();
                // zy 16 初始化log相关配置
                reInitializeArkLogger();
                // zy 17 启动Ark Service Container
                arkServiceContainer.start();
                // zy 18 StandardPipeline
                Pipeline pipeline = arkServiceContainer.getService(Pipeline.class);
                // zy 19 执行pipeline
                pipeline.process(pipelineContext);
                。。。
        }
    

    4.1.9 首先进入ArkServiceContainer.start方法,在17.2步注册了pipeline相关的服务,哪些服务后面会说到,另外这里使用的是Guice框架。在17.3步,注册了biz模块管理,注入,异步事件服务。

        public void start() throws ArkRuntimeException {
            if (started.compareAndSet(false, true)) {
                    。。。
                    // zy 17.2 这里Modules值是 com.alipay.sofa.ark.container.guice.ContainerModule
                    injector = Guice.createInjector(findServiceModules());
                    。。。
    
                    /** zy 17.3 其中arkServiceList包含如下服务:
                     *     @see PluginDeployServiceImpl    空执行
                     *     @see BizDeployServiceImpl       空执行
                     *     @see ClassLoaderServiceImpl     生成arkClassLoader,systemClassLoader等
                     *     @see StandardTelnetServerImpl   开启telnet服务
                     */
                    for (ArkService arkService : arkServiceList) {
                        arkService.init();
                    }
    
                    // zy 17.4 注册biz模块管理,注入,异步事件服务
                    ArkServiceContainerHolder.setContainer(this);
                    ArkClient.setBizFactoryService(getService(BizFactoryService.class));
                    ArkClient.setBizManagerService(getService(BizManagerService.class));
                    ArkClient.setInjectionService(getService(InjectionService.class));
                    ArkClient.setEventAdminService(getService(EventAdminService.class));
                    ArkClient.setArguments(arguments);
                    。。。
            }
        }
    

    4.1.10 上面提到的findServiceModules代码最终注入的是下面这个模型,下面可以清楚的看到添加了插件、biz模块、扩展点、事件等服务。这些会在pipeline执行以及容器运行期会用到。

    public class ContainerModule extends AbstractArkGuiceModule {
    
        @Override
        protected void configure() {
            binder().bind(Pipeline.class).to(StandardPipeline.class);
    
            Multibinder<ArkService> arkServiceMultibinder = Multibinder.newSetBinder(binder(),
                ArkService.class);
            arkServiceMultibinder.addBinding().to(PluginDeployServiceImpl.class);
            arkServiceMultibinder.addBinding().to(BizDeployServiceImpl.class);
            arkServiceMultibinder.addBinding().to(ClassLoaderServiceImpl.class);
            arkServiceMultibinder.addBinding().to(StandardTelnetServerImpl.class);
    
            binder().bind(PluginManagerService.class).to(PluginManagerServiceImpl.class);
            binder().bind(BizManagerService.class).to(BizManagerServiceImpl.class);
            binder().bind(ClassLoaderService.class).to(ClassLoaderServiceImpl.class);
            binder().bind(PluginDeployService.class).to(PluginDeployServiceImpl.class);
            binder().bind(BizDeployService.class).to(BizDeployServiceImpl.class);
            binder().bind(RegistryService.class).to(RegistryServiceImpl.class);
            binder().bind(InjectionService.class).to(InjectionServiceImpl.class);
            binder().bind(TelnetServerService.class).to(StandardTelnetServerImpl.class);
            binder().bind(BizFactoryService.class).to(BizFactoryServiceImpl.class);
            binder().bind(PluginFactoryService.class).to(PluginFactoryServiceImpl.class);
            binder().bind(ExtensionLoaderService.class).to(ExtensionLoaderServiceImpl.class);
            binder().bind(EventAdminService.class).to(EventAdminServiceImpl.class);
        }
    }
    

    4.1.11 接下来我们进入pipeline的代码StandardPipeline.process,执行代码很简单,注释上也说明了执行了哪些Stage以及他们的功能。

        public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
            for (PipelineStage pipelineStage : stages) {
                    。。。
                    /**
                     * zy
                     *
                     * @see HandleArchiveStage   注册plugin和biz模块, 仅注册不部署
                     * @see RegisterServiceStage plugin,模块和事件这些内部服务不可被覆盖, 因此先发布
                     * @see ExtensionLoaderStage 扩展服务
                     * @see DeployPluginStage    部署插件
                     * @see DeployBizStage       部署biz模块
                     * @see FinishStartupStage   结束, 里面主要发送一个结束事件
                     *
                     */
                    pipelineStage.process(pipelineContext);
                    。。。
            }
        }
    

    4.1.12 我们分析下DeployPluginStage的代码,第一步是解析导入导出相关的类、加载器等资源,第二步是部署插件。

        public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
            // zy 解析并缓存, 插件中导入导出的类,类加载和资源等
            classloaderService.prepareExportClassAndResourceCache();
            // zy 部署插件
            pluginDeployService.deploy();
        }
    

    4.1.13 首先我们看下,第一步解析相关的代码,可以看到整个代码逻辑是比较清晰的,就是根据不同的类型进行解析。

        public void prepareExportClassAndResourceCache() {
            for (Plugin plugin : pluginManagerService.getPluginsInOrder()) {
                for (String exportIndex : plugin.getExportPackageNodes()) {
                    exportNodeAndClassLoaderMap.putIfAbsent(exportIndex, plugin.getPluginClassLoader());
                }
                for (String exportIndex : plugin.getExportPackageStems()) {
                    exportStemAndClassLoaderMap.putIfAbsent(exportIndex, plugin.getPluginClassLoader());
                }
                for (String exportIndex : plugin.getExportClasses()) {
                    exportClassAndClassLoaderMap
                        .putIfAbsent(exportIndex, plugin.getPluginClassLoader());
                }
                for (String resource : plugin.getExportResources()) {
                    exportResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
                    exportResourceAndClassLoaderMap.get(resource).add(plugin.getPluginClassLoader());
                }
                for (String resource : plugin.getExportPrefixResourceStems()) {
                    exportPrefixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
                    exportPrefixStemResourceAndClassLoaderMap.get(resource).add(
                        plugin.getPluginClassLoader());
                }
                for (String resource : plugin.getExportSuffixResourceStems()) {
                    exportSuffixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
                    exportSuffixStemResourceAndClassLoaderMap.get(resource).add(
                        plugin.getPluginClassLoader());
                }
            }
        }
    

    4.1.14 然后我们看下,第二步插件部署逻辑PluginDeployServiceImpl.deploy代码,按照顺序,循环部署。

        public void deploy() throws ArkRuntimeException {
            for (Plugin plugin : pluginManagerService.getPluginsInOrder()) {
                。。。
                deployPlugin(plugin);
                。。。
            }
        }
    
        private void deployPlugin(Plugin plugin) throws ArkRuntimeException {
            。。。
            plugin.start();
            。。。
        }
    

    4.1.15 继续往下看,在PluginModel.start方法中,整个代码逻辑是用插件自己的类加载器PluginClassLoader加载了插件中实现的启动类PluginActivator,最后通过执行pluginActivator.start来启动插件。

        public void start() throws ArkRuntimeException {
            。。。
            EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService(
                EventAdminService.class);
    
            // zy 每个插件都有一个自己的类加载器: PluginClassLoader
            ClassLoader oldClassLoader = ClassLoaderUtils
                .pushContextClassLoader(this.pluginClassLoader);
            try {
                eventAdminService.sendEvent(new BeforePluginStartupEvent(this));
                // zy 使用自己的PluginClassLoader加载插件的启动类
                pluginActivator = (PluginActivator) pluginClassLoader.loadClass(activator)
                    .newInstance();
                // zy 调用start方法, 启动插件
                pluginActivator.start(pluginContext);
            } catch (Throwable ex) {
                throw new ArkRuntimeException(ex.getMessage(), ex);
            } finally {
                eventAdminService.sendEvent(new AfterPluginStartupEvent(this));
                ClassLoaderUtils.popContextClassLoader(oldClassLoader);
    
            }
        }
    

    4.1.16 至此插件的启动依然清晰,4.8.3中剩下biz模块也比较重要,我们再继续分析下,进入到DeployBizStage代码,不同于插件部署,biz模块部署后会通过eventAdminService.sendEvent发送一个事件,用于通知相关的事件钩子。

        public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
            String[] args = pipelineContext.getLaunchCommand().getLaunchArgs();
            bizDeployService.deploy(args);
            eventAdminService.sendEvent(new AfterFinishDeployEvent());
        }
    

    4.1.17 继续看bizDeployService.deploy的代码,init方法就是给变量赋值了,没什么。继续跟bizDeployer.deploy的代码

        public void deploy(String[] args) throws ArkRuntimeException {
            ServiceReference<BizDeployer> serviceReference = registryService
                .referenceService(BizDeployer.class);
            bizDeployer = serviceReference.getService();
            。。。。
            bizDeployer.init(args);
            bizDeployer.deploy();
        }
    

    4.1.18 进入bizDeployer.deploy的代码,可以看到和插件部署类似,也是按照顺序,循环启动biz模块。

        public void deploy() {
            for (Biz biz : bizManagerService.getBizInOrder()) {
                。。。
                biz.start(arguments);
                。。。
            }
        }
    

    4.1.19 进入BizModel.start方法,依然使用了MainMethodRunner这个工具类来启动当前biz模块,参数mainClass是biz模块中有main方法的启动类,是在创建BizModel对象的时候设置的,jar包的解析流程,后面我会再写一篇文章分析下。这里和插件启动一样,每个biz模块也会由一个独立的BizClassLoader负责加载模块所有的类。

        public void start(String[] args) throws Throwable {
            。。。
            // zy 替换成BizClassLoader
            ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(this.classLoader);
            EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService(
                EventAdminService.class);
            。。。
                eventAdminService.sendEvent(new BeforeBizStartupEvent(this));
                resetProperties();
                MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClass, args);
                // zy 部署biz模块
                mainMethodRunner.run();
                // this can trigger health checker handler
                eventAdminService.sendEvent(new AfterBizStartupEvent(this));
            。。。
        }
    

    4.2 启动流程回顾

    从下面的类图中可以总结出启动过程中最重要的三个类:SofaArkBootstrap、ArkContainer以及ArkServiceContainer,插件和biz模块都是在ArkServiceContainer中通过pipeline机制,部署起来的。


    image.png

    4.3 类加载

    从上面的代码分析可以看到,SOFAArk里面的ContainerClassLoader、PluginClassLoader以及BizClassLoader,都是打破了双亲委派机制,采用了一种分发机制去加载类。
    以BizClassLoader为例,如下图所示,会根据类的类型,使用不同的方式加载类。所以SOFAArk里面的类加载是一种分发加载机制。


    image.png

    相关文章

      网友评论

          本文标题:SOFAArk启动源码分析

          本文链接:https://www.haomeiwen.com/subject/siyjgctx.html