$ ls -l

JBoss Application Server 7 内部架构概述

27 Feb 2015

本文由关冕译自:AS 7 Internal Architecture Overview,转载请注明出处。

概述

AS 7包括两个主要内容:

AS中包含了两个用于管理其所暴露接口的客户端(一个命令行工具和一个基于Web的管理控制台)。AS代码库还带有由Arquillian项目发布的SPIs的实现,允许AS7服务器运行基于Arquillian的测试。

AS核心

AS的核心由以下主要元素构成:

AS扩展

与最终用户相关的应用服务器的大部分功能都由AS扩展提供。AS7代码库中的大部分模块均是一个扩展实现,每个扩展提供了一组内聚的功能。很多扩展提供了对于Java EE规范的某些方面的支持。

扩展实现了org.jboss.as.controller.Extension接口从而允许与核心AS管理层集成。通过该机制,扩展能够:

关于扩展的更多信息请参阅“Extending JBoss AS 7”文档。关于扩展AS7与扩展JBoss AS之前版本的差异的更多信息可以参阅https://community.jboss.org/docs/DOC-25627。

AS7启动过程

为了更好地说明AS7的架构,让我们看一下一个独立服务器(standalone server)的启动过程。

原始启动(Primordial Boot)

当你在AS 7发行版的根目录下运行bin/standalone.sh的时候,最终的效果是用下列基本命令来启动一个JVM:

java -jar jboss-modules.jar -mp modules org.jboss.as.standalone
如果你启动了AS并且使用ps命令查看实际的JVM启动命令,你会看到很多JVM设置(-Xmx和类似的一些参数)和很多系统属性设置,但是以上命令是最关键的信息。让我们分解说明一下:
java -jar jboss-modules.jar
从这里可以看出VM调用的实际主类并不在JBoss AS类库中,而是在jboss-modules.jar中(org.jboss.modules.Main类)。所以,当你启动AS的时候你在做的第一件事是设置一个模块化的类加载环境。
-mp modules
这是传给org.jboss.modules.Main.main()方法的参数。-mp是“module path”的简写,它的值是一个路径,在某种程度上类似于操作系统$PATH环境变量的值。这个路径中的每一项对应一个位置,jboss-modules在需要时在该位置下查找模块。如果这个路径中存在一个以上的项目,这些项目将以顺序的方式被搜索,一旦模块被找到,搜索过程结束。在当前情况下路径中只有一个项目——AS发行版根目录下的modules/目录
org.jboss.as.standalone
这是位于模块路径上一个模块的名字。这个模块将被jboss-modules加载。如果你查看AS发行版中的modules/org/jboss/as/standalone/main/module.xml文件,你会看到它包含以下元素:
main-class name="org.jboss.as.server.Main"
当jboss-modules发现传递给它main()方法的模块中的module.xml文件中的元素时,它知道要加载指定的类并且将任何剩余的命令行参数传递给该模块的main()方法。所以,我们现在建立起了一个模块化的类加载环境,并且org.jboss.as.server.Main.main()方法(可以在AS代码库的server模块找到)已经被调用了。

org.jboss.as.server.Main.main()方法做了很多事情,但是对于希望了解如何开发AS的人来讲最相关的两件事情是:

基于服务的启动(Service-based Boot)

BootstrapImpl做的最重要的事情是创建了JBoss MSC库中ServiceContainer接口的一个实例。一个服务容器管理着一组正在运行的服务。服务可以通过JBoss MSC服务接口中指定的方法进行启动和停止。通过JBoss MSC服务接口的public T getValue()方法,服务可以暴露某种类型T的值。由服务指定的值类型T默认被服务的消费者使用,并应该代表服务的公共接口。值类型T与服务的实现类型可能相同或不同。

ServiceContainer提供了一个用于管理服务的API -- 通过它可以配置和安装服务,删除服务,触发启动和停止,查找已经安装的服务,等等。配置一个服务的时候会分析该服务对其他服务的依赖关系,并让ServiceContainer在启动该服务之前向其注入依赖的服务。如果一个服务依赖于另一个服务,在ServiceContainer调用该服务的start方法之前,其他服务必须已经启动成功。如果由于某种原因被依赖的服务需要被停止,ServiceContainer将首先调用依赖服务的stop方法。

ServiceContainer维护了一个内部线程池。为了实现对大量相互关联的服务的高性能管理,与启动和停止服务相关的活动被作为一系列任务(concisely scoped tasks),线程池中的线程用来执行这些任务。例如,执行一个服务的start方法将被作为一个任务由ServiceContainer线程池中的一个线程执行。基于这一事实,与启动和停止服务相关的任何活动将是高度多线程的,认识到这一点非常重要。例如,你永远不能假设让ServiceContainer安装一个服务的线程与调用该服务的start方法的线程是同一个线程。

到目前为止,我们已经提到了上文“AS核心”中讨论的四点主要架构元素中的两点:由jboss-modules库提供的模块化类加载系统和由jboss-msc库提供的快速,高度可扩展的服务容器框架。

截止目前在AS启动过程中,所有活动在一个单独的线程上,JVM的main线程。在BootstrapImpl.bootstrap()方法中,事情变得更有趣起来,大量剩余的启动工作,包括安装服务,由ServiceContainer线程池中的线程完成。

BootstrapImpl.bootstrap()方法安装了两个服务:

ApplicationServerService在它的start方法中启动了许多其它服务。在这些服务中,最重要的是ServerService。

ServerService和Controller Boot Thread

ServerService引入了上文“AS核心”中讨论的四点主要元素中的第三点和第四点 -- 可扩展的管理层和部署框架。对于那些熟悉"Extending JBoss AS 7"文档的人来说,ServerService做了许多与一个Extension实现在它的initialize(ExtensionContext context)方法中所做事情同类型的活动,但是对于核心AS管理模型:

ServerService实现了Service接口。ModelController是一个在被管理的AS进程中用于管理操作的中心执行点。(例如,在一个被管理的域中的server或HostController)

ServerService在它的start方法中创建了一个独立的线程,"Controller Boot Thread",它负责协调启动过程的剩余部分。它执行以下主要任务:

Controller Boot Thread执行的任务将在下面进一步详细阐述。

XML解析, 扩展加载, 扩展解析初始化

用于解析AS7配置文件(例如,standalone.xml, domain.xml, host.xml)的XML解析器的任务是生成一系列管理操作,ModelController会执行这些管理操作生成运行期配置。每个管理操作具有相同的格式,通过命令行可以向服务器发送操作请求以调用一个等效的操作。

当XML解析器解析到XML中的元素的时候具有一些特殊的行为:

启动管理操作的执行,扩展初始化

一旦xml解析完成,一系列管理操作准备好由ModelController执行。每个管理操作具有相同的格式(地址,操作名,参数),以便在启动之后通过命令行向服务器发送操作请求来调用一个等效的操作。Controller Boot Thread要求ModelController以一个单元的方式来执行操作,每个单独的操作作为整体工作单元中的一个步骤。执行过程分为3个阶段,整体单元中的所有步骤会在下一阶段开始执行前完成当前阶段。(每当任何单一操作或一系列原子操作被调用时,这种执行模式均适用,不仅仅是在启动过程当中)

在这些启动操作传递到ModelController之前,会对添加扩展资源的操作进行检查(例如,使用命令行语法,/extension=org.foo.extension:add)。当发现这样一个操作的时候,相应扩展实现的initialize(ExtensionContext context)方法会被调用。这种方式给扩展提供了一个机会在启动操作执行之前向核心AS管理层注册它的资源,属性定义和OperationStepHandlers。

等待服务容器稳定和启动完成

AS 7启动过程中最后的步骤是等待JBoss MSC ServiceContainer处理由处理程序(handlers)为启动操作添加的所有服务的安装和启动。正如上文介绍ServiceContainer时所讨论,服务的启动由ServiceContainer内部线程池中的线程执行。服务不是由Controller Boot Thread启动。但是,ServerService在每个服务的控制对象上附加了一个监听器,通过这种方式ServerService能够跟踪在何时所有服务都达到一个稳定的状态。Controller Boot Thread使用此功能实现阻塞,直到ServiceContainer稳定下来。在这一时刻,所有服务或者启动成功或者失败,启动过程已接近完成。Controller Boot Thread将ControlledProcessState服务的状态从STARTING切换到RUNNING,写日志记录启动过程完成,至此启动过程结束。

部署处理

当你触发部署一些内容,调用是核心AS管理层支持的管理操作之一。负责处理该操作的逻辑将从操作请求提取相关信息(例如,部署的名称)然后会安装服务到AS的服务容器。

RootDeploymentUnitService中存在一个指向所有DeploymentUnitProcessor (DUP)实现的引用,这些实现由启动时的核心ServerService或由子系统(subsystems)注册。DUP实现根据部署处理的阶段(Phase)进行分组,它们在阶段中执行,并以数字方式排序。DeploymentUnitProcessors以一个链的形式组织,每个DUP执行一个有限的任务集,以帮助一个部署成为一组有用的服务。

部署工作分阶段进行。在Phase enum查看阶段的列表。对于每个阶段,一个DeploymentUnitPhaseService代表了被安装到服务容器的阶段。每个阶段服务(排除第一个)依赖于前一个阶段的阶段服务,每个阶段服务(排除最后一个)在它的start方法中为下一个阶段安装阶段服务。RootDeploymentUnitService在它的start方法中安装第一个阶段服务,而这又依赖于RootDeploymentUnitService。所有这一切的作用是如果RootDeploymentUnitService被停止(例如,通过undeploy管理操作),这将要求服务容器首先停止第一个阶段服务,这将反过来触发要求先停止下一个阶段服务,依此类推一直到最后一个阶段服务。其效果是阶段服务将按照与他们启动时相反的顺序被停止。

阶段服务在他们的启动和停止方法中做的首要事情是调用每个DeploymentUnitProcessor为它们的阶段注册的deploy和undeploy方法。deploy方法在start方法中被调用,undeploy方法在stop方法中被调用。对于每个deploy/undeploy调用,DUP设置有一个DeploymentPhaseContext,它提供调用的上下文并允许DUP访问服务容器,安装或删除服务。

部署处理和模块

DeploymentUnitProcessors执行的任务之一是为部署建立模块化的类加载环境。每个顶级的部署都有自己的动态生成的模块。对于包括已知子部署的部署类型(例如,一个ear可以包括wars, ejb jars, 等等)这些子部署也具有自己的动态生成模块。这些部署模块可以看到哪些其他模块取决于部署框架在分析部署内容时所确定的需求(例如,通过解析部署描述符,读取清单,或扫描注解)。

在这个处理过程中的核心角色是org.jboss.as.server.deployment.module.ModuleSpecProcessor,它是一个DeploymentUnitProcessor。ModuleSpecProcessor配置并安装了一个MSC服务,该服务与jboss-modules进行交互,以动态地生成部署模块。其他先于ModuleSpecProcessor执行的DeploymentUnitProcessors分析部署的内容并向DeploymentUnit添加上下文信息,DeploymentUnit被ModuleSpecProcessor用于确认该部署模块可以访问哪些其他模块。因此,例如,一个由JPA子系统注册的DUP可能认识到部署需要JPA支持(例如,通过检测persistence.xml文件的存在)并记录应该添加提供JPA APIS的模块对该部署的可见性。所有这一切的作用是部署的类可以访问位于AS自己的模块或在其他部署中的一组合适的类,但不可以访问位于其它模块中的类。

Tweet