Wildfly-配置-部署和管理指南-全-

Wildfly 配置、部署和管理指南(全)

原文:zh.annas-archive.org/md5/c3f9cb4c55103099ae159521114a9172

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

WildFly 是 JBoss AS 社区版本的新的名称。WildFly 仍然是市面上最受欢迎的 Java 企业服务器。它易于使用,拥有干净的管理界面、强大的命令行工具、模块化架构、轻量级且速度极快。如果你需要产品支持,很容易从 WildFly 迁移到 JBoss EAP 服务器,并且重要的是,许可证和支持成本不会让你破产。

本书通过介绍如何下载和安装服务器,温和地引导你了解 WildFly。然后,我们继续探讨配置企业服务和各种子系统,以及如何保护你的服务器和应用程序。随着本书的深入,主题变得越来越高级,因此在书的后期部分,我们探讨了通过集群和负载均衡实现的高可用性。

不论你是希望提高 WildFly 知识水平的 Java 开发者,还是想要更深入理解 WildFly 内部运作的服务器管理员,这本书中都有适合你的内容。

本书涵盖的内容

第一章, 安装 WildFly,介绍了 WildFly 服务器。你将学习 Java 和 WildFly 的安装,并学习如何启动、停止和重启服务器。你还将发现 WildFly 安装目录内各种文件夹的用途,并基本了解 WildFly 内核。最后,你将学习如何在 Eclipse 中安装 WildFly 服务器适配器。

第二章, 配置 WildFly 核心子系统,详细描述了独立配置文件的结构。你将了解模块和子系统的概念,然后你将详细了解如何配置两个核心子系统,即日志子系统和服务线程池子系统。

第三章, 配置企业服务,教你如何配置企业服务和组件,例如事务、连接池、企业 JavaBeans 和 JMS。你还将学习如何安装 JDBC 驱动程序和配置数据库连接。

第四章, Undertow Web 服务器,解释了 Undertow 的架构,并教你如何配置 servlet 容器。你还将学习如何提供静态内容并配置 JSP。在本章中,我们使用 JSF、EJB 和 JPA 创建了一个简单的 Web 应用程序,并教你如何使用 Eclipse WildFly 服务器适配器部署应用程序。

第五章, 配置 WildFly 域,教你如何管理 WildFly 域。它涵盖了域和主机控制器配置,并概述了服务器域与多个独立服务器实例之间的区别。

第六章, 应用程序结构和部署,解释了 Web 和企业归档的结构以及它们的打包方式。你还将详细了解将你的应用程序部署到 WildFly 服务器的方法。我们还解释了在 WildFly 中类加载是如何工作的。

第七章, 使用管理接口,介绍了更多高级的命令行界面命令,例如添加数据源和配置 JMS 的命令。我们还提供了一个关于 Web 管理控制台的高级概述。

第八章, 集群,提供了关于集群独立和域服务器的详细示例。你还将了解如何使用 JGroups 来集群企业组件,如消息传递和 Hibernate。

第九章, 负载均衡 Web 应用,解释了使用 Apache Web 服务器与 WildFly 结合使用的优势。你还将学习如何使用 mod_jk、mod_proxy 和 mod_cluster 来负载均衡你的 Web 应用。

第十章, 保护 WildFly,教你如何配置安全子系统。我们涵盖了各种登录模块,如数据库登录和 LDAP。我们还探讨了如何保护企业组件,如企业 JavaBeans 和 Web 服务。然后我们来看如何保护管理接口。

第十一章, WildFly、OpenShift 和云计算,讨论了云计算的进步以及它为公司带来的好处。我们探讨了如何使用 OpenShift 来简化软件开发并帮助快速将应用部署到云服务器。

附录, CLI 参考,提供了一些在 CLI 中常用命令的快速参考。

你需要为此书准备的东西

预期具备 Java 知识。一些企业 Java 知识会有帮助,尽管不是必需的。要运行 WildFly,你需要以下软件:

  • JDK 8

  • WildFly 8

  • MySQL(如果你配置了 MySQL 数据源)

如果你希望运行本书中的 Java 代码示例,你还需要:

  • Maven 3

  • 一个 IDE(本书中使用 Eclipse)

  • MySQL

本书面向的对象

本书面向 Java 开发者、系统管理员以及任何想要了解更多关于如何配置 WildFly 8 服务器的人。对于那些对 WildFly 服务器是新手的人来说,本书也很适合,因为本书假设没有先前的经验。本书逐步深入到高级概念,这意味着它也适合更有经验的系统管理员和开发者。

规范

在本书中,你会发现许多不同风格的文本,用于区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称会像以下这样显示:“此文件的内容将包括由context-root元素指定的自定义 Web 上下文。”

代码块会这样设置:

<servers>
    <server name="server-one" group="other-server-group">
        <socket-bindings socket-binding-group="ha-sockets"/>
    </server>
</servers>

当我们希望您注意代码块中的特定部分时,相关的行或项目会以粗体显示:

public class RemoteEJBClient { 
    static { 
        Security.addProvider(new JBossSaslProvider());
    }
    public static void main(String[] args) throws Exception { 
        super();
    }
}

任何命令行输入或输出会这样写:

[standalone@localhost:9990 /] data-source remove --name=MySQLPool

新术语重要词汇会以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,会在文本中这样显示:“同意条款并点击确定。”

注意

警告或重要提示会以这样的框出现。

小贴士

小贴士和技巧会像这样出现。

读者反馈

我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大价值的标题非常重要。

要向我们发送一般反馈,只需发送一封电子邮件到<feedback@packtpub.com>,并在邮件的主题中提及书名。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在,您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。

下载示例代码

您可以从您在www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

错误清单

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进此书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误清单提交表单链接,并输入您的错误清单详情。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support选择您的标题来查看。

侵权

互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过链接版权问题与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者以及为我们提供有价值内容的能力方面的帮助。

问题

如果您在本书的任何方面遇到问题,可以通过问题咨询与我们联系,我们将尽力解决。

第一章。安装 WildFly

自 Java 首次发布以来,Java 语言经历了许多变化,并将继续适应以满足开发者的需求。2010 年收购 Sun 的 Oracle 表示,其高级 Java 策略是增强和扩展 Java 到新的和新兴的软件开发目标;简化、优化并将 Java 平台集成到新的部署架构中;以及投资于 Java 开发者社区,以增加参与度。

这在 Java 企业版中确实如此,其重点是提高开发者的生产力,提供对 HTML5 的支持,并满足企业需求。在所有企业 Java 版本中,Java EE 7 是最透明且最开放于社区参与的。通过允许公开反馈,社区的需求可以得到实现并用于帮助塑造 Java EE 7,从而最终促进企业 Java 的增长和成功。

此外,应用程序服务器内部使用了大量开源项目,例如 Hibernate 和 Undertow。整合所有这些库并非没有代价,因为每个库都随着复杂性的增加而需要越来越多的附加库来工作。

如大多数 IT 专家所同意,今天应用程序服务器的挑战在于结合客户所需的一套丰富功能,以及轻量级和灵活的容器配置。

WildFly 8 的新特性有哪些?

WildFly 8 是 JBoss AS 项目的直接延续。对 JBoss AS 社区版本的更名是为了减少开源 JBoss 服务器、JBoss 社区和 JBoss 企业应用平台JBoss EAP)之间的混淆。WildFly 8 是免费和开源的,由 JBoss 社区提供支持,而 JBoss EAP 是一个带有 RedHat 支持的授权产品。

WildFly 8 相较于早期版本最显著的更新如下:

  • Java EE7 认证:WildFly 是一个完全符合 Java EE 企业服务器的产品,这意味着它为构成 Java EE 7 的所有 Java 规范请求JSRs)提供了参考实现。JSRs 基本上是针对 Java 语言的变更请求。有关 JSRs 如何工作的更多信息,请参阅 www.jcp.org/en/jsr/overview

  • Undertow 的到来:JBoss Web 已完全移除并替换为 Undertow。Undertow 是一个支持非阻塞和阻塞处理程序、WebSocket 和异步 Servlet 的前沿 Web 服务器。它被设计用于可扩展性和最大吞吐量。它易于使用,易于配置,并且高度可定制。

  • 端口减少:WildFly 中的开放端口数量已大幅减少。只有两个端口开放:8080 和 9990。这是通过使用 Undertow 的 HTTP 升级功能在 HTTP 上多路复用协议来实现的。

  • 安全管理器:你现在可以按部署配置安全权限。

  • 日志记录:WildFly 日志记录已经进行了多项增强。你现在可以通过管理界面查看日志文件,定义自定义格式化程序,并按部署配置日志记录。

  • 集群:WildFly 的集群进行了大量重构,并包括许多新特性,包括 Web 会话、单点登录和 Undertow 的 mod_cluster 支持。还有一个新的公共集群 API 和新的@Stateful EJB 缓存实现。

  • 命令行界面(CLI):你现在在连接到服务器时可以定义别名,CLI GUI 还增加了额外的功能,允许你探索树中的任何节点。

在本章中,我们将涵盖以下主题:

  • 安装 Java 环境

  • 安装 WildFly 8

  • 安装 JBoss 工具

  • 探索应用服务器文件系统

  • 理解 WildFly 内核

开始使用应用服务器

在硬件要求方面,你应该知道,在撰写本文时,服务器发行版需要大约 150 MB 的硬盘空间,并为独立服务器分配了最小 64 MB 和最大 512 MB。

为了开始,我们将执行以下步骤:

  1. 下载并安装 Java 开发工具包。

  2. 下载并安装 WildFly 8。

  3. 下载并安装 Eclipse 开发环境。虽然我们将在本书中使用 Eclipse,但你完全可以选择你喜欢的 IDE。

在本章结束时,你将安装所有必要的软件,并准备好开始使用应用服务器。

安装 Java 环境

WildFly 是用 Java 编写的;因此,它需要一个Java 虚拟机JVM)来运行,以及标准版 Java 库。因此,在我们开始设置或学习 WildFly 之前,我们首先需要安装Java 开发工具包JDK)。

要使用 WildFly,你需要至少 Java SE 7 或更高版本。尽管在 WildFly 8.x 源代码中没有计划使用 Java 8 语言变更,但 WildFly 是针对 Java 8 编译的。建议您使用 Java SE 8 的最新版本来运行 WildFly。

因此,让我们转到 Oracle 下载页面,www.oracle.com/technetwork/java/javase/downloads/index.html,它现在托管所有 JDK 下载,如下面的截图所示:

安装 Java 环境

这将带你去最新 JDK 的下载页面。在撰写本文时,这是 Java 8 更新 5。在下载 JDK 之前,你需要接受许可协议。选择下载适用于你操作系统的最新 Java 版本。查看以下截图:

安装 Java 环境

下载时间取决于你的网络速度。

在 Linux 上安装 Java

在 Linux 上安装 Java 非常简单。下载完成后,将tar.gz文件解压到您选择的安装位置。以下命令将存档提取到您的当前目录:

tar -xzvf jdk-8u5-linux-x64.tar.gz

接下来,您需要将路径添加到环境变量中。这可以通过将以下行添加到您的用户配置文件脚本(位于您家目录中的.profile文件)来实现:

export JAVA_HOME=/installDir/jdk1.8.0_05
export PATH=$JAVA_HOME/bin:$PATH

在 Windows 上安装 Java

Windows 用户可以直接运行可执行文件(.exe)来启动安装。安装程序的名字取决于操作系统和您的系统架构(32 位或 64 位);然而,步骤将是相同的——只是名字会变化。在撰写本文时,64 位 Windows 上最新版本的 Java 安装程序名为jdk-8u5-windows-x64.exe

当使用 Windows 时,您应该避免包含空格的安装路径,例如C:\Program Files,因为这会导致在引用核心库时出现一些问题。C:\Software\Java或简单的C:\Java是一个更好的选择。

安装完成后,您需要更新计算机上的几个设置,以便它知道 Java 的位置。最重要的设置是JAVA_HOME,它直接被 WildFly 启动脚本引用。

如果您正在运行 Windows XP/2000,请按照以下步骤操作:

  1. 右键点击我的电脑,从上下文菜单中选择属性

  2. 高级选项卡上,点击环境变量按钮。

  3. 然后,在系统变量框中,点击新建

  4. 将新变量命名为JAVA_HOME,并给出 JDK 安装路径的值;我建议像C:\Java\jdk1.8.0_05这样的路径。在 Windows 上安装 Java

    提示

    Windows 7 技巧

    由于 Windows 7 的安全性提高,标准用户必须开启用户账户控制UAC)以更改环境变量,并且更改必须通过用户账户完成。在用户账户窗口中,在任务下选择更改我的环境变量。使用新建编辑删除按钮来修改环境变量。

  5. 现在是时候修改系统的PATH变量了。双击PATH系统变量。在弹出的框中,导航到变量值行的末尾,添加一个分号,然后添加您的 JDK 路径。这将是类似%JAVA_HOME%\bin的东西。

安装 WildFly 8

WildFly 应用程序服务器可以从 WildFly 网站免费下载,www.wildfly.org/downloads/。请查看以下截图:

安装 WildFly 8

你会注意到有一个选项可以下载一个简约的核心发行版。这个版本是为那些想要使用 WildFly 8 架构构建自己的应用程序运行时环境的开发者准备的。

选择下载完整的 Java EE7 发行版。与 JBoss AS 7 一样,WildFly 不附带安装程序。只需将压缩存档提取到您选择的任何位置即可。

Linux 用户可以使用tarunzip命令提取文件(取决于您下载的压缩文件类型):

tar -xzvf wildfly-8.1.0.Final.tar.gz
unzip wildfly-8.1.0.Final.zip

对于使用 Windows 的用户,您可以使用 WinZip 或 WinRAR,注意选择一个不包含空格的文件夹。

小贴士

安全警告

Unix/Linux 用户应该知道,WildFly 不需要 root 权限,因为 WildFly 使用的默认端口都不低于 1024 的特权端口范围。为了降低用户通过 WildFly 获得 root 权限的风险,请以非 root 用户安装并运行 WildFly。

开始启动 WildFly

安装 WildFly 后,进行简单的启动测试以验证您的 Java 配置没有问题是很明智的。要测试您的安装,请转到 WildFly 安装的bin目录,并执行以下命令:

  • 对于 Linux/Unix 用户:

    $ ./standalone.sh
    
    
  • 对于 Windows 用户:

    > standalone.bat
    
    

以下截图显示了 WildFly 8 的启动控制台:

开始启动 WildFly

以下命令启动了一个与 JBoss AS 7 之前版本中使用的run.sh脚本启动应用程序服务器等效的 WildFly 独立实例。run.sh文件仍然位于 WildFly 的bin目录中,但它只是一个占位符,不会启动应用程序服务器。

注意应用程序服务器启动有多快。这是由于 WildFly 的模块化架构。在启动时,关键服务并发启动,非关键服务仅在需要时启动,从而实现了极快的启动速度。本地缓存意味着服务器在第二次启动时将更快!

如果您需要自定义应用程序服务器的启动属性,则需要打开并修改standalone.conf文件(或 Windows 用户的standalone.conf.bat)。此文件包含 WildFly 的内存需求。以下是它的 Linux 核心部分:

if [ "x$JAVA_OPTS" = "x" ]; then
   JAVA_OPTS="-Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true"
   JAVA_OPTS="$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS -Djava.awt.headless=true"
fi

小贴士

Java SE 8 用户

在 Java 8 中,PermGen 已被 Metaspace 取代。如果您使用的是 Java 8,请从standalone.conf文件中删除-XX:MaxPermSize=256m属性,并将其替换为-XX:MaxMetaspaceSize=256m。这将防止在启动时将 VM 警告打印到您的 WildFly 日志中。

默认情况下,应用程序服务器以 64 MB 的最小堆空间内存需求和 512 MB 的最大需求启动。这足以开始使用;然而,如果您需要在上面运行核心 Java EE 应用程序,您可能至少需要 1 GB 的堆空间。更现实地说,根据您的应用程序类型,您可能需要 2 GB 或更多。一般来说,32 位机器无法执行超过 4 GB 空间的过程;然而,在 64 位机器上,处理过程的大小实际上没有限制。

你可以通过将浏览器指向应用服务器的欢迎页面来验证服务器是否可以从网络访问,默认情况下,该页面可以通过以下知名地址访问:http://localhost:8080。请查看以下截图:

启动 WildFly

使用命令行界面连接到服务器

如果你之前使用过 JBoss AS 7 之前的版本的应用服务器,你可能听说过 twiddle 命令行工具,该工具可以查询应用服务器上安装的 MBeans。这个工具在 JBoss AS 7 中被替换,但在 WildFly 中仍然被使用。它的替代品是一个更复杂的界面,称为 命令行界面CLI),可以在 JBOSS_HOME/bin 文件夹中找到。

提示

关于 JBOSS_HOME 的引用

尽管 JBoss AS 的社区版本已被重命名为 WildFly,但你将看到启动脚本中的属性仍然使用属性 JBOSS_HOME 来引用 WildFly 的安装目录。因此,当我们提到 WildFly 的根安装时,我们将继续使用 JBOSS_HOME

只需启动 jboss-cli.sh 脚本(对于 Windows 用户是 jboss-cli.bat),你就可以通过 shell 界面管理应用服务器,如以下截图所示。请注意,服务器需要运行才能通过 CLI 连接。

使用命令行界面连接到服务器

一旦你进入了 shell 会话,如果你不确定可以发出哪些命令,你可以简单地按一下 Tab 键来显示所有可能的命令。如果你的命令部分输入,并且只有一个可能的匹配命令,你的命令将会自动完成。那些使用 Linux 的用户会对这种命令行辅助方式感到熟悉。

在前面的截图中,我们刚刚使用 connect 命令连接到服务器,默认情况下,该命令使用回环服务器地址,并连接到端口号 9990。

注意

CLI 在 第七章 中进行了深入讨论,使用管理接口,这是关于服务器管理接口的全部内容。在接下来的几节中,我们将初步了解其基本功能,以便让你熟悉这个强大的工具。

停止 WildFly

可能停止 WildFly 的一种最简单的方法是使用 Ctrl + C 发送中断信号。这应该在发出启动命令的同一控制台窗口中完成,即服务器运行的地方。

然而,如果你的 WildFly 进程是在后台启动的或者运行在另一台机器上(见以下章节),那么你可以使用 CLI 界面立即发出 shutdown 命令,如下所示:

[disconnected /] connect
[standalone@localhost:9990 /] shutdown
[disconnected /]

定位关闭脚本

实际上还有一个选项可以关闭应用程序服务器,这在您需要从脚本中关闭服务器时非常有用。此选项包括将 --connect 选项传递给管理外壳,从而关闭交互模式,如下所示:

jboss-cli.sh --connect command=:shutdown       # Unix / Linux
jboss-cli.bat --connect command=:shutdown      # Windows

在远程机器上停止 WildFly

关闭远程机器上运行的应用程序服务器只需连接并提供服务器的远程地址到 CLI:

[disconnected /] connect 192.168.1.10

[192.168.1.10:9990 /] shutdown

注意

通过 CLI 远程访问 WildFly 需要身份验证。有关更多信息,请参阅第十章 Chapter 10. Securing WildFly,Securing WildFly。这也要求远程 WildFly 安装上的管理界面打开以允许远程连接。这在第七章 Chapter 7. Using the Management Interfaces,Using the Management Interfaces 中有详细说明。

重启 WildFly

CLI 包含许多有用的命令。其中最有帮助的选项之一是使用 reload 命令重新加载所有或部分服务器配置。

当在服务器的 root 节点 路径 上执行时,WildFly 会重新加载所有服务配置,如下面的命令所示:

[disconnected /] connect

[standalone@localhost:9990 /] reload

安装 Eclipse 环境

尽管本书的主要重点是 WildFly 应用程序服务器的管理,但我们还关注应用程序打包和部署。因此,我们有时会添加需要安装到您机器上的开发环境的示例。

本书使用的开发环境是 Eclipse。Eclipse 在全球开发者中都很知名,它包含了一个庞大的插件集,建立在其核心功能之上。如果您对其他 IDE 感到舒适,那么请随意使用它,但本书将仅演示 Eclipse。在撰写本书时,只有 Eclipse 和 NetBeans 为 WildFly 提供插件。

因此,让我们转到位于 www.eclipse.org/downloads 的 Eclipse 下载页面。

从此页面下载最新的企业版。该压缩包包含已安装的所有 Java EE 插件,需要大约 248 MB 的磁盘空间。请查看以下截图:

安装 Eclipse 环境

提示

如果您使用 Java 8,请确保您下载 Eclipse Luna (4.4) 或 4.3 的修补版本。

下载 Eclipse 后,将其解压缩到您选择的文件夹。提取的文件夹将被称为 eclipse。要启动 Eclipse,请导航到 eclipse 文件夹并运行:

$ ./eclipse

Windows 用户只需双击 eclipse 文件夹(带有大、蓝色、圆形 Eclipse 图标的文件夹)中的可执行文件。

安装 JBoss tools

下一步是安装 WildFly 8 适配器,它是名为 JBoss tools 的插件套件的一部分。在 Eclipse 中安装新插件非常简单;只需执行以下步骤:

  1. 从菜单中,导航到帮助 | Eclipse 市场 place

  2. 然后,搜索您想要安装的插件(在本例中,键入jboss tools)。

  3. 最后,点击以下截图所示的安装按钮:安装 JBoss 工具

确保您选择与您的 Eclipse 版本匹配的 JBoss 工具版本,例如 Luna 或 Kepler。在本例中,我们使用 Eclipse Luna,因此我选择了 Luna 版本的 JBoss 工具。如果您只想安装 WildFly 适配器,请选择JBossAS 工具。同意条款并点击确定。当提示时重启 Eclipse。

您现在可以通过以下步骤在 Eclipse 中设置 WildFly 服务器:

  1. 导航到新建 | 服务器

  2. 展开JBoss 社区节点。

  3. 选择以下截图所示的选项,WildFly 8安装 JBoss 工具

  4. 确保您选择已安装的 Java 8 JRE。

  5. 将主目录指向 WildFly 根目录,如下截图所示:安装 JBoss 工具

探索应用服务器文件系统

现在我们已经完成了所有必要工具的安装,我们将专注于应用服务器的结构。当您浏览应用服务器文件夹时,您首先会注意到其文件系统基本上分为两个核心部分:这种二分法反映了独立服务器和服务器之间的区别。

域服务器的概念在应用服务器市场上并不新鲜,然而,它仅在 JBoss AS 7 中作为管理并协调一组应用服务器实例的方式被引入。未配置为域一部分的应用服务器节点被认定为独立服务器。在实践中,独立服务器类似于您在 JBoss AS 7 之前的应用服务器版本中看到的应用服务器单个实例。

我们将在第五章配置 WildFly 域中详细讨论域的概念。目前,我们将探索这两种服务器类型的不同文件系统结构。

从宏观角度来看,我们可以看到主要文件系统分为两部分:一部分与域服务器相关,另一部分与独立服务器相关。以下图表展示了应用服务器的树状结构:

探索应用服务器文件系统

在下一节中,我们将更深入地探讨 WildFly 应用服务器的文件夹结构,剖析其内容并查看其用途。

bin 文件夹

bin 文件夹是您将找到所有启动脚本的地方,例如 standalone.shdomain.sh。除了启动脚本外,您还可以找到 standalone.conf,它可以用于自定义 WildFly 的引导过程。

如您之前所见,bin 文件夹还包括 jboss-cli.sh 脚本(Windows 用户为 jboss-cli.bin),该脚本启动交互式 CLI。您还会找到各种其他有用的脚本,例如 add-user.shvault.sh。此文件夹还包含用于生成 Web 服务定义语言及其相应 Java 接口的 Web 服务实用脚本(wsconsume.shwsprovide.sh)。

bin 目录内有几个子目录。service 目录和 init.d 目录包含允许您在 Windows 和 Linux 上分别将 WildFly 作为服务安装的程序。

docs 文件夹

docs 文件夹包含两个子文件夹,examplesschemaschema 文件夹包含配置作为模式使用的所有 .xsd 模式定义文件。

examples 文件夹包含许多配置示例,从简约的独立示例到 ec2 高可用性示例(HA 表示高可用性,ec2 指的是亚马逊弹性计算云)。

域文件夹

下一个文件夹是 domain 文件夹,它包含跨越一组文件夹的域结构:

  • configuration 文件夹包含所有配置文件:

    • 主要配置文件是 domain.xml,它包含域节点使用的所有服务。它还配置了所有服务的套接字绑定接口。

    • 域的关键文件之一是 host.xml,它用于定义主机控制器HC)。

    • 配置文件夹中包含的最后一个文件是 logging.properties,它用于定义进程控制器PC)和主机控制器的引导过程日志格式。

  • content 文件夹用作存储已部署模块的存储库。

  • lib 文件夹托管 ext 子文件夹,它用于支持 Java SE/EE 风格的扩展。一些应用程序服务器部署器能够扫描此文件夹以查找由本地类加载器拾取的附加库。尽管如此,此方法不建议使用,并且仅为了符合语言规范而维护。应使用 modules 文件夹在 WildFly 中安装您的库。

  • 如您所想象,log 文件夹包含域的日志输出。默认情况下,每次服务器重启时,该文件都会被截断。

  • servers 文件夹包含配置文件中定义的每个服务的一组子文件夹。每个服务器下最有用的目录是 log 文件夹,这是单个实例发出日志的位置。

  • data 文件夹由应用程序服务器用于存储其运行时数据,例如事务日志。

  • 最后,tmp 文件夹用于存储服务器写入的临时文件。

standalone 文件夹

如果您以独立模式运行应用程序服务器,这将是在文件系统中您感兴趣的部分。其结构相当类似于 domain 文件夹,唯一的例外是没有 deployment 文件夹。让我们按顺序进行。就在 standalone 文件夹下面,您将找到以下一组子目录:

  • configuration

  • data

  • deployments

  • lib

  • log

  • tmp

这些子目录的内容和使用说明如下:

  • configuration 文件夹包含应用程序服务器的配置文件。实际上,应用程序服务器附带了一套不同的配置文件,每个文件使用不同的扩展集。在没有传递任何参数的情况下启动独立启动脚本时,默认将使用 standalone.xml 配置文件。

    除了 standalone.xml 之外,此文件夹还包含配置引导过程日志的 logging.properties 文件。您在此处找到的其他文件是 mgmt-users.propertiesmgmt-group.properties,可用于保护管理接口。安全性在第十章,保护 WildFly中详细讨论。

  • data 文件夹用于存储应用程序服务器的运行时数据,例如事务日志。

  • deployments 文件夹是用户可以放置其部署内容(例如,WAR、EAR、JAR 和 SAR 文件)的位置,以便在服务器运行时自动部署。鼓励运行生产系统的用户使用 WildFly 的管理 API 上传和部署部署内容,而不是依赖于定期扫描此目录的部署扫描子系统。有关更多详细信息,请参阅第六章,应用结构和部署

  • lib 文件夹托管子文件夹 ext,用于定义应用程序服务器的扩展。对于域的 lib 路径的考虑也适用于此处。

  • log 文件夹包含应用程序服务器独立实例生成的日志。默认日志文件名为 server.log,在服务器重启时默认会被截断。这可以在 standalone.xml 文件中进行配置。

  • tmp 文件夹用于保存 WildFly 编写的临时文件。

欢迎内容文件夹

welcome-content 文件夹包含默认页面,当您浏览到应用程序服务器的根目录时(http://localhost:8080),将加载此页面。从 Web 服务器配置的角度来看,这是 Web 根上下文

模块文件夹

modules 文件夹下,您将找到应用程序服务器的一组库,它们是服务器分发的一部分。

从历史上看,JBoss AS 版本曾以不同的方式管理其库集合。让我们回顾一下,以恢复一些秩序。早期,4.x 版本用于将核心服务器库定义到JBOSS_HOME/server库中。此后,每个服务器定义都有其特定的库在server/<servername>/lib文件夹中。

这种方法相当简单,然而,它导致了在default/all服务器发行版中重复的库的无用激增。

5.x 和 6.x 版本中存在common/lib文件夹的概念,这是所有服务器定义中通用模块的主要存储库。每个服务器发行版仍然包含一个server/<servername>/lib路径,用于特定于该服务器定义的库。与早期版本相比,核心服务器模块的存储库保持不变,由JBOSS_HOME/server组成。

JBoss AS 7 采用了一种更模块化的方法,改进了所有早期方法。这种模块化方法在 WildFly 中保持不变。服务器引导库jboss-modules.jar位于应用服务器根目录中。这个单一的存档就是您启动 WildFly 应用服务器内核所需的所有内容。

主要系统模块位于modules文件夹下的system/layers/base文件夹中。在 WildFly 中,这一变化略有不同,因为在 JBoss AS 7 中,所有模块都是直接在modules文件夹中定义的。

下表概述了不同服务器版本中使用的各种方法:

AS 版本 引导库 服务器库
4.x JBOSS_HOME/server JBOSS_HOME/server/<server>/lib
5.x 和 6.x JBOSS_HOME/server JBOSS_HOME/common/libJBOSS_HOME/server/<server>/lib
7.x 和 8.x JBOSS_HOME/jboss-modules.jar JBOSS_HOME/modules

列出所有模块将占用太多空间,然而,模块存储库布局通常与模块名称相同。例如,org.jboss.as.ejb3模块可以在modules文件夹的org/jboss/as/ejb3子文件夹中找到。这种组织模块的方法确实是有意义的,如果您习惯了 maven 存储库布局结构,您将不会在理解它时遇到问题。

在本章的最后部分,我们将看到模块是如何由应用服务器实际加载的。

理解 WildFly 的内核

JBoss AS 7 对内核进行了重新设计。了解模块化内核的细节将有助于您理解书中稍后引入的概念。内核基于两个主要项目,如下所示:

  • JBoss Modules:该项目处理容器中资源的类加载。您可以将 JBoss 模块视为在模块化环境中执行应用程序的轻量级引导包装器。

  • 模块化服务容器MSC):该项目提供了一种安装、卸载和管理容器使用的服务的方法。MSC 还进一步使资源注入到服务和服务之间的依赖管理成为可能。

以下图显示了 WildFly 服务器内核的基本架构:

理解 WildFly 的内核

在获得这些信息后,我们现在可以继续加载服务器模块。

加载应用程序服务器模块

如果您想了解下一章中讨论的服务器配置,那么了解更多关于 JBoss 模块的知识是至关重要的。本质上,模块只是一个 JAR 文件的包装,但被应用程序容器视为模块。这样做的原因是类加载和依赖管理,因为每个模块都可以被视为一个可插入的单元,如下一图所示。WildFly 有两种不同类型的模块;它们之间的唯一区别是它们的打包方式:

  • 静态模块

  • 动态模块

看看下面的截图:

加载应用程序服务器模块

使用静态模块是加载模块的最简单方式,它是启动应用程序服务器时的默认模块。静态模块定义在JBOSS_HOME/modules/system/layers/base目录中。每个模块都有一个名为module.xml的配置文件。以下示例显示了javax.batch.apimodule.xml文件内容:

<module  name="javax.batch.api">
    <resources>
        <resource-root path="jboss-batch-api_1.0_spec-1.0.0.Final.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.enterprise.api"/>
    </dependencies>    
</module>

如您所见,模块定义包含两个主要元素,模块中定义的资源(及其路径)和模块的依赖。在这个例子中,主要资源是jboss-batch-api_1.0_spec-1.0.0.Final.jar,位于与module.xml文件相同的文件夹中。它依赖于另外两个模块,javax.apijavax.enterprise.api

定义了main-class元素的模块被称为可执行的。换句话说,模块名称可以列在命令行上,并且命名模块的main-class中的标准静态main(String[])方法将被加载并执行。

提示

如果您在服务器上部署了多个依赖相同第三方库的应用程序,创建自定义静态模块非常有用。这意味着您不需要部署带有相同捆绑库的多个应用程序。创建自定义静态模块的另一个好处是您可以显式声明对其他静态模块的依赖。模块的安装过程在第三章 配置企业服务 中有详细说明,其中我们安装了一个 JDBC 驱动作为模块。

接近模块库的另一种方式是使用动态模块。这可以通过以下两种方式实现:

  • 首先,我们可以在 JAR 文件内的MANIFEST文件中添加模块信息,例如,在主类mypackage/MyClass中:

    Dependencies: org.jboss.logging
    
  • 完成此操作的第二种方式是通过将依赖项添加到jboss-deployment-structure.xml文件中,如下面的代码所示:

    <jboss-deployment-structure>
      <deployment>
        <dependencies>
          <module name="org.jboss.logging" />
        </dependencies>
      </deployment>
    </jboss-deployment-structure>
    

我们将在第六章应用结构和部署中更详细地介绍这一点,其中我们解释了类加载。

摘要

在本章中,我们概述了随 WildFly 一起提供的最新功能。

我们已经看到 WildFly 由模块化架构组成,并且 WildFly 的内核由两个独立的项目组成:JBoss Modules 和 MSC。

这种模块化架构导致了一个异常轻量级的内核,能够按需加载模块,从而实现更快的启动时间。

应用服务器的物理结构反映了独立服务器和域服务器之间的二分法,前者是一个单节点实例,而后者是一组由域控制器和主机控制器管理的资源。

在下一章中,我们将更深入地探讨如何配置应用服务器的细节,重点关注独立服务器配置文件(standalone.xml),它包含核心应用服务器及其上运行的堆栈企业服务的配置。

第二章:配置 WildFly 核心子系统

第一章为我们提供了开始使用 WildFly 8 的基础。现在是时候深入 WildFly 的配置,看看如何管理应用服务器的独立实例了。你会发现整个服务器都在一个文件中进行配置。

配置文件由一系列子系统组成,包括应用服务器核心服务和标准 Java EE 服务。由于一个章节中不可能讨论所有子系统,因此它们被分散在几个章节中。到本章结束时,你应该能够理解和配置以下内容:

  • 服务器配置文件 standalone.xml

  • 应用服务器的线程池

  • 应用服务器的日志子系统

配置我们的应用服务器

默认配置文件命名为 standalone.xml,用于独立服务器,以及 domain.xml 用于应用服务器域。应用服务器域可以被视为一种专门的服务器配置,它还包括域和主机控制器设置。我们将在 第五章 中讨论应用服务器域,配置 WildFly 域。然而,就核心服务配置而言,这里所涵盖的内容也适用于域配置。配置文件(standalone.xmldomain.xml)是非静态文件,这意味着运行时更改会被持久化到它们中,例如,添加一个新的组件,如 JMS 目标,或部署应用程序。

你可以定义你需要的任意数量的配置文件。WildFly 8.1.0 版本提供了 standalone.xml(Web 配置文件)的一些变体,例如 standalone-full.xml(完整配置文件)和 standalone-ha.xml(具有高可用性的 Web 配置文件)。你还可以在 JBOSS_HOME/docs/examples/configs 中找到一些示例配置文件。如果你想使用不同的配置文件启动服务器,可以使用以下参数启动服务器:

./standalone.sh --server-config standalone-full-ha.xml

注意

standalone.xml 文件位于 JBOSS_HOME/standalone/configuration 文件夹中。此配置文件为 XML 格式,并由 JBOSS_HOME/docs/schema 文件夹中找到的一组 .xsd 文件进行验证。

如果你想检查单个 .xsd 文件,可以在你的服务器发行版的 JBOSS_HOME/docs/schema 文件夹中找到它们。你可以通过简单地检查这些文件或将它们导入到你的 Eclipse 环境中来了解所有可用的服务器参数。一旦它们位于你的项目中,右键单击你的文件,然后导航到 生成 | XML 文件

应用服务器的配置遵循一个树状结构,其中根元素包含服务器定义,如下面的图所示:

配置我们的应用服务器

在以下章节中,我们将详细展示服务器配置的重要部分。这将有助于理解应用程序服务器中每个单个组件的作用,尽管建议您不要手动更改配置文件。

手动更改配置文件可能导致未经检查的数据修改。这可能会破坏文件的格式,防止 WildFly 启动。如果您确实需要手动更新文件,您应该考虑首先创建备份副本。

注意

更改服务器配置的最佳实践是使用命令行界面CLI)或 Web 管理控制台,这些在第七章 使用管理接口 中进行了描述。

扩展

应用程序服务器包含一系列用于扩展应用程序服务器核心的模块。WildFly 的核心非常轻量级,这些扩展提供了您期望从应用程序服务器中获得的大部分功能。就像常规静态模块一样,它们存储在JBOSS_HOME/modules文件夹中。在部署任何应用程序之前,standalone.xmldomain.xml文件中定义的每个扩展都会被 WildFly 类加载器拾取。以下代码显示了服务器配置的摘录:

<extensions>
    <extension module="org.jboss.as.clustering.infinispan"/>
    <extension module="org.jboss.as.connector"/>
    <extension module="org.jboss.as.deployment-scanner"/>
    <extension module="org.jboss.as.ee"/>
    <extension module="org.jboss.as.ejb3"/>
  ...
</extensions>

路径

可以使用paths元素定义文件系统路径的逻辑名称。然后可以通过其逻辑名称引用这些路径,而无需在配置文件中每次都输入完整路径。默认情况下,path条目不包括在配置中。如果您想包括它,您将必须手动添加完整的配置。以下示例定义了一个相对于 WildFly 服务器日志的路径,逻辑名称为log.dir。对于独立服务器,此目录转换为JBOSS_HOME/standalone/log/mylogdir

<paths>

    <path name="log.dir" path="mylogdir" relative-to="jboss.server.log.dir"/>
</paths>

要在其他配置文件部分引用此路径,只需使用逻辑名称作为路径即可。以下示例显示了用于存储日志、旋转文件处理器的路径:

<periodic-rotating-file-handler name="FILE" autoflush="true">
  <file relative-to="log.dir" path="myserver.log"/>
</periodic-rotating-file-handler>

注意

请注意,属性relative-to不是必需的。如果您在路径配置中不包括它,则假定路径是绝对路径。

WildFly 提供了一套系统路径,您可以在不手动配置的情况下使用。以下表格概述了预配置的路径。前五个路径不能被覆盖,但其余路径可以使用前面代码片段中显示的路径元素进行覆盖。

路径 含义
jboss.home WildFly 分发的根目录
user.home 用户的家目录
user.dir 用户当前的工作目录
java.home Java 安装目录
jboss.server.base.dir 单个服务器实例的根目录
jboss.server.data.dir 服务器将用于持久数据文件存储的目录
jboss.server.log.dir 服务器将用于日志文件存储的目录
jboss.server.tmp.dir 服务器将用于临时文件存储的目录
jboss.domain.servers.dir 主控制器将在其中为单个服务器实例创建工作区的目录

管理接口

管理接口在management元素内进行配置。此配置由 CLI、管理控制台和 JMX 使用。本机 CLI 接口和 Web 控制台都在管理端口 9990 上运行。以下示例取自默认服务器配置,并突出了用于管理接口的端口号:

<socket-binding-group name="standard-sockets" default-interface="public">
 <socket-binding name="management-http" interface="management" port="9990"/>
    <socket-binding name="management-https" interface="management" port="9993"/>
</socket-binding-group>

在以下代码片段中,我们展示了前面的socket-binding配置被standalone.xml文件的management-interfaces部分引用:

<management-interfaces>
     <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
 <socket-binding http="management-http"/>
    </http-interface>
</management-interfaces>

管理接口在第七章使用管理接口中进行了详细讨论,该章节提供了对应用服务器管理工具的详细覆盖。

配置文件和子系统

配置文件可以被视为子系统集合,而每个子系统又包含通过扩展添加到应用服务器的功能子集(请参阅扩展部分)。例如,Web 子系统包含容器使用的连接器集合的定义,消息子系统定义了 AS 消息提供者使用的 JMS 配置和模块等。

独立文件和域配置文件之间的重要区别在于其中包含的配置文件数量。当使用独立配置时,有一个包含一组子系统配置的单个配置文件。另一方面,域配置可以提供多个配置文件。

接口

接口定义了网络接口/IP 地址或主机名可以绑定的逻辑名称。

默认情况下,独立应用服务器定义了两个可用的网络接口,即management接口和public接口:

    <interfaces>
        <interface name="management">
            <inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
        </interface>
        <interface name="public">
            <inet-address value="${jboss.bind.address:127.0.0.1}"/>
        </interface>
    </interfaces>

public网络接口旨在用于应用服务器核心服务:

<socket-binding-group name="standard-sockets" default-interface="public">
  ...
</socket-binding-group>

管理网络接口由 AS 管理接口引用,如管理接口部分所示。

默认情况下,两个网络接口都解析到回环地址127.0.0.1。这意味着应用服务器公共服务和管理工作站只能从本地机器访问。通过更改inet-address值,可以将网络接口绑定到另一个 IP 地址。以下示例显示了服务器正在监听 IP 地址192.168.1.1

<interface name="public">
 <inet-address value="192.168.1.1"/>
</interface>

如果您想将网络接口绑定到所有可用的 IP 地址集,可以使用<any-address />元素,如下所示:

<interface name="public">
 <any-address />
</interface>

网络接口的另一个有用变体是网络接口卡nic)元素,它从网络卡名称收集地址信息:

<interface name="public">
 <nic name="eth0" />
</interface>

注意

通过 CLI 绑定管理接口

你也可以使用-b开关绑定你的公共接口,后跟一个有效的主机/IP 地址。这将导致服务器监听提供的主机/IP 地址。例如,要将所有公共接口绑定到所有 IPv4 地址,你将使用$JBOSS_HOME/bin/standalone.sh -b=0.0.0.0

socket-binding 组

一个 socket-binding 组定义了一个套接字的逻辑名称。每个 socket-binding 名称可以在配置文件的其它部分被引用。在本节中,你可以配置将监听传入连接的网络端口。每个 socket-binding 组通过default-interface属性引用一个网络接口。看看以下代码片段:

<socket-binding-group name="standard-sockets" default-interface="public">
        <socket-binding name="management-http" interface="management" port="9990"/>
        <socket-binding name="management-https" interface="management" port="9993"/>
        <socket-binding name="ajp" port="8009"/>
        <socket-binding name="http" port="8080"/>
        <socket-binding name="https" port="8443"/>
<socket-binding name="jacorb" interface="unsecure" port="3528"/>
        <socket-binding name="jacorb-ssl" interface="unsecure" port="3529"/>
        <socket-binding name="txn-recovery-environment" port="4712"/>
        <socket-binding name="txn-status-manager" port="4713"/>
</socket-binding-group>

为了更改绑定服务的端口号,你可以更改其服务的port属性,但更好的方法是使用管理接口之一。这将提供受影响更改的即时结果。在以下示例中,我们将使用 CLI 更改http连接器的默认端口号:

[standalone@localhost:9990 /] /socket-binding-group=standard-sockets/socket-binding=http:write-attribute(name="port", value="8090")
{
  "outcome" => "success",
  "response-headers" => {
    "operation-requires-reload" => true,
    "process-state" => "reload-required"
  }
}

你可能已经注意到在上面的响应中需要重新加载。这可以通过执行以下命令来实现:

[standalone@localhost:9990 /] :reload

系统属性

本节包含一组系统级属性,可以作为启动过程的一部分添加到应用服务器中。默认情况下,system-properties条目被排除在配置之外。如果你想使用此功能,你需要添加完整的配置。以下配置片段将名为 example 的属性设置为true

<system-properties>
    <property name="myboolean" value="true"/>
</system-properties>

该属性可以在应用服务器上使用以下代码检索:

String s = System.getProperty("myboolean");

部署

配置文件的最后一部分包含所有已在应用服务器上注册的已部署应用程序。每次部署或卸载新应用程序时,本节都会更新以反映新的应用程序堆栈。

配置核心子系统

现在你已经掌握了 WildFly 配置文件的基本概念,我们将更详细地探讨单个服务。

在以下图中,你可以找到一个粗略的 WildFly 8 核心子系统表示(为了简单起见,我们只包括本书涵盖的子系统):

配置核心子系统

作为配置应用服务器的一个初步体验,我们将探索前图中加粗显示的区域。这些包括以下核心应用服务器子系统:

  • 线程池子系统

  • JBoss 日志子系统

让我们直接进入第一个子系统,线程池。

配置线程池子系统

线程池解决两个不同的问题。首先,由于减少了每个任务的调用开销,它们通常在执行大量异步任务时提供改进的性能。其次,它们提供了一种限制和管理资源(包括线程)的方法,这些资源在执行一系列任务时被消耗。

在 JBoss 服务器在 JBoss AS 7 之前的版本中,线程池配置集中在一个文件或部署描述符中。在 WildFly 中,任何使用线程池的子系统都管理自己的线程配置。

通过适当配置线程池部分,您可以调整使用该类型池以交付新任务的具体区域。应用程序服务器线程池配置可以包括以下元素:

  • 线程工厂配置

  • 限制队列线程配置

  • 阻塞限制队列线程配置

  • 无界队列线程配置

  • 无阻塞队列线程池配置

  • 无阻塞队列线程池配置

  • 调度线程配置

注意

重要提示:在 WildFly 9 中,线程子系统可能会被标记为弃用,但在 WildFly 8 中,此配置完全有效。

让我们详细看看每个单独的元素。

配置线程工厂

线程 工厂(实现java.util.concurrent.ThreadFactory)是一个按需创建新线程的对象。使用线程工厂可以消除对新线程调用的硬编码,使应用程序能够使用特殊的线程子类、优先级等。

默认情况下,线程工厂不包括在服务器配置中,因为它依赖于您很少需要修改的默认值。尽管如此,我们将为可能需要完全控制线程配置的资深用户提供一个简单的配置示例。

以下是一个自定义线程工厂配置的示例:

<thread-factory name="MyThreadFactory" thread-name-pattern="My Thread %t" group-name="dummy" />

以下是在定义线程工厂时可以使用的可能属性:

  • name属性是创建的线程工厂的名称

  • 可选的priority属性可以用来指定创建的线程的优先级

  • 可选的group-name属性指定为该线程工厂创建的线程组的名称

  • thread-name-pattern是用于创建线程名称的模板。以下模式可以使用:

模式 输出
%% 输出百分号
%g 输出每个工厂的线程序列号
%f 输出全局线程序列号
%i 输出线程 ID
%G 输出线程组名称

限制队列线程池

有限队列线程池是应用服务器中最常用的池类型。它通过定义线程池大小的约束来帮助防止资源耗尽。它也是最难使用的。其固有的复杂性源于它维护一个固定长度的队列和两个池大小:一个核心大小和一个最大大小。

如果每次提交新任务时,正在运行的线程数少于核心大小,则创建一个新线程。否则,如果有空间在队列中,任务将被排队。

如果这些选项都不可行,执行器需要评估它是否还可以创建一个新线程。如果正在运行的线程数少于最大大小,则创建一个新线程。否则,如果指定了指定的hand-off执行器,则任务将被分配给该执行器。如果没有指定hand-off执行器,则任务将被丢弃。

以下图表总结了整个过程,展示了所有部件是如何结合在一起的:

有限队列线程池

以下是从配置文件中摘取的有限队列线程池的示例配置:

<bounded-queue-thread-pool name="jca-short-running">
  <core-threads count="10"/>
  <queue-length count="10"/>
  <max-threads count="10"/>
  <keepalive-time time="10" unit="seconds"/>
</bounded-queue-thread-pool>

小贴士

下载示例代码

您可以从您在www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

以下表格简要描述了每个属性/元素:

属性/元素 描述
name 指定创建的执行器的 bean 名称
allow-core-timeout 指定核心线程是否超时;如果为false,则只有超过核心大小的线程会超时
core-threads 指定核心线程池大小,它小于最大池大小
max-threads 指定最大线程池大小
queue-length 指定执行器队列长度
keepalive-time 指定超出核心池大小的线程在空闲时应该保持运行的时间
thread-factory 指定用于创建工作线程的特定线程工厂的 bean 名称
handoff-executor 指定在任务无法接受时委托任务给执行器的执行器

注意

性能重点

队列大小池大小值是性能权衡的结果,需要在两者之间找到合适的平衡。当使用小池子和大队列时,可以最小化 CPU 使用率、操作系统资源和上下文切换开销。然而,它可能会产生人为的低吞吐量。如果任务强烈依赖于 I/O(因此经常阻塞),系统可能能够为比您允许的更多线程分配时间。使用小队列通常需要更大的池大小,这会使 CPU 更忙碌,但可能会遇到不可接受的调度开销,这也会降低吞吐量。

阻塞有界队列线程池

阻塞有界队列线程池的配置与有界队列线程池非常相似;它的工作流程略有不同。区别在于,而不是尝试将任务转交给指定的转交执行器,调用者会阻塞,直到队列中有空间可用。

该线程池的流程图如下所示:

阻塞有界队列线程池

下面是一个阻塞有界队列线程池的示例配置:

<blocking-bounded-queue-thread-pool name="jca-short-running">
    <core-threads count="10"/>
    <queue-length count="10"/>
    <max-threads count="10"/>
    <keepalive-time time="10" unit="seconds"/>
</bounded-queue-thread-pool>

请参阅以下表格,了解有界队列线程池的每个属性/元素的描述。以下表格显示了阻塞有界队列线程池可用的属性/元素:

属性/元素 描述
name 指定创建的执行器的 bean 名称
allow-core-timeout 指定核心线程是否可能超时;如果为false,则只有超过核心大小的线程会超时
core-threads 指定核心线程池的大小,它小于最大池大小
max-threads 指定最大线程池大小
queue-length 指定执行器队列长度
keepalive-time 指定超出核心池大小的线程在空闲时应保持运行的时间
thread-factory 指定用于创建工作线程的特定线程工厂的 bean 名称

无界队列线程池

无界队列线程池执行器比有界线程池采用更简单但更冒险的方法;也就是说,它总是接受新的任务。

实际上,无界线程池有一个核心大小和一个无上限的队列。当提交任务时,如果正在运行的线程数小于核心大小,则创建一个新线程。否则,任务将被放置在队列中。如果允许提交太多任务给这种类型的执行器,可能会发生内存不足的情况。请查看以下流程图:

无界队列线程池

由于其固有的风险,无界线程池默认情况下不包括在服务器配置中。这里我们提供一个示例,只有一个建议:孩子们,在家不要尝试这个!

<unbounded-queue-thread-pool name="unbounded-threads">
  <max-threads count="10" />
  <keepalive-time time="10" unit="seconds"/>
</unbounded-queue-thread-pool>

如果你想了解更多关于每个线程池元素/属性的含义,可以参考有界线程池表格。

无界队列线程池可用的属性/元素如下表所示:

属性/元素 描述
name 指定创建的执行器的 bean 名称
max-threads 指定线程池的最大大小
keepalive-time 指定超出核心池大小的线程在空闲时应该保持运行的时间
thread-factory 指定用于创建工作线程的特定线程工厂的 bean 名称

无界队列线程池

如其名所示,无队列线程池是一个没有队列的线程池执行器。基本上,这个执行器绕过了有界线程执行器的逻辑,因为它不尝试将任务存储在队列中。

因此,当提交任务时,如果正在运行的线程数少于最大大小,则创建一个新线程。否则,如果指定了指定的hand-off执行器,则任务将被分配给该执行器。如果没有指定任何指定的hand-off,则任务将被丢弃。请看以下流程图:

无界队列线程池

无队列执行器默认情况下也不包含在配置文件中。然而,我们在这里提供一个示例配置:

<queueless-thread-pool name="queueless-thread-pool" blocking="true">
  <max-threads count="10"/>
  <keepalive-time time="10" unit="seconds"/>
</queueless-thread-pool>

无阻塞无队列线程池

无阻塞无队列线程池的配置与无队列线程池类似。与无阻塞队列线程池类似,区别在于它不是尝试将任务传递给指定的 hand-off 执行器,而是调用者会阻塞,直到队列中有空间可用。

请看以下图示:

无阻塞无队列线程池

虽然它不包括在默认配置文件中,但这里有一个示例:

<blocking-queueless-thread-pool name="queueless-thread-pool">
    <max-threads count="10" />
    <keepalive-time time="10" unit="seconds"/>
</blocking-queueless-thread-pool>

无界队列线程池可用的属性/元素有namemax-threadskeepalive-timethread-factory

调度线程池

服务器端调度线程池用于需要定期运行或延迟执行的活动。它内部映射到java.util.concurrent.ScheduledThreadPoolExecutor实例。请看以下图示:

调度线程池

此类型的执行器使用scheduled-thread-pool执行器元素进行配置,如下所示:

<scheduled-thread-pool name="remoting">
  <max-threads count="10"/>
  <keepalive-time time="10" unit="seconds"/>
</scheduled-thread-pool>

调度线程池被remoting框架和 HornetQ 子系统使用,后者使用有界 JCA 线程执行器和调度池进行延迟交付。

配置应用程序服务器日志

每个应用程序都需要跟踪日志语句。目前,Java 应用程序有几种日志库的实现,其中最流行的是:

  • Log4j:它是一个来自 Apache 的灵活的开源日志库。Log4j 在开源社区中广泛使用,并且在 JBoss AS 的早期版本中是默认的日志实现。

  • Java SE 日志库(JUL):它作为 Java SE 平台标准库的一部分提供了日志类和接口。

Log4j 和 JUL 有非常相似的 API。它们在概念上只有细微的差别,但基本上做的是同样的事情,除了 Log4j,它有更多的功能。你可能需要也可能不需要这些功能。

JBoss 日志框架基于 JUL,围绕三个主要概念构建:记录器处理器格式化器。这些概念允许开发者根据消息的类型和优先级进行日志记录,并控制消息最终到达的位置以及它们的显示方式。

以下图表显示了使用 JUL 框架的日志周期。应用程序在日志对象上执行日志调用。这些日志对象分配 LogRecord 对象,并将它们传递给处理对象以进行发布。日志和处理对象都可能使用格式化器来安排日志布局,并使用过滤器来决定它们是否对特定的日志记录感兴趣。

看一下以下图表:

配置应用程序服务器日志

选择你的日志实现

WildFly/JBoss 应用程序服务器通过其版本,使用了不同的框架来处理应用程序服务器日志。在 JBoss AS 5 及更早版本中,log4j 是应用程序服务器使用的默认日志 API。

自从 JBoss AS 6 开始,日志提供程序切换到了 JBoss 自有的实现,该实现基于 JDK 1.4 日志系统。然而,它为默认 JDK 实现中的许多缺陷提供了几个修复和解决方案。

例如,JDK 中提供的 java.util.logging 默认实现没有按 Web 应用程序进行日志记录,因为配置是按虚拟机进行的。

因此,WildFly 用自己的实现替换了默认的 JUL 日志管理器实现,以解决这些问题。以下图表说明了构成 WildFly 8 日志子系统的模块:

选择你的日志实现

在层次结构的顶部,有 org.jboss.logmanager 模块,这是管理 JBoss 日志子系统的顶级库。在 jboss logmanager 之下,你可以找到具体的实现,例如 org.jboss.loggingorg.jboss.log4j.logmanager 模块。默认情况下,应用程序服务器使用前者模块(org.jboss.logging),该模块通过 org.jboss.as.logging 实现,以管理应用程序服务器内的日志。但是,如果你想切换到 log4j 实现,你需要 org.jboss.log4j.logmanager 模块(在本章的最后部分,我们将包括如何在应用程序中使用 log4j 的示例)。

注意

WildFly 不限于 JBoss 日志或 log4j。你可以使用任何日志库,包括 slf4j 或 commons logging。

配置日志子系统

日志子系统自带一组日志处理器。处理器对象从记录器接收日志消息并将其导出。例如,它可能将它们写入控制台或文件,发送到网络日志服务,或将它们转发到操作系统日志。默认情况下,定义了以下处理器:

  • console-handler

  • periodic-rotating-file-handler

  • size-rotating-file-handler

  • async-handler

  • syslog-handler

  • custom-handler

console-handler

console-handler 定义了一个处理器,它简单地将日志消息写入控制台,如下所示:

<console-handler name="CONSOLE" autoflush="true">
  <level name="INFO"/>
  <formatter>
    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
  </formatter>
</console-handler>

可选的 autoflush 属性确定是否自动刷新缓冲日志。此选项的默认值为 true

level 元素定义了与处理器关联的最低日志级别,这意味着任何具有此日志级别和更高值的都将被记录。日志级别的完整范围,从最低到最高,为:OFFFINESTFINERFINECONFIGINFOWARNINGSEVEREALL

formatter 元素为格式化 LogRecords 提供支持。日志格式化继承了 log4j 布局模式的相同模式字符串,而 log4j 又受到了亲爱的旧 C 语言 printf 函数的启发。请参阅 logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html 的 log4j 文档。

在这里,我们只是提到 %d{HH:mm:ss,SSS} 使用括号中包含的转换输出日志事件的日期

  • 字符串 %-5p 输出日志事件的优先级

  • 字符串 [%c] 用于输出日志事件的类别

  • 字符串 (%t) 输出生成日志事件的线程

  • 字符串 %s 输出日志消息

  • 最后,字符串 %n 输出平台相关的行分隔符字符或字符

periodic-rotating-file-handler

periodic-rotating-file-handler 定义了一个处理器,该处理器将写入文件,并在从给定后缀字符串派生的周期后旋转日志,该后缀字符串应格式为 java.text.SimpleDateFormat 所理解。

下面是它的定义:

<periodic-rotating-file-handler name="FILE" autoflush="true">
  <level name="INFO"/>
  <formatter>
    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
  </formatter>
  <file relative-to="jboss.server.log.dir" path="server.log"/>
  <suffix value=".yyyy-MM-dd"/>
  <append value="true"/>
</periodic-rotating-file-handler>

此处理器引入了包含路径的文件元素,这是实际的文件名及其 relative-to 位置。在我们的例子中,相对位置对应于 jboss.server.log.dir 应用服务器参数。

注意

使用默认的后缀配置,日志在中午 12 点滚动。通过更改 SimpleDateFormat 的值,你还可以更改日志滚动的周期,例如,后缀 yyyy-MM-dd-HH 将每小时滚动日志。

size-rotating-file-handler

size-rotating-file-handler 定义了一个处理器,该处理器将日志写入文件,当文件大小超过某个点时进行日志轮转。它还保留一定数量的备份。

标准配置中没有定义大小处理器。然而,我们可以从 JBOSS_HOME/docs/schema/jboss-as-logging_2_0.xsd 文件中找到其基本配置。请查看以下代码:

<size-rotating-file-handler name="FILESIZE" autoflush="true" >
  <rotate-size value="500k" />
  <level name="INFO"/>
  <formatter>
    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
  </formatter>
  <file relative-to="jboss.server.log.dir" path="server.log"/>
</size-rotating-file-handler>

异步处理器

async-handler 是一个复合处理器,它附加到其他处理器上以产生异步日志事件。在幕后,此处理器使用一个有界队列来存储事件。每次发出日志时,异步处理器将日志追加到队列中并立即返回。以下是对 FILE 追加器的异步日志示例:

<async-handler name="ASYNC">
  <level name="INFO" />
  <queue-length>1024</queue-length>
  <overflow-action>block</overflow-action>
  <sub-handlers>
    <handler-ref name="FILE" />
  </sub-handlers>
</async-handler>

在此处理器中,我们还指定了事件发送到的队列大小,以及当 async 队列溢出时要采取的操作。您可以选择 block,这将导致调用线程被阻塞,或者选择 discard,这将导致消息被丢弃。

注意

我应该何时使用异步处理器?

异步处理器为大量 I/O 绑定的应用程序提供了显著的性能优势。相反,CPU 绑定应用程序可能不会从异步日志中受益,因为它会给 CPU 带来额外的压力。

syslog 处理器

可以使用 syslog-handler 将日志写入远程日志服务器。这允许多个应用程序将它们的日志消息发送到同一个服务器,在那里它们可以一起解析。支持 RFC3164 和 RFC5424 格式。以下是一个 syslog-handler 的示例:

<syslog-handler name="SYSLOG" enabled="true">
    <level name="INFO" />
    <port value="514" />
    <server-address value="192.168.0.56" />
    <formatter>
        <syslog-format syslog-type="RFC5424" />
    </formatter>
</syslog-handler>

自定义处理器

到目前为止,我们只看到了一些基本的日志处理器,这些处理器通常包含在您的服务器配置中。如果您需要更高级的日志管理方法,您可以定义一个自定义日志处理器。为了添加自定义处理器,您需要定义一个扩展 java.util.logging.Handler 接口的类,并重写其抽象方法。例如,以下名为 JdbcLogger 的类用于将日志写入数据库(完整代码可在 community.jboss.org/wiki/CustomLogHandlersOn701 找到)。

注意

注意,尽管这篇文章是为 JBoss AS 7 编写的,但它对 WildFly 8 仍然有效。

请查看以下代码片段:

public class JdbcLogger extends Handler{
  @Override
  public void publish(LogRecord record){
    try{
      insertRecord(record);
    }
    catch (SQLException e)  {
      e.printStackTrace();
    }
  }
  @Override
  public void flush() {     . . . .    }
  @Override
  public void close() {     . . . .    }
}

一旦编译,此类需要打包到一个存档(例如,logger.jar)中,并在应用程序服务器中作为模块安装。我们将模块命名为 com.JDBCLogger,它需要在 modules 文件夹下具有以下结构:

自定义处理器

标签 Path to be created 显示了我们将在其中放置 logger.jar 存档及其配置文件(module.xml)的目录结构,如下所示:

<module  name="com.JDBCLogger">
    <resources>
        <resource-root path="logger.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="org.jboss.logging"/>
        <module name="com.mysql"/>
    </dependencies>
</module>

注意,此模块依赖于另一个模块com.mysql。在下一章中,我们将展示如何在安装适当的模块后连接到数据库。

我们几乎完成了。现在,将处理程序插入到包含其属性(包括数据库连接字符串和用于将日志插入数据库的语句)的日志子系统:

<custom-handler name="DB" class="com.sample.JdbcLogger" module="com.JDBCLogger">
    <level name="INFO"/>
    <formatter>
        <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
    </formatter>
    <properties>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
        <property name="insertStatement" value="INSERT INTO into log_table VALUES (?, $TIMESTAMP, $LEVEL, $MDC[ip], $MDC[user], $MESSAGE, hardcoded)"/>
    </properties>
</custom-handler>
<root-logger>
    <level name="INFO"/>
    <handlers>
        <handler name="CONSOLE"/>
        <handler name="FILE"/>
        <handler name="DB"/>
    </handlers>
</root-logger>

新的handler,命名为DB,被添加到root-logger中,以收集所有优先级为INFO或更高的日志语句。在测试日志记录器之前,不要忘记在您的 MySQL 数据库上创建所需的表,如下所示:

CREATE TABLE log_table(
  id INT(11) NOT NULL AUTO_INCREMENT,
  `timestamp` VARCHAR(255) DEFAULT NULL,
  level VARCHAR(255) DEFAULT NULL,
  mdc_ip VARCHAR(255) DEFAULT NULL,
  mdc_user VARCHAR(255) DEFAULT NULL,
  message VARCHAR(1500) DEFAULT NULL,
  hardcoded VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (id)
)
ENGINE = INNODBAUTO_INCREMENT = 1

如果您已仔细遵循所有必需的步骤,您将注意到log_table包含自服务器启动以来触发的日志事件。查看以下屏幕截图:

自定义处理器

配置日志记录器

日志记录器对象用于记录特定系统或应用程序组件的消息。日志记录器通常使用分层点分隔的命名空间命名。日志记录器名称可以是任意字符串,但通常应基于记录组件的包名或类名。例如,日志记录器指示日志系统为com.sample包生成日志语句,如果它们的日志级别为WARN或更高:

<logger category="com.sample">
  <level name="WARN"/>
</logger>

在层次结构的顶部,有root-logger。关于root-logger有两个重要事项需要注意:

  • 它始终存在

  • 它不能通过名称检索

在默认服务器配置中,root-logger定义了两个与CONSOLEFILE处理器连接的处理程序:

<root-logger>
    <level name="INFO"/>
    <handlers>
        <handler name="CONSOLE"/>
        <handler name="FILE"/>
    </handlers>
</root-logger>

每个部署的日志记录

WildFly 具有配置每个部署日志记录的能力。默认情况下,这是启用的。这意味着如果您将日志配置文件添加到您的部署中,其配置将用于该部署的日志记录。有效的日志配置文件如下:

  • logging.properties

  • jboss-logging.properties

  • log4j.properties

  • log4j.xml

  • jboss-log4j.xml

如果您将应用程序打包到 EAR 中,您的日志配置文件应放入META-INF目录。如果您将应用程序打包到 JAR 或 WAR 中,则可以将其放置在META-INF目录或WEB-INF目录中。

如果您想禁用每个部署的日志记录,您需要将use-deployment-logging-config的值设置为false。查看以下代码片段:

<subsystem >
 <use-deployment-logging-config value="false"/>
     <console-handler name="CONSOLE">
         <level name="INFO"/>
         <formatter>
             <named-formatter name="COLOR-PATTERN"/>
         </formatter>
    </console-handler>
    ...
</subsystem>

注意

在 WildFly 8 中,系统属性org.jboss.as.logging.per-deployment已被弃用。您应使用use-deployment-logging-config代替。

绕过容器日志

由于某些原因,您可能希望完全绕过容器日志记录。为此,将add-logging-api-dependencies属性添加到您的日志配置中,并将其值设置为false。这将禁用隐式服务器日志依赖项的添加,如下面的代码所示:

<subsystem >
 <add-logging-api-dependenciesuse-deployment-logging-config value="false"/>
    <console-handler name="CONSOLE">
        <level name="INFO"/>
        <formatter>
            <named-formatter name="COLOR-PATTERN"/>
        </formatter>
    </console-handler>
    ...
</subsystem>

要仅针对每个应用程序绕过日志记录,您需要使用 jboss-deployment-structure.xml 文件来排除日志子系统。我们将在第六章“应用程序结构和部署”中详细介绍 jboss-deployment-structure.xml 文件。

摘要

在本章中,我们介绍了应用程序服务器配置的基础知识,现在它由一个包含所有已安装服务配置的单个单体文件组成。

尽管这个主要配置文件将是您了解 WildFly 基础设施的主要参考点,但我们必须强调通过其中一个管理接口修改它的重要性。

我们已经详细检查了线程池配置中的每个部分。我们还看到线程池依赖于 Java 标准版线程执行器 API 来定义一系列池,并且这些池被应用程序服务器的核心服务所使用。

接下来,我们讨论了建立在 Java Util Logging 框架之上的 JBoss 日志框架,它解决了 JUL 的某些已知缺点。我们描述了如何在您的应用程序中配置每个应用程序的日志记录。

在下一章中,我们将查看一些核心企业服务配置,例如数据源和消息子系统。这些服务是许多企业应用程序的骨架。

第三章。配置企业服务

本章介绍了与应用服务器一起提供的 Java 企业服务的配置。许多服务都在它们自己的子系统内进行配置。根据您的应用程序是否需要这些服务,可以添加或删除这些子系统。以下是我们将按顺序查看的最常见子系统:

  • 连接到数据库

  • 配置企业 JavaBeans 容器

  • 配置消息服务

  • 配置事务服务

  • 配置并发

连接到数据库

为了使您的应用程序能够连接到数据库,您需要通过添加数据源来配置您的服务器。在服务器启动时,每个数据源都会预先填充一个数据库连接池。应用程序通过执行 JNDI 查找并调用 getConnection() 来从池中获取数据库连接。请查看以下代码:

Connection result = null;
try {
    Context initialContext = new InitialContext();
    DataSource datasource = (DataSource)initialContext.lookup("java:/MySqlDS");
    result = datasource.getConnection();
} catch (Exception ex) {
    log("Cannot get connection: " + ex);}

在连接被使用后,您应尽快调用 connection.close()。这会释放连接,并允许它返回到连接池——以便其他应用程序或进程使用。

在 JBoss AS 7 之前发布的版本需要将数据源配置文件(ds.xml)与应用程序一起部署。自从 JBoss AS 7 发布以来,由于应用程序服务器的模块化特性,这种方法已不再是强制性的。

默认情况下,应用程序服务器附带 H2 开源数据库引擎(www.h2database.com),由于其小巧的体积和基于浏览器的控制台,非常适合测试目的。

然而,现实世界中的应用程序需要一个行业标准数据库,例如 Oracle 数据库或 MySQL。在下一节中,我们将向您展示如何为 MySQL 数据库配置数据源。

任何数据库配置都需要两个步骤的过程,如下所示:

  • 安装 JDBC 驱动程序

  • 将数据源添加到您的配置中

让我们详细查看每个部分。

安装 JDBC 驱动程序

在 WildFly 的模块化服务器架构中,您有几种方式来安装您的 JDBC 驱动程序。您可以将其作为模块或作为部署单元安装。

第一种也是推荐的方法是将驱动程序作为模块安装。在 将驱动程序作为部署单元安装 部分中,我们将探讨一种更快的安装驱动程序的方法。然而,它确实存在各种限制,我们将在稍后讨论。

提示

请参阅本章的源代码以获取完整的模块示例。

安装新模块的第一步是在模块文件夹下创建目录结构。模块的实际路径为 JBOSS_HOME/modules/<module>/main

main 文件夹是所有关键模块组件安装的地方,即驱动程序和 module.xml 文件。因此,接下来,我们需要添加以下单元:

  • JBOSS_HOME/modules/com/mysql/main/mysql-connector-java-5.1.30-bin.jar

  • JBOSS_HOME/modules/com/mysql/main/module.xml

在本例中使用的 MySQL JDBC 驱动程序,也称为 Connector/J,可以从 MySQL 网站免费下载(dev.mysql.com/downloads/connector/j/)。在撰写本文时,最新版本是 5.1.30。

最后要做的事情是创建module.xml文件。这个文件包含实际的模块定义。确保模块名称(com.mysql)与在您的数据源中定义的module属性相匹配非常重要。

您还必须声明 JDBC 驱动程序资源的路径,最后添加模块依赖项,如下面的代码所示:

<module  name="com.mysql"> <resources>
        <resource-root path="mysql-connector-java-5.1.30-bin.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

下面是一个显示这个新模块最终目录结构的图:

安装 JDBC 驱动

注意

您会注意到modules文件夹中已经存在目录结构。所有系统库都存放在system/layers/base目录中。您的自定义模块应直接放置在modules文件夹中,而不是与系统模块一起。

添加本地数据源

一旦安装了 JDBC 驱动程序,您需要在应用程序服务器的配置文件中配置数据源。在 WildFly 中,您可以配置两种类型的数据源,本地数据源和xa 数据源,它们可以通过配置文件中的元素名称来区分。

注意

使用java.sql.Driver,本地数据源不支持两阶段提交。另一方面,xa 数据源支持使用javax.sql.XADataSource进行两阶段提交。

通过在服务器配置文件中添加数据源定义或使用管理接口,可以完成添加数据源定义。管理接口是推荐的方法,因为它们会为您准确更新配置,这意味着您不需要担心语法是否正确。

在本章中,我们将通过直接修改服务器配置文件来添加数据源。虽然这不是推荐的方法,但它将使您熟悉文件的语法和布局。在第七章“使用管理接口”中,我们将向您展示如何使用管理工具添加数据源。

下面是一个可以复制到standalone.xml配置文件中的数据源配置示例:

<datasources>
  <datasource jndi-name="java:/MySqlDS" pool-name="MySqlDS_Pool" enabled="true" jta="true" use-java-context="true" use-ccm="true">
    <connection-url>
      jdbc:mysql://localhost:3306/MyDB
    </connection-url>
    <driver>mysql</driver>
    <pool />
    <security>
      <user-name>jboss</user-name>
      <password>jboss</password>
    </security>
    <statement/>
    <timeout>
      <idle-timeout-minutes>0</idle-timeout-minutes>
      <query-timeout>600</query-timeout>
    </timeout>
  </datasource>
  <drivers>
    <driver name="mysql" module="com.mysql"/>
  </drivers>
</datasources>

如您所见,配置文件使用与早期-*.ds.xml文件相同的 XML 架构定义,因此从先前的版本迁移到 WildFly 不会很困难。

注意

在 WildFly 中,将数据源绑定到java:/java:jboss/ JNDI 命名空间是强制性的。

让我们来看看这个文件的各种元素:

  • connection-url:此元素用于定义连接到数据库的路径。

  • driver:此元素用于定义 JDBC 驱动程序类。

  • pool: 此元素用于定义 JDBC 连接池属性。在这种情况下,我们将保留默认值。|

  • security: 此元素用于配置连接凭据。|

  • statement: 此元素仅作为语句缓存选项的占位符添加。|

  • timeout: 此元素是可选的,包含一组其他元素,例如 query-timeout,它是对查询超时的静态配置,即查询在超时前允许的最大秒数。还包括的 idle-timeout-minutes 元素表示在关闭之前连接可能空闲的最大时间;将其设置为 0 禁用它,默认为 15 分钟。|

配置连接池|

数据源配置的一个关键方面是 pool 元素。您可以在不修改任何现有 WildFly 配置的情况下使用连接池,因为在不修改的情况下,WildFly 将选择使用默认设置。如果您想自定义池配置,例如,更改池大小或更改要池化的连接类型,您需要学习如何修改配置文件。|

这里是一个池配置的示例,可以添加到您的数据源配置中:|

<pool>
    <min-pool-size>5</min-pool-size>
    <max-pool-size>10</max-pool-size>
    <prefill>true</prefill>
    <use-strict-min>true</use-strict-min>
    <flush-strategy>FailingConnectionOnly</flush-strategy>
</pool>

实际上,pool 配置中包含的属性是从早期版本借用的,所以我们在这里包括它们供您参考:|

属性 含义
initial-pool-size 这意味着池应该持有的初始连接数(默认为 0(零))。
min-pool-size 这是池中的最小连接数(默认为 0(零))。
max-pool-size 这是池中的最大连接数(默认为 20)。
prefill 这尝试将连接池预填充到最小连接数。
use-strict-min 这确定是否应该关闭低于 min-pool-size 的空闲连接。
allow-multiple-users 这确定是否可以通过 getConnection 方法让多个用户访问数据源。在 WildFly 中,这略有变化。在 WildFly 中,需要 <allow-multiple-users>true</allow-multiple-users> 这一行。在 JBoss AS 7 中,使用了 <allow-multiple-users/> 空元素。
capacity 这指定了池的容量策略——要么是 incrementer 要么是 decrementer
connection-listener 在这里,您可以指定 org.jboss.jca.adapters.jdbc.spi.listener.ConnectionListener,它允许您监听连接回调,例如激活和钝化。
flush-strategy 这指定了在发生错误时如何刷新池(默认为 FailingConnectionsOnly)。

配置语句缓存|

对于连接池中的每个连接,WildFly 服务器能够创建一个语句缓存。当使用预处理语句或可调用语句时,WildFly 将缓存该语句以便重用。为了激活语句缓存,您必须在 prepared-statement-cache-size 元素中指定一个大于 0 的值。请看以下代码:

<statement>
    <track-statements>true</track-statements>
    <prepared-statement-cache-size>10</prepared-statement-cache-size>
    <share-prepared-statements/>
</statement>

注意,我们还将 track-statements 设置为 true。这将启用 statementsResultSets 的自动关闭。如果您想使用预处理语句缓存并且/或者不想防止游标泄漏,这是很重要的。

最后一个元素 share-prepared-statements 只能在启用预处理语句缓存时使用。此属性确定同一事务中的两个请求是否应该返回相同的语句(默认为 false)。

添加 xa 数据源

添加 xa-datasource 需要对数据源配置进行一些修改。xa-datasource 在其自己的元素中配置,即在数据源内部。您还需要在 driver 元素中指定 xa-datasource 类。

在以下代码中,我们将为我们的 MySQL JDBC 驱动程序添加配置,该配置将用于设置 xa-datasource

<datasources>
  <xa-datasource jndi-name="java:/XAMySqlDS" pool-name="MySqlDS_Pool" enabled="true" use-java-context="true" use-ccm="true">
    <xa-datasource-property name="URL">
      jdbc:mysql://localhost:3306/MyDB
    </xa-datasource-property>
    <xa-datasource-property name="User">jboss
    </xa-datasource-property>
    <xa-datasource-property name="Password">jboss
    </xa-datasource-property>
    <driver>mysql-xa</driver>
  </xa-datasource>
  <drivers>
    <driver name="mysql-xa" module="com.mysql">
      <xa-datasource-class>
        com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
      </xa-datasource-class>
    </driver>
  </drivers>
</datasources>

小贴士

数据源与 xa 数据源

在单个事务跨越多个数据源的情况下,例如,如果方法消耗 Java 消息服务JMS)并更新 Java 持久化 APIJPA)实体时,您应该使用 xa 数据源。

将驱动程序作为部署单元安装

在 WildFly 应用程序服务器中,每个库都是一个模块。因此,只需将 JDBC 驱动程序部署到应用程序服务器即可触发其安装。

注意

如果 JDBC 驱动程序由多个 JAR 文件组成,您将无法将其作为部署单元安装。在这种情况下,您必须将驱动程序作为核心模块安装。

因此,要将数据库驱动程序作为部署单元安装,只需将 mysql-connector-java-5.1.30-bin.jar 驱动程序复制到您的安装目录中的 JBOSS_HOME/standalone/deployments 文件夹,如下图中所示:

将驱动程序作为部署单元安装

部署您的 JDBC 驱动程序后,您仍然需要将其添加到服务器配置文件中。最简单的方法是将以下数据源定义粘贴到配置文件中,如下所示:

<datasource jndi-name="java:/MySqlDS" pool-name="MySqlDS_Pool"  
  enabled="true" jta="true" use-java-context="true" use-ccm="true">
  <connection-url>
    jdbc:mysql://localhost:3306/MyDB
  </connection-url>
  <driver>mysql-connector-java-5.1.130-bin.jar</driver>
  <pool />
  <security>
    <user-name>jboss</user-name>
    <password>jboss</password>
  </security>
</datasource>

或者,您可以使用 命令行界面CLI)或网络管理控制台来实现相同的结果,如后续第七章使用管理接口中所述,使用管理接口

小贴士

关于域部署呢?

在本章中,我们讨论的是独立服务器的配置。服务也可以在域服务器中进行配置。然而,域服务器没有指定用于部署的文件夹。相反,使用管理接口将资源注入域。第五章,配置 WildFly 域,将详细介绍使用域服务器部署模块的所有步骤。

选择正确的驱动程序部署策略

在这一点上,您可能会想知道部署 JDBC 驱动程序的最佳实践。将驱动程序作为部署单元安装是一个方便的快捷方式;然而,它可能会限制其使用。首先,它需要一个 JDBC 4 兼容的驱动程序。

部署不兼容 JDBC 4 的驱动程序是可能的,但需要简单的修补程序。为此,创建一个包含 java.sql.Driver 文件的 META-INF/services 结构。文件的内容将是驱动程序名称。例如,假设您需要修补 MySQL 驱动程序——内容将是 com.mysql.jdbc.Driver

一旦创建了结构,您可以使用任何压缩工具或 .jar 命令打包您的 JDBC 驱动程序,jar -uf <your-jdbc-driver.jar> META-INF/services/java.sql.Driver

注意

尽管令人好奇,尽管不是所有驱动程序都被应用程序服务器识别为 JDBC 4 兼容,但最新的 JDBC 驱动程序都符合 JDBC 4 规范。以下表格描述了一些最常用的驱动程序及其 JDBC 兼容性:

数据库 驱动程序 JDBC 4 兼容 包含 java.sql.Driver
MySQL mysql-connector-java-5.1.30-bin.jar 是,尽管 WildFly 并未将其识别为兼容
PostgreSQL postgresql-9.3-1101.jdbc4.jar 是,尽管 WildFly 并未将其识别为兼容
Oracle ojdbc6.jar/ojdbc5.jar
Oracle ojdbc4.jar

如您所见,列表中一个最显著的例外是较旧的 Oracle ojdbc4.jar,它不符合 JDBC 4 规范,并且不包含 META-INF/services/java.sql.Driver 中的驱动程序信息。

驱动程序部署的第二个问题与 xa-datasources 的特定情况相关。将驱动程序作为部署安装意味着应用程序服务器本身无法推断出驱动程序中使用的 xa-datasource 类的信息。由于这些信息不包含在 META-INF/services 中,您被迫为要创建的每个 xa-datasource 指定 xa-datasource 类的信息。

当您将驱动程序作为模块安装时,xa-datasource 类信息可以共享给所有已安装的数据源。

<driver name="mysql-xa" module="com.mysql">
  <xa-datasource-class>
    com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
  </xa-datasource-class>
</driver>

因此,如果您对这些问题的限制不是太严格,将驱动程序作为部署安装是一个方便的快捷方式,可以在您的开发环境中使用。对于生产环境,建议您将驱动程序作为静态模块安装。

以编程方式配置数据源

安装您的驱动程序后,您可能希望限制服务器文件中的应用程序配置量。这可以通过程序化配置数据源来完成。此选项不需要修改您的配置文件,这意味着更高的应用程序可移植性。通过使用@DataSourceDefinition注解,可以配置数据源程序化,如下所示:

@DataSourceDefinition(name = "java:/OracleDS",
  className = " oracle.jdbc.OracleDriver",
  portNumber = 1521,
  serverName = "192.168.1.1",
  databaseName = "OracleSID",
  user = "scott",
  password = "tiger",
  properties = {"createDatabase=create"})
@Singleton
public class DataSourceEJB {
  @Resource(lookup = "java:/OracleDS")
  private DataSource ds;
}

在这个例子中,我们为 Oracle 数据库定义了一个数据源。重要的是要注意,当程序化配置数据源时,您实际上会绕过 JCA,它代理客户端和连接池之间的请求。

这种方法的明显优势在于,您可以在不重新配置其数据源的情况下,将应用程序从一个应用服务器移动到另一个应用服务器。另一方面,通过在配置文件中修改数据源,您将能够充分利用应用服务器的全部好处,其中许多对于企业应用程序是必需的。

配置企业 JavaBeans 容器

企业 JavaBeans (EJB) 容器是 Java 企业架构的基本组成部分。EJB 容器提供用于托管和管理容器中部署的 EJB 组件的环境。容器负责提供一组标准服务,包括缓存、并发、持久性、安全性、事务管理和锁定服务。

容器还为主机组件提供分布式访问和查找功能,并且它拦截所有对主机组件的方法调用,以强制执行声明性安全和事务上下文。请看以下图示:

配置企业 JavaBeans 容器

如此图中所示,您将能够在 WildFly 中部署完整的 EJB 组件集:

  • 无状态会话 Bean (SLSB):SLSBs 是实例没有对话状态的对象。这意味着当它们不服务客户端时,所有 Bean 实例都是等效的。

  • 有状态会话 Bean (SFSB):SFSBs 支持与紧密耦合的客户端进行对话服务。有状态会话 Bean 为特定客户端完成一项任务。它在客户端会话期间维护状态。会话完成后,状态不会保留。

  • 消息驱动 Bean (MDB):MDBs 是一种能够异步处理任何 JMS 生产者发送的消息的企业 Bean。

  • 单例 EJB:这本质上与无状态会话 Bean 类似;然而,它使用单个实例来服务客户端请求。因此,您保证在调用之间使用相同的实例。单例可以使用一组具有更丰富生命周期和更严格锁定策略的事件来控制对实例的并发访问。在下一章,关于 Web 应用程序的部分,我们将展示一个使用单例 EJB 来保存一些缓存数据的 Java EE 7 应用程序。

  • 无接口 EJB:这只是标准会话 Bean 的另一种视图,除了本地客户端不需要一个单独的接口,也就是说,Bean 类的所有公共方法都会自动暴露给调用者。接口仅在 EJB 3.x 中使用,如果您有多个实现。

  • 异步 EJB:这些能够像消息驱动 Bean(MDB)一样异步处理客户端请求,除了它们提供了一个类型化的接口,并采用更复杂的方法来处理客户端请求,这些请求由以下组成:

    • 由客户端调用的 fire-and-forget 异步 void 方法

    • retrieve-result-later 异步方法具有 Future<?> 返回类型

注意

不保持会话状态的 EJB 组件(SLSB 和 MDB)可以配置为发出定时通知。有关更多信息,请参阅 配置定时服务 部分。

配置 EJB 组件

现在我们已经简要概述了 EJB 的基本类型,接下来我们将探讨应用服务器配置的具体细节。这包括以下组件:

  • SLSB 配置

  • SFSB 配置

  • MDB 配置

  • 定时服务配置

让我们详细看看它们。

配置无状态会话 Bean

EJB 在 ejb3.2.0 子系统中进行配置。默认情况下,WildFly 启动时不存在无状态会话 Bean 实例。随着单个 Bean 的调用,EJB 容器初始化新的 SLSB 实例。

这些实例随后将被保存在一个池中,该池将用于服务未来的 EJB 方法调用。EJB 在客户端方法调用期间保持活动状态。方法调用完成后,EJB 实例将返回到池中。因为 EJB 容器在每次方法调用后将无状态会话 Bean 从客户端解绑,所以客户端使用的实际 Bean 类实例可能在不同调用之间不同。请看以下图表:

配置无状态会话 Bean

如果一个 EJB 类的所有实例都是活动的,并且池的最大池大小已经达到,那么请求 EJB 类的新客户端将会被阻塞,直到一个活动的 EJB 完成一个方法调用。根据您如何配置您的无状态池,如果在最大时间内无法从池中获取实例,则可能会触发获取超时。

您可以通过主要配置文件或编程方式配置会话池。让我们看看这两种方法,从主要配置文件开始。

为了配置您的池,您可以操作两个参数:池的最大大小(max-pool-size)和实例获取超时(instance-acquisition-timeout)。让我们看看一个例子:

<subsystem >
 <session-bean>
  <stateless>
   <bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
  </stateless>
  ...
 </session-bean>
  ...
 <pools>
  <bean-instance-pools>
   <strict-max-pool name="slsb-strict-max-pool" max-pool-size="25" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
  </bean-instance-pools>
 </pools>
  ...
</subsystem>

在这个例子中,我们已将 SLSB 池配置为具有 25 个元素的严格上限。严格的池最大值是唯一可用的池实例实现;它允许固定数量的并发请求同时运行。如果有更多请求正在运行,并且超过了池的严格最大大小,这些请求将阻塞,直到有实例可用。在池配置中,我们还设置了 instance-acquisition-timeout 值为 5 分钟,如果您的请求大小超过池大小,该值将发挥作用。

您可以配置任意数量的池。EJB 容器使用的池由 bean-instance-pool-ref 元素上的 pool-name 属性指示。例如,这里我们添加了一个额外的池配置,largepool,并将其设置为 EJB 容器的池实现。看看以下代码:

<subsystem >
  <session-bean>
    <stateless>
      <bean-instance-pool-ref pool-name="large-pool"/>
    </stateless>
  </session-bean>
  <pools>
    <bean-instance-pools>
      <strict-max-pool name="large-pool" max-pool-size="100" 
instance-acquisition-timeout="5" 
instance-acquisition-timeout-unit="MINUTES"/>
    <strict-max-pool name="slsb-strict-max-pool" max-pool-size="25" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
    </bean-instance-pools>
  </pools>
</subsystem>

使用 CLI 配置无状态池大小

我们已经详细说明了通过主要配置文件配置 SLSB 池大小的步骤。然而,建议的最佳实践是使用 CLI 来更改服务器模型。

这是您如何将名为 large-pool 的新池添加到您的 EJB 3 子系统中的方法:

/subsystem=ejb3/strict-max-bean-instance-pool=large-pool:add(max-pool-size=100)

现在,您可以将此池设置为默认池,供 EJB 容器使用,如下所示:

/subsystem=ejb3:write-attribute(name=default-slsb-instance-pool, value=large-pool)

最后,您可以通过操作 max-pool-size 属性来随时更改池大小属性,如下所示:

/subsystem=ejb3/strict-max-bean-instance-pool=large-pool:write-attribute(name="max-pool-size",value=50)

配置有状态的会话豆

SFSBs 绑定到特定的客户端。应用程序服务器使用缓存将活动 EJB 实例存储在内存中,以便它们可以快速检索以供未来的客户端请求使用。缓存包含客户端当前正在使用的 EJBs 以及最近使用过的实例。看看以下图解:

配置有状态的会话豆

在内存中保留 EJBs 是一项昂贵的操作,因此您应该尽快通过钝化或删除它们来将它们移出内存。

钝化是一种过程,通过该过程 EJB 容器确保空闲的 SFSB 实例通过将它们的状态保存到磁盘来从缓存中释放。

相反,从缓存中删除豆是一个可以由 EJB 容器程序触发的过程。要程序化地删除 EJB,请将 @javax.ejb.Remove 注解添加到您的函数中。当调用此方法时,EJB 将被删除。看看以下代码:

@Remove
public void remove() {}

以下示例显示了 ejb3:2.0 子系统的部分内容,它显示了 SFSB 的配置以及其缓存和钝化存储配置。请看以下代码:

<subsystem >
  <session-bean>
 <stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
  </session-bean>
  ...
  <caches>
 <cache name="simple"/>
 <cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
  </caches>
  <passivation-stores>
 <passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
  </passivation-stores>
  ...
</subsystem>

如您所见,有状态的豆元素引用了一个缓存定义(命名为 distributable),该定义反过来连接到一个钝化存储(命名为 infinispan)。请注意可选的 max-size 属性,它限制了缓存中可以包含的 SFSB 数量。您还可以看到集群缓存使用 infinispan 的 passivation-store(有关 infinispan 缓存的更多信息,请参阅第八章,集群,Chapter 8)。

小贴士

在 WildFly 中,file-passivation-storecluster-passivation-store 元素已被弃用,以支持 passivation-store。这两个弃用元素将在未来的版本中完全删除。

配置消息驱动的豆(Beans)

消息驱动的 MDBs)是无状态的、服务器端、事务感知的组件,用于处理异步 JMS 消息。

MDB 最重要的一点是它们可以并发地消费和处理消息。

这种能力在传统 JMS 客户端中提供了显著的优势,因为传统 JMS 客户端必须自定义构建以在多线程环境中管理资源、事务和安全。

与会话豆有明确的生命周期一样,MDB 也有。MDB 实例的生命周期基本上与无状态豆相同。MDB 有两种状态:不存在方法就绪池。请看以下图示:

配置消息驱动的豆

当接收到消息时,EJB 容器会检查池中是否有任何可用的 MDB 实例。如果有一个豆可用,WildFly 会使用该实例。在 MDB 实例的 onMessage() 方法返回后,请求完成,并将实例放回池中。这导致最佳响应时间,因为请求是在不等待创建新实例的情况下得到服务的。

如果没有可用的豆实例,容器会通过比较 MDB 的 MaxSize 属性与池大小来检查池中是否有更多 MDB 的空间。

如果 MaxSize 仍未达到,则会初始化一个新的 MDB。创建序列,如前图所示,与无状态豆相同。另一方面,如果无法创建新实例,则请求将被阻塞,直到一个活动的 MDB 完成。如果在 instance-acquisition-timeout 定义的时时间内无法从池中获取实例,则会抛出异常。

MDB 池的配置与 SLSB 的配置完全相同,所以我们在这里只包括它,不做进一步解释:

<subsystem >
  <mdb>
    <resource-adapter-ref resource-adapter-name="hornetq-ra"/>
    <bean-instance-pool-ref pool-name="mdb-strict-max-pool"/>
  </mdb>
  <pools>
    <bean-instance-pools>
      <strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
    </bean-instance-pools>
  </pools>
</subsystem>

注意

要了解更多关于各种企业 Bean 的信息,你可以参考 Java EE 7 教程,链接为docs.oracle.com/javaee/7/tutorial/doc/ejb-intro002.htm

配置定时器服务

EJB 3 定时器服务提供了一种方法,允许方法在特定时间或时间间隔被调用。如果你的应用程序业务流程需要周期性通知,这将非常有用。

EJB 定时器服务可以在任何类型的 EJB 3 中使用,除了有状态的会话 Bean。使用定时器服务就像在方法上标注 @javax.ejb.Timeout 注解一样简单。当时间间隔到期时,容器将触发该方法。

以下示例展示了如何实现一个非常简单的定时器,它将通过调用 scheduleTimer(long milliseconds) 方法来启动。请看以下代码:

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import javax.annotation.Resource;
import javax.ejb.*;

@LocalBean
@Stateless
public class TimerSampleBean {

    @Resource
    private SessionContext ctx;

    public void scheduleTimer(long milliseconds) {
        LocalDate date = LocalDate.now().plus(milliseconds, ChronoUnit.MILLIS);
        ctx.getTimerService().createTimer(date.toEpochDay(), "Hello World");
    }

    @Timeout
    public void timeoutHandler(Timer timer) {
        System.out.println("* Received Timer event: " + timer.getInfo());
        timer.cancel();
    }
}

在配置方面,你可以在文件系统或数据库中存储计划执行的作业。要在文件系统中保存它们,你需要从file-data-store属性(在这个例子中称为file-store)引用default-data-store属性。定时器服务保留的线程数量可以通过thread-pool-name属性进行配置,该属性需要引用一个thread-pool元素。请看以下代码:

<subsystem >
    <timer-service default-data-store="file-store" thread-pool-name="default">
        <data-stores>
            <file-data-store name="file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
        </data-stores>
    </timer-service>
    <thread-pools>
        <thread-pool name="default">
            <max-threads count="10"/>
            <keepalive-time time="100" unit="milliseconds"/>
        </thread-pool>
    </thread-pools>
</subsystem>

配置消息系统

消息导向中间件一直是应用服务器的一个基本组成部分。消息系统允许你将异构系统松散耦合在一起,同时通常提供可靠性、事务以及许多其他功能。

消息系统不是 Java EE Web 配置的一部分,因此你不会在standalone.xml文件中找到消息子系统的配置。然而,消息子系统包含在名为standalone-full.xml的配置文件中。

注意

消息系统通常支持两种主要的异步消息风格:队列(点对点消息)和主题(发布/订阅消息)。

在点对点模型中,发送者将消息发布到特定的队列,接收者从队列中读取消息。在这里,发送者知道消息的目的地,并将消息直接发布到接收者的队列中。

发布/订阅模型支持将消息发布到特定的消息主题。订阅者可以注册对特定消息主题接收消息的兴趣。在这个模型中,发布者和订阅者都不知道对方的存在。

以下表格显示了两种不同模型的特点:

点对点消息 发布/订阅
只有一个消费者获取消息。 多个消费者(或没有)将接收消息。
生产者不需要在消费者消费消息时运行,消费者也不需要在消息发送时运行。 发布者必须为客户端创建一个消息主题,以便订阅。订阅者必须持续活跃以接收消息,除非他已经建立了持久订阅。在这种情况下,当订阅者未连接时发布的消息将在他重新连接时重新分发。
每条成功处理的消息都会被消费者确认。

JBoss AS 在其各个版本中使用了不同的 JMS 实现。自 6.0 版本发布以来,默认的 JMS 提供者是 HornetQ (www.jboss.org/hornetq),它提供了一个多协议、可嵌入、高性能、集群的异步消息系统。

在其核心,HornetQ 被设计成一套Java****对象POJOs)。它只有一个 JAR 依赖项,即 Netty 库,该库利用 Java 非阻塞 输入/输出NIO)API 来构建高性能网络应用程序。

注意

由于其易于适应的架构,HornetQ 可以嵌入到您的项目中,也可以在任何依赖注入框架中实例化,例如 Spring 或 Google Guice。

在本书中,我们将介绍 HornetQ 作为模块嵌入到 WildFly 子系统中的场景。以下图表显示了 HornetQ 服务器在整个系统中的位置:

配置消息系统

如您所见,HornetQ 集成的一个关键部分是处理应用程序服务器和 HornetQ 服务器之间通信的JCA****适配器

提示

为什么你不能简单地将你的资源连接到 HornetQ 服务器?

这在理论上是可能的;然而,它违反了 Java EE 规范,并将导致丢失应用程序服务器 JCA 层提供的功能,例如连接池和自动事务注册。当使用消息传递时,例如在 EJB 内部,这些功能是可取的。有关 JCA 线程池配置的描述,请参阅第二章中的“有界队列线程池”部分,“配置核心 WildFly 子系统”

配置传输

配置 JMS 消息的传输是消息系统调优的关键部分。默认情况下,HornetQ 使用 Netty 作为其高性能、低级别的网络库。Netty 是一个 NIO 客户端-服务器框架,它使得快速轻松地开发网络应用程序成为可能,例如协议****服务器客户端。它极大地简化并简化了网络编程,例如 TCP 和 UDP 套接字服务器。

HornetQ 传输中最重要的概念之一是接受者和连接器的定义。

接受者定义了 HornetQ 服务器接受的连接类型。另一方面,连接器定义了如何连接到 HornetQ 服务器。连接器由 HornetQ 客户端使用。

HornetQ 定义了三种类型的接受者和连接器:

  • inVM:当 HornetQ 客户端和服务器都在同一虚拟机中运行时,可以使用此类型(inVM 代表虚拟机内部)

  • Netty:此类型定义了通过 TCP 进行远程连接的方式(使用 Netty 项目处理 I/O)

  • http:这是 WildFly 的默认配置,它定义了通过 HTTP 将远程连接到 HornetQ 的方式(它使用 Undertow 将 HTTP 协议升级到 HornetQ 协议)

为了进行通信,HornetQ 客户端必须使用与服务器接受者兼容的连接器。兼容的客户端-服务器通信要求使用以下图中显示的相同类型的接受者/连接器进行:

配置传输

我们可以看到,无法将 InVM 客户端连接器连接到 Netty 服务器接受者。另一方面,如果它们配置在相同的主机和端口上运行,则可以将 HTTP 客户端连接器连接到 HTTP 服务器接受者。

WildFly 8 自带一个预配置的接受者/连接器对,它是 WildFly 消息子系统的组成部分,如下面的代码所示:

<connectors>
    <http-connector name="http-connector" socket-binding="http">
        <param key="http-upgrade-endpoint" value="http-acceptor"/>
    </http-connector>
    <http-connector name="http-connector-throughput" socket-binding="http">
        <param key="http-upgrade-endpoint" value="http-acceptor-throughput"/>
        <param key="batch-delay" value="50"/>
    </http-connector>
    <in-vm-connector name="in-vm" server-id="0"/>
</connectors>
<acceptors>
    <http-acceptor name="http-acceptor" http-listener="default"/>
    <http-acceptor name="http-acceptor-throughput" http-listener="default">
        <param key="batch-delay" value="50"/>
        <param key="direct-deliver" value="false"/>
    </http-acceptor>
    <in-vm-acceptor name="in-vm" server-id="0"/>
</acceptors>

如您所见,除了in-vm接受者/连接器对之外,每个部分都定义了两种类型的接受者/连接器,其中一种依赖于默认配置http-connector,另一种(http-acceptor-throughput)是针对更高的消息吞吐量而专门设计的。

当您对可以添加到接受者/连接器部分的参数有更完整的了解时,您可以进一步调整 HTTP 传输。以下是一个所有参数及其含义的完整列表:

参数 描述
use-nio 如果此值为true,则将使用 Java 非阻塞 I/O。如果设置为false,则将使用旧的阻塞 Java I/O。默认值为true
host 此属性指定要连接到的主机名或 IP 地址(在配置连接器时)或要监听的主机名(在配置接受者时)。此属性的默认值是localhost。可以通过逗号分隔来指定多个主机或 IP 地址。
port 此属性指定要连接到的端口(在配置连接器时)或要监听的端口(在配置接受者时)。此属性的默认值是5445
tcp-no-delay 如果此值为true,则将禁用 Nagle 算法。此属性的默认值是true
tcp-send-buffer-size 此参数确定 TCP 发送缓冲区的大小(以字节为单位)。此属性的默认值是32768字节。
tcp-receive-buffer-size 此参数决定了 TCP 接收缓冲区的大小,单位为字节。此属性的默认值是 32768 字节。
batch-delay 此参数允许你配置 HornetQ,以便在发送传输之前将消息批量写入,最多延迟 batch-delay 毫秒。这可以增加非常小的消息的整体吞吐量。此属性的默认值是 0 毫秒。
direct-deliver 此参数允许你配置消息投递是否使用携带消息的同一线程完成。将此设置为 true(默认值)会以牺牲消息吞吐量为代价减少线程上下文切换的延迟。如果你的目标是更高的吞吐量,请将此参数设置为 false
nio-remoting-threads 当使用 NIO 时,HornetQ 默认将使用等于处理传入数据包所需核心处理器数量的三倍的线程数。如果你想覆盖此值,你可以通过指定此参数来设置线程数。此参数的默认值是 -1,表示使用从 Runtime.getRuntime().availableProcessors() * 3 计算得出的值。
http-client-idle-time 这决定了客户端在发送一个空的 HTTP 请求以保持连接活跃之前可以空闲多长时间。
http-client-idle-scan-period 这决定了我们多久可以扫描一次空闲客户端,单位为毫秒。
http-response-time 这决定了服务器在发送一个空的 HTTP 响应以保持连接活跃之前可以等待多长时间。
http-server-scan-period 这决定了我们多久可以扫描一次需要响应的客户端,单位为毫秒。
http-requires-session-id 如果设置为 true,客户端在第一次调用后将会等待以接收一个会话 ID。

在 HornetQ 用户中,一个常见的混淆来源是,如果服务器负责接受连接和投递消息,为什么连接器会被包含在服务器配置中。这主要有两个原因:

  • 有时服务器在连接到另一个服务器时会充当客户端本身,例如,当一个服务器桥接到另一个服务器或服务器参与集群时。在这些情况下,服务器需要知道如何连接到其他服务器。这是由连接器定义的。

  • 如果你正在使用 JMS 以及服务器端的 JMS 服务来实例化 JMS ConnectionFactory 实例并将它们绑定到 JNDI,那么在创建 HornetQConnectionFactory 时,它需要知道该连接工厂将连接到哪个服务器。

配置连接工厂

客户端使用 JMS ConnectionFactory 对象来连接到服务器。connection-factory 实例的定义包含在默认服务器配置中。请查看以下代码:

<connection-factory name="InVmConnectionFactory">
    <connectors>
        <connector-ref connector-name="in-vm"/>
    </connectors>
    <entries>
        <entry name="java:/ConnectionFactory"/>
    </entries>
</connection-factory>
<connection-factory name="RemoteConnectionFactory">
    <connectors>
        <connector-ref connector-name="http-connector"/>
    </connectors>
    <entries>
        <entry name="java:jboss/exported/jms/RemoteConnectionFactory"/>
    </entries>
</connection-factory>

你可以找到两个连接工厂定义,具体如下:

  • InVmConnectionFactory:此连接工厂绑定到 java:/ConnectionFactory,并在服务器和客户端在同一个 JVM(因此在同一 WildFly 服务器)中运行时使用。

  • RemoteConnectionFactory:正如其名所示,当 JMS 连接由远程服务器提供时可以使用此连接工厂。默认情况下,它使用 http-connector 并绑定到 JNDI 名称,java:jboss/exported/jms/RemoteConnectionFactory

配置 JMS 目的地

除了在 JMS 子系统中的连接工厂定义之外,您还可以找到 JMS 目的地(队列和主题),它们是服务器分发的一部分。请看以下代码:

<jms-destinations>
    <jms-queue name="ExpiryQueue">
        <entry name="java:/jms/queue/ExpiryQueue"/>
    </jms-queue>
    <jms-queue name="DLQ">
        <entry name="java:/jms/queue/DLQ"/>
    </jms-queue>
</jms-destinations>

队列的 name 属性定义了队列的名称。在 JMS 级别,队列的实际名称遵循命名约定,因此将是 jms.queue.ExpiryQueue

entry 元素配置用于将队列绑定到 JNDI 的名称。这是一个强制性的元素,队列可以包含多个此类元素以将同一队列绑定到不同的名称。

例如,以下是配置 MessageDrivenBean 组件以从 ExpiryQueue 消费消息的方法:

@MessageDriven(name = "MessageMDBSample", activationConfig = {
  @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
  @ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/jms/queue/ExpiryQueue"),
  @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") })

public class SampleMDBean implements MessageListener {
  @Resource
  private MessageDrivenContext context;
}

小贴士

为什么知道实际的目的地名称是有用的?

显然,了解服务器的目的地名称(例如,jms.queue.ExpiryQueue)似乎并不重要。我们更关心目的地绑定的 JNDI 条目。然而,如果要在多个目的地之间定义一些属性,实际的目的地名称就非常重要了。有关更多信息,请参阅下一节,使用地址自定义目的地

队列和主题定义可以包含一些非强制性的元素,例如 selectordurable

<jms-queue name="selectorQueue">
     <entry name="/queue/selectorQueue"/>
     <selector>name='john'</selector>
     <durable>true</durable>
</jms-queue>

selector 元素定义了预定义队列将具有的 JMS 消息选择器。只有与选择器匹配的消息才会被添加到队列中。这是一个可选元素,省略时默认值为 null

durable 元素指定队列是否将被持久化。这同样是可选的,如果省略则默认为 true

使用地址自定义目的地

如果您想为 JMS 目的地提供一些自定义设置,可以使用 address-setting 块,该块可以应用于单个目的地和一组目的地。默认配置将一组最小属性应用于所有目的地。请看以下代码:

<address-settings>
  <!--default for catch all-->
  <address-setting match="#">
    <dead-letter-address>jms.queue.DLQ</dead-letter-address>
    <expiry-address>jms.queue.ExpiryQueue</expiry-address>
    <redelivery-delay>0</redelivery-delay>
    <max-size-bytes>10485760</max-size-bytes>
    <message-counter-history-day-limit>10</message-counter-history-day-limit>
    <address-full-policy>BLOCK</address-full-policy>
  </address-setting>
</address-settings>

这里是关于地址设置的简要描述。

地址设置的 match 属性定义了目的地的过滤器。当使用通配符 # 时,属性将适用于所有目的地。例如:

<address-setting match="jms.queue.#">

在这里,这些设置将应用于 destination 部分中定义的所有队列:

<address-setting match="jms.queue.ExpiryQueue ">

这些设置将应用于名为 jms.queue.ExpiryQueue 的队列。

目的地属性的简短描述如下:

属性 描述
dead-letter-address 这指定了无法投递的消息的目的地。
expiry-address 这定义了将过期消息发送到何处。
expiry-delay 这定义了使用默认过期时间的消息的过期时间。
redelivery-delay 这定义了在尝试重新投递取消的消息之前应等待多长时间。
max-size-bytes 这指定了在进入page模式之前消息的最大字节数。
page-size-bytes 这指定了分页系统中每个页面文件的大小。
max-delivery-attempts 这定义了在将取消的消息发送到dead-letter-address之前,可以重新投递消息的次数。
message-counter-history-day-limit 这指定了消息计数器历史记录将保留多少天。
address-full-policy 当达到目的地最大大小时使用此选项。当设置为PAGE时,进一步的消息将被分页到磁盘。如果值为DROP,进一步的消息将被静默丢弃。当使用BLOCK时,客户端消息生产者在尝试发送进一步的消息时将被阻塞。

HornetQ 持久化配置

我们需要讨论的最后一个 HornetQ 主题是消息持久化。HornetQ 拥有自己的优化持久化引擎,当您了解其各种组件的所有信息时,可以进一步配置。

注意

HornetQ 高数据持久化的秘密在于将数据附加到日志文件,而不是使用昂贵的随机访问操作,这需要更高的磁盘头移动程度。

日志文件在运行时预先创建并填充了填充字符。通过预先创建文件,当其中一个被填满时,日志可以立即使用下一个文件继续,而无需暂停创建它。

以下是为消息子系统设置的默认日志值。尽管这些值在standalone-full.xml文件中没有明确设置,但它们的缺失会导致使用这些默认值。

<journal-file-size>102400</journal-file-size>
<journal-min-files>2</journal-min-files>
<journal-type>NIO</journal-type>
<persistence-enabled>true</persistence-enabled>

默认的journal-file-size(以字节为单位)是100 KB。日志将维护的最小文件数由属性journal-min-files指示,该属性表示至少将维护两个文件。

属性journal-type指示用于数据持久化的输入/输出库的类型。有效的值是NIOASYNCIO

选择NIO将设置 Java NIO 日志。选择AIO将设置 Linux 异步 I/O 日志。如果您选择AIO但未运行 Linux 或未安装libaio,则 HornetQ 将检测到这一点并自动回退到使用NIO

persistence-enabled属性设置为false时,将禁用消息持久化。这意味着不会持久化绑定数据、消息数据、大消息数据、重复 ID 缓存或分页数据。禁用数据持久化将给您的应用程序带来显著的性能提升;然而,其另一面是您的数据消息不可避免地会失去可靠性。

为了完整性,我们包括一些额外的属性,如果您想自定义消息/分页和日志存储目录,可以包含这些属性。请查看以下代码:

<bindings-directory relative-to="jboss.server.data.dir" path="hornetq/bindings" />

<large-messages-directory relative-to="jboss.server.data.dir" path="hornetq/largemessages" />

<paging-directory relative-to="jboss.server.data.dir" path="hornetq/paging" />

<journal-directory relative-to="jboss.server.data.dir" path="hornetq/journal" />

为了获得最佳性能,我们建议将日志存储在其自己的物理卷上,以最小化磁盘头移动。如果日志位于与其他进程共享的卷上,这些进程可能正在写入其他文件(例如,绑定日志、数据库或事务协调器),那么磁盘头在写入这些文件时可能会在这些文件之间快速移动,从而大幅降低性能。

配置事务服务

事务可以被定义为必须作为一个单元执行的一组操作,可能包括持久化数据对象、发送消息等。

当事务中的操作跨越数据库或其他位于单独计算机或进程上的资源执行时,这被称为分布式事务。此类企业级事务需要在涉及的资源之间进行特殊协调,并且可能非常难以可靠地编程。这就是Java 事务 APIJTA)发挥作用的地方,它提供了资源可以实现的接口,并且它们可以绑定以参与分布式事务。

EJB 容器是一个支持 JTA 的事务管理器,因此可以参与涉及其他 EJB 容器以及第三方 JTA 资源(如许多数据库管理系统)的分布式事务。

在 WildFly 8 中,事务在其自己的子系统中进行配置。事务子系统主要由以下四个元素组成:

  • 核心环境

  • 恢复环境

  • 协调器环境

  • 对象存储

核心环境包括TransactionManager接口,该接口允许应用程序服务器代表被管理的资源控制事务边界。请查看以下图示:

配置事务服务

事务协调器反过来管理参与事务的事务对象和资源的通信。

JBossTS 的恢复子系统确保事务的结果一致地应用于受事务影响的所有资源,即使任何应用程序进程或托管它们的机器崩溃或失去网络连接。

在事务服务中,JBoss 事务服务使用对象存储来持久记录事务的结果以进行故障恢复。事实上,恢复管理器扫描对象存储和其他信息位置,寻找需要或可能需要恢复的事务和资源。

核心和恢复环境可以通过更改它们的套接字绑定属性进行自定义,这些属性在socket-binding-group配置部分中引用。

您可能会发现定义自定义属性在协调器环境部分更有用,这可能包括默认超时和日志统计信息。以下是一个示例自定义事务配置:

<subsystem >
  <core-environment>
    <process-id>
      <uuid/>
    </process-id>
  </core-environment>
  <recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
 <coordinator-environment default-timeout="300" statistics-enabled="true" />
</subsystem>

default-timeout的值指定了用于新事务的默认事务超时,该值以秒为整数指定。

提示

事务超时如何影响您的 应用程序

事务超时定义了所有 JTA 事务注册的超时时间,从而严重影响了您的应用程序行为。一个典型的 JTA 事务可能由您的 EJB 或 JMS 会话启动。因此,如果这些事务的持续时间超过指定的超时设置,事务服务将自动回滚事务。

statistics-enabled的值决定了事务服务是否应该收集统计信息。默认情况下不收集此信息。

提示

在 WildFly 中,enable-statistics属性已被弃用,以statistics-enabled属性取而代之。如果您从 JBoss AS 7 迁移,则弃用的属性仍然有效,但可能在未来的版本中删除。

配置并发

并发实用工具是 WildFly 8 的新功能。作为 Java EE 7 的一部分,我们的目标是简化企业应用程序中的多线程任务。在 Java EE 7 之前,没有安全的方法在应用程序中以编程方式创建新线程。

使用新的并发实用工具,您的新线程现在可以保证访问其他企业服务,例如事务和安全。

主要并发组件包括:

  • ContextService

  • ManagedThreadFactory

  • ManagedExecutorService

  • ManagedScheduledExecutorService

配置上下文服务

上下文服务用于从现有对象创建上下文代理,并在 WildFly 的ee模块中进行配置。以下是在 WildFly 中的默认配置:

<subsystem >
    ....
    <concurrent>
        <context-services>
            <context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
        </context-services>
        ....
    </concurrent>
</subsystem>

name属性是您的上下文服务名称,而use-transaction-setup-provider属性表示上下文代理是否应该挂起和恢复活动事务。

配置托管线程工厂

ManagedThreadFactory组件用于创建由容器管理的线程。默认配置如下:

<concurrent>
    ...
    <managed-thread-factories>
        <managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
    </managed-thread-factories>
    ...
</concurrent>

要在 Java 代码中使用默认的线程工厂,只需使用@Resource注解,而不为lookup属性提供值,如下所示:

@Stateless
public class ReportBean {
    @Resource
    private Managed ;
    public void runReports() {
        MyTask myTask = new MyTask();
        Future<Report> future = executorService.submit(myTask);
    }
}

配置托管执行器服务

此类用于在您的企业应用程序中执行第二个线程的任务。您应该始终优先使用此服务,而不是 Java SE 库中找到的执行器服务。以下是在 WildFly 中的配置示例:

<concurrent>
    ...
    <managed-executor-services>
       <managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default"  context-service="default" hung-task-threshold="60000" core-threads="5" max-threads="25" keepalive-time="5000"/>
                </managed-executor-services>
    ...
</concurrent>

以下是在 WildFly 中配置您的managed-executor-service可以使用的完整属性列表:

context-service 这定义了要使用哪个上下文服务。
core-threads 这定义了执行器线程池中的线程数,包括空闲线程。
hung-task-threshold 这指定了在将线程视为无响应之前,线程可以运行多长时间(以毫秒为单位)。
jndi-name 这指定了此资源的 JNDI 名称。
keepalive-time 这指定了当线程数大于核心线程大小时,线程可以保持空闲的时间长度。
long-running-tasks 这用于检查线程是短运行线程还是长运行线程。
max-threads 这指定了执行器池中允许的最大线程数。
name 这指定了资源的名称。
queue-length 这指定了可以存储在输入队列中的任务数。零表示无限。
reject-policy 这定义了如何处理失败的任务。ABORT值将导致抛出异常;RETRY_ABORT,这将导致重试,如果重试失败则终止。
thread-factory 这指定了线程工厂的名称。如果没有提供,则使用默认的线程工厂。

配置托管调度执行器服务

这与ManagedExecutorService相同,但它具有额外的功能,允许您在特定时间安排线程启动。以下是一个配置示例:

<concurrent>
    ...
    <managed-scheduled-executor-services>
       <managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" core-threads="2" keepalive-time="3000"/>
                </managed-scheduled-executor-services>
    ...
</concurrent>

以下是可以用于配置您的managed-scheduled-executor-service的属性列表。请参阅managed-executor-service部分前面的表格,以了解每个属性的详细信息。

  • context-service

  • core-threads

  • hung-task-threshold

  • jndi-name

  • keepalive-time

  • long-running-tasks

  • name

  • reject-policy

  • thread-factory

摘要

在本章中,我们继续分析应用服务器配置,通过查看 Java 的企业服务。

我们首先学习了如何配置数据源,这可以用于将数据库连接添加到您的应用程序中。在 WildFly 8 中安装数据源需要两个简单的步骤:安装 JDBC 驱动程序并将数据源添加到服务器配置中。

我们接着看了企业 JavaBeans 子系统,它允许您配置和调整您的 EJB 容器。我们研究了基本的 EJB 组件配置(SLSB、SFSB 和 MDB),然后研究了可以用来为您的应用程序提供基于时间的服务的 EJB 定时器服务配置。

接着,我们描述了面向消息的中间件的配置,它允许您将异构系统松散耦合在一起,同时通常提供可靠性、事务以及各种其他功能。

然后我们转向事务子系统配置,它可以用来收集事务日志并定义所有 JTA 事务的超时时间。

最后,我们通过查看如何使用ee子系统在 WildFly 中配置并发性来完成我们的旅程。

在下一章中,我们将讨论 Web 容器配置,提供一个完整的示例,该示例使用各种企业技术,并关注应用程序的结构和打包。

第四章. Undertow Web 服务器

在本章中,我们将探讨如何配置 Undertow,这是 WildFly 8 附带的服务器。这将完成我们对独立服务器配置的概述。

然后,我们将通过创建、打包和部署一个示例 Java EE 7 项目来查看典型企业应用程序的结构。它将包括 JavaServer Faces 组件、企业 JavaBeans 和 CDI,并且还将使用Java 持久化 APIJPA)。这将让你感受到与完整的 Java EE 7 应用程序一起工作的感觉。

到本章结束时,你将了解到:

  • Undertow 的架构

  • Undertow 主机配置

  • 服务器静态内容服务

  • Servlet 容器配置

  • JSP 配置

  • 会话 cookie 的配置

  • 如何创建一个简单的 Web 应用程序

Undertow 概述

那些使用过 WildFly 先前版本的人会知道,历史上,JBoss 总是包括 Tomcat,或者 Tomcat 的分支(称为 JBoss Web),作为应用程序服务器的 Web 容器。

决定替换 JBoss Web 是因为需要一个新的 Web 容器,它支持新的 Java EE 7 要求,如 WebSocket 和 HTTP 升级。还决定新的 Web 服务器应该是轻量级和灵活的,并且具有更好的性能。结果服务器响应极快,可以扩展到超过一百万个连接,并且具有卓越的吞吐量。

Undertow 架构

Undertow 是用 Java 编写的,基于非阻塞输入/输出API(通常称为新输入/输出或简称NIO)。通过基于组合的架构和使用流畅的构建器 API 构建,Undertow 可以轻松配置,提供你所需的多或少的函数。通过将处理程序串联起来,你可以构建从简单的 HTTP 处理程序到完整的 Java EE 3.1 容器的任何东西。

组成 Undertow 服务器有三个核心部分:

  • XNIO 工作实例:这些实例在 Java NIO 之上形成了一个薄抽象层,提供通道 API、IO 和工作者线程的管理以及 SSL 支持。

  • 监听器:这些处理传入的连接和底层协议。

  • 处理程序:这些处理程序串联在一起,为 Undertow 提供主要功能。它们定义了如何处理传入的请求。

以下图示展示了这些组件如何组合在一起以创建 Web 服务器,并演示了处理程序是如何串联在一起的:

Undertow 架构

配置 Undertow

在本节中,我们将探讨如何配置 Undertow 的不同组件。Undertow 是在standalone.xml文件中找到的 Undertow 子系统内进行配置的。以下是 Undertow 子系统的摘录:

<subsystem >
    <buffer-cache name="default"/>
    <server name="default-server">
        <http-listener name="default" socket-binding="http"/>
        <host name="default-host" alias="localhost">
            <location name="/" handler="welcome-content"/>
            <filter-ref name="server-header"/>
            <filter-ref name="x-powered-by-header"/>
        </host>
    </server>
    <servlet-container name="default">
        <jsp-config/>
    </servlet-container>
    <handlers>
        <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
    </handlers>
    ...
</subsystem>

Undertow Web 服务器的大部分配置都在serverservlet-container元素内进行,这两个元素我们将在下一节中查看。

服务器配置

server元素内,您可以配置主机和监听器。配置主服务器实例的属性如下:

名称 含义
default-host 如果请求没有主机头,则使用此虚拟主机
servlet-container 这是将要使用的 servlet 容器,如servlet-container元素中配置的

配置监听器

如我们之前所述,Undertow 由监听器和处理器组成。监听器在server元素内进行配置,如下面的代码所示。standalone.xml文件中的默认配置只有一个定义的连接器,即 HTTP 连接器:

<server name="default-server">
    <http-listener name="default" socket-binding="http"/>
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <filter-ref name="server-header"/>
        <filter-ref name="x-powered-by-header"/>
    </host>
</server>

注意,socket-binding属性指向在socket-binding-group部分定义的配置:

<socket-binding-group name="standard-sockets" default-interface="public">
    <socket-binding name="http" port="8080"/>
</socket-binding-group>

备注

WildFly 还支持 AJP 和 HTTPS 连接协议;我们将在第九章负载均衡 Web 应用和第十章保护 WildFly 中分别详细介绍。

在配置监听器时有很多选项。HTTP 监听器元素的属性概述如下:

属性 描述 默认值
allow-encoded-slash 当设置为 true 时,此属性允许服务器解码百分号编码的斜杠字符(%2F)。只有当您有一个需要此功能的遗留应用程序时,才启用此选项,因为它可能由于不同服务器对斜杠的不同解释而具有安全影响。 false
always-set-keep-alive 此属性确定是否应将Connection: keep-alive头添加到所有响应中,即使规范不需要也是如此。 true
buffer-pipelined-data 此属性确定对 HTTP 管道请求的响应是否应缓冲并一次性发送。如果 HTTP 管道正在使用且响应较小,这可以提高性能。 true
buffer-pool 此属性引用在 I/O 子系统定义的缓冲池,用于内部读取和写入请求。通常,这些缓冲池至少应为 8 KB,除非您处于内存受限的环境中。 default
certificate-forwarding 如果此属性启用,则监听器将从SSL_CLIENT_CERT属性中获取证书。此属性仅在客户端位于代理后面且代理配置为始终设置这些头时启用。
decode-url 此属性确定是否应对 URL 进行解码。如果此属性设置为false,则 URL 中的百分号编码字符将保持不变。 true
enabled 此属性表示此监听器是否启用 true
max-cookies 此属性定义允许的最大 cookie 数量。如果客户端发送的 cookie 数量超过此值,则连接将被关闭。这存在是为了防止基于哈希碰撞的 DOS 攻击。 200
max-header-size 此属性定义允许的最大 HTTP 头块大小(以字节为单位)。任何值大于此值的请求头将被关闭。 5120
max-headers 此属性定义允许的最大头数。它存在是为了防止基于哈希碰撞的 DOS 攻击。 200
max-parameters 此属性定义允许的最大查询或路径参数数量。如果发送的参数更多,则连接将被关闭。它存在是为了防止基于哈希碰撞的 DOS 攻击。 1000
max-post-size 此属性定义允许的传入 POST 请求的最大大小。 0(无限)
name 此属性定义分配给监听器的名称。
proxy-address-forwarding 此属性启用x-forwarded-host和类似头,并设置远程 IP 地址和主机名。
redirect-socket 当此属性启用时,如果监听器支持非 SSL 请求并且收到需要 SSL 传输的匹配安全约束的请求,则会自动将请求重定向到指定的套接字绑定端口。
socket-binding 此属性确定监听器监听的地址和端口。
url-charset 此属性定义解码 URL 的字符集。 UTF-8
worker 此属性引用在 IO 子系统定义的 XNIO 工作器。正在使用的工作器控制 IO 和阻塞线程池。 default

配置主机

server元素内的主机配置对应于虚拟主机,并直接嵌套在server元素下,如下面的代码所示。虚拟主机允许您根据运行 WildFly 的机器所知的 DNS 名称对 Web 应用程序进行分组。

<server name="default-server">
    ...
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <filter-ref name="server-header"/>
        <filter-ref name="x-powered-by-header"/>
    </host>
</server>

在这里解释了嵌套在主机内的元素:

  • location: 此元素定义指向内容的 URL 路径,例如welcome-content

  • access-log: 此元素允许您配置访问日志的位置和格式。

  • filter-ref: 此元素定义应用于当前主机的过滤器。

  • single-sign-on: 此元素允许您配置用于身份验证的 cookie。

可以通过更改默认属性来完全配置访问日志,如下面的代码所示:

<access-log directory="${jboss.server.log.dir}" pattern="common" prefix="access_log" rotate="true" suffix=".log" worker="default"/>

filter-ref元素通过引用在filters元素中定义的过滤器的名称来声明应用的过滤器,如下面的高亮代码所示:

<server name="default-server">
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <filter-ref name="server-header"/>
        <filter-ref name="x-powered-by-header"/>
    </host>
</server>
<filters>
    <response-header name="server-header" header-name="Server" header-value="Wildfly 8"/>
    <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow 1"/>
</filters>

服务器静态内容

您可能不想将所有静态内容与应用程序一起部署。这些可能是图像、PDF 文档或其他类型的文件。您可以配置 Undertow 在本地文件系统中查找这些文件。以下示例展示了如何通过向 Undertow 子系统添加文件处理器和位置来完成此操作:

<server name="default-server">
    <http-listener name="default" socket-binding="http"/>
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <location name="/img" handler="images"/>
    </host>
</server>
<handlers>
    <file name="welcome-content" path="${jboss.home.dir}/welcome-content" directory-listing="true"/>
    <file name="images" path="/var/images" directory-listing="true"/>
</handlers>

通过此附加配置,对 www.yourdomain.com/contextroot/img 资源的所有请求都将重定向到您的硬盘上的文件系统。

配置 servlet 容器

一个 servlet 容器的实例定义在单个 servlet-container 元素内。如果您希望有多个 servlet 容器,则可以拥有多个 servlet-container 元素;然而,对于大多数配置,一个实例就足够了。standalone.xml 中的默认配置如下所示:

<servlet-container name="default">
    <jsp-config/>
</servlet-container>

以下表格详细说明了 servlet-container 可用的属性:

属性 描述 默认值
allow-non-standard-wrappers 此属性放宽了 servlet 规范,该规范要求应用程序仅使用扩展 ServletRequestWrapperServletResponseWrapper 类的包装类来包装请求/响应。 false
default-buffer-cache 这是默认 servlet 中用于缓存静态资源的缓冲区缓存。
default-encoding 这是请求和响应的默认编码。
eager-filter-initialization 通过将此属性设置为 true,在第一次请求时调用 web.xml 文件中定义的过滤器的 init 方法,而不是在服务器启动时调用。 false
ignore-flush 此选项忽略 servlet 输出流上的刷新操作。 false
stack-trace-on-error 此属性的可用选项为 allnonelocal-onlyall 值将显示所有跟踪信息(不应在生产环境中使用),而 none 表示不显示堆栈跟踪,local-only 表示仅显示来自本地地址的请求,并且没有标题指示请求已被代理。此功能使用 Undertow 错误页面而不是 web.xml 中指定的默认错误页面。 local-only
use-listener-encoding 此选项使用接收请求的监听器使用的默认编码。 false

可以向 servlet-container 元素添加多个子元素,这将允许您配置您的 JSPs、会话 cookie 和持久会话。

配置 JSP

JSP 元素在默认配置中提供。由于没有添加其他属性,因此应用默认配置,如下所示:

<jsp-config 
check-interval="0" 
development="false" 
disabled="false" 
display-source-fragment="true" 
dump-smap="false" 
error-on-use-bean-invalid-class-attribute="false" 
generate-strings-as-char-arrays="false" 
java-encoding="UTF8" 
keep-generated="true" 
mapped-file="true" 
modification-test-interval="4" 
recompile-on-fail="false" 
smap="true" 
source-vm="1.6" 
tag-pooling="true" 
target-vm="1.6" 
trim-spaces="false" 
x-powered-by="true"/>

您可能会对配置 Undertow 会话 cookie 感兴趣。默认情况下,standalone.xml 文件中不包含配置文本,因此您需要将其作为 servlet-container 配置的子元素添加:

<servlet-container name="default">
    <jsp-config/>
    <session-cookie name="default" domain="yourdomain.com" http-only="true" max-age="60" secure="true"/>
</servlet-container>

session-cookie元素的可能的属性在以下表中显示。如果您没有明确设置这些值,则不会设置任何值,因为没有默认值:

属性 描述 默认值
name 该属性定义了 cookie 的名称
domain 该属性定义了 cookie 的域
comment 该属性定义了 cookie 的注释
http-only 该属性确定 cookie 是否为 HTTP-only true
secure 该属性确定 cookie 是否被标记为安全 true
max-age 该属性定义了 cookie 的最大年龄(以分钟为单位) 0(无限)

保存会话状态

保存会话允许在服务器重启或应用程序重新部署时存储会话数据。为了启用此功能,您需要将persistent-sessions元素添加到配置文件中,如下面的代码所示。此属性应在您的开发环境中使用,而不是在生产环境中使用。

<servlet-container name="default">
    <jsp-config/>
    <persistent-sessions path="/session" relative-to="${jboss.server.tmp.dir}"/>
</servlet-container>

提示

如果您没有指定path变量,则会话仅在重新部署之间持久,而不是在服务器重启之间持久。

配置缓冲区缓存

缓冲区缓存用于缓存内容,例如静态文件。缓冲区缓存由一个或多个区域组成,每个区域被分割成更小的缓冲区。以下是一个buffer-cache元素的配置示例:

<subsystem >
    <buffer-cache name="default" buffer-size="1024" buffers-per-region="2048" max-regions="10" />
    ...
</subsystem>

提示

总缓存大小可以通过将缓冲区大小乘以每个区域的缓冲区数量和最大区域数量来计算。在我们的例子中,它将是:

1024 字节 * 2048 * 10 = 20971520 字节

创建和部署 Web 应用程序

正如您所看到的,应用服务器提供了一个相对直接的方式来配置 Web 容器。为了构建和部署 Web 应用程序,了解如何组织应用程序及其特定的配置文件将是有益的。

WildFly 8 是一个符合 Java EE 7 的应用程序服务器,因此可以用于部署各种 Web 应用程序。构建 Web 应用程序的一种方式是使用JavaServer FacesJSF)技术,这是 JSP 技术的演变。它也是企业 Java 的一部分,这意味着 WildFly 默认支持它。WildFly 8 使用 Mojarra 实现支持 JSF 2.2 版本。

注意

本例的目的是向您展示如何在 WildFly 8 上创建、配置和部署一个 Java EE 7 应用程序。如果您想了解更多关于各种 Java EE 7 技术,您应该查看 Arun Gupta 创建的许多 Java EE 7 示例,这些示例已经针对 WildFly 进行了配置。GitHub URL 是github.com/javaee-samples/javaee7-samples

接下来,我们将创建一个简单的应用程序。这个例子旨在演示如何配置典型企业应用程序中发现的各个企业组件。

创建新的 Maven Web 项目

您可以使用多种方式在 Eclipse 中创建 Web 应用程序项目。由于 Maven 是事实上的构建工具,因此在这个例子中使用 Maven 项目结构是有意义的。

让我们从创建项目文件结构开始。转到 文件 | 新建 | Maven 项目,选择 跳过原型选择,创建一个简单项目,然后进入下一页。然后,根据以下截图完成工件详情,确保您选择 war 作为打包方式:

创建新的 Maven Web 项目

点击 完成 后,Eclipse 将为您的应用程序生成默认的文件夹结构:

创建新的 Maven Web 项目

我们将使用 JSF 来创建视图。配置 JSF 2.2 Web 应用程序需要非常少的努力。您可以通过以下步骤实现这一点:

  1. 创建一个名为 web.xml 的文件,并将其放置在您应用程序的 WEB-INF 文件夹中。

  2. FacesServlet 添加到您的 web.xml 文件中,并指定将哪些 URL 模式定向到它。

  3. 创建一个名为 faces-config.xml 的文件,并将其放置在 WEB-INF 文件夹中。

注意

FacesServlet 是一个管理使用 JavaServer Faces 构建用户界面的 Web 应用程序请求处理生命周期的 servlet。

这是完整的 web.xml 文件。您可以看到我们指定了 FacesServlet 将处理的 URL 模式:

<?xml version="1.0" encoding="UTF-8"?>
<web-app    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>Java EE 7 - WildFly 8</display-name>
  <welcome-file-list>
    <welcome-file>index.xhtml</welcome-file>
  </welcome-file-list>
  <context-param>
    <param-name>
      com.sun.faces.enableRestoreView11Compatibility
    </param-name>
    <param-value>true</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
  <context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
    <param-value>resources.application</param-value>
  </context-param>
  <listener>
    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
  </listener>
</web-app>

接下来,您将看到一个名为 faces-config.xml 的最小化 JSF 配置文件,该文件将被放置在您应用程序的 WEB-INF 文件夹中。此文件声明了我们将要使用的 JSF 版本,在我们的例子中是 2.2:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2">
</faces-config>

Eclipse 可以为您创建这些配置文件。为此,您需要激活 JavaServer Faces Facets。在您的项目上右键单击,选择 项目 属性。在这里,您将找到一组配置选项,可以在 项目 Facets 选项下自动添加到您的项目中。您可能需要修改文件以确保使用正确的命名空间,并更新 web.xml 文件的内容。

接下来,我们需要将项目依赖项添加到 Maven 配置文件 pom.xml 中。在项目构建时,Maven 将为您下载和管理所有依赖项。以下代码显示了 pom.xml 的完整内容:

<project  
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.packtpub</groupId>
    <artifactId>chapter4</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <description>Simple Java EE 7 example using WildFly</description>

    <repositories>
        <repository>
            <id>JBoss Repository</id>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-7.0</artifactId>
            <version>1.0.1.Final</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <!-- build plugins removed for brevity -->
</project>

提示

你会注意到正在使用 JBoss Nexus 仓库而不是 Maven Central。这是因为自从 Java EE 6 以来,JBoss 已经托管了自己的 EE API。这样做的原因是 Java EE 6 中未实现的方法。要了解完整的动机,请导航到developer.jboss.org/blogs/donnamishelly/2011/04/29/jboss-java-ee-api-specs-project。我建议你使用由 JBoss 提供的版本,因为它与 WildFly 中提供的代码相同。

添加 JSF 组件

为了学习如何打包 Java EE 7 应用程序,我们将向你展示如何结合 JSF 组件,例如 JSF 视图与 CDI 和 EJB 等企业组件。

在这个例子中,我们将创建一个简单的缓存系统,该系统使用 EJB 单例在内存中处理缓存。然后,我们向你展示如何将数据持久化到数据库。让我们先向你的动态 Web 项目添加一个名为index.xhtml的页面:

<!DOCTYPE html>
<html 

    >

<h:head>
    <link href="main.css" rel="stylesheet" type="text/css" />
</h:head>
<h:body>
    <h2>JSF 2 example on WildFly 8</h2>
    <h:form id="jsfexample">
        <h:messages />
        <h:panelGrid columns="2" styleClass="default">
            <h:outputText value="Enter key:" />
            <h:inputText value="#{manager.key}" />

            <h:outputText value="Enter value:" />
            <h:inputText value="#{manager.value}" />

            <h:commandButton actionListener="#{manager.save}"
                styleClass="buttons" value="Save key/value" />
            <h:commandButton actionListener="#{manager.clear}"
                styleClass="buttons" value="Clear cache" />
        </h:panelGrid>

        <h:dataTable value="#{manager.cacheList}" var="item"
            styleClass="table" headerClass="table-header"
            rowClasses="table-odd-row,table-even-row">
            <h:column>
                <f:facet name="header">Key</f:facet>
                <h:outputText value="#{item.key}" />
            </h:column>
            <h:column>
                <f:facet name="header">Value</f:facet>
                <h:outputText value="#{item.value}" />
            </h:column>
        </h:dataTable>
    </h:form>
</h:body>
</html>

注意

要了解 JSF,请参考在线教程docs.oracle.com/javaee/7/tutorial/doc/jsf-intro.htm

以下代码引用了一个名为manager的后备 Bean,用于存储和检索键/值对。后备 Bean 是简单的 Java 类,用作 UI 组件的模型。你也会注意到PropertyManager类中的@RequestScoped注解。

提示

当定义后备 Bean 的作用域时,如果你不使用 CDI(这几乎不可能),你应该只使用javax.faces.bean.RequestScoped注解。相反,你应该使用javax.enterprise.context.*包中的注解,它是上下文和依赖注入框架的一部分。

现在,让我们看看如何编码PropertyManager托管 Bean:

package com.packtpub.chapter4.bean;
import java.util.List;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.inject.Named;
import org.jboss.logging.Logger;
import com.packtpub.chapter4.ejb.SingletonBean;
import com.packtpub.chapter4.entity.Property;

@Named("manager") 
@RequestScoped 
public class PropertyManager { 

    private Logger logger = Logger.getLogger(getClass());

    @EJB 
    private SingletonBean ejb;
    private String key;
    private String value;

    public void save(ActionEvent e) { 
        try { 
            ejb.save(key, value);
            FacesContext.getCurrentInstance().addMessage( 
                    null, 
                    new FacesMessage(FacesMessage.SEVERITY_INFO, 
                            "Property Saved!", null));
        } catch (Exception ex) { 
            logger.error("Error saving property", ex);
            FacesContext.getCurrentInstance().addMessage( 
                    null, 
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, 
                            "Error Saving!", ex.getMessage()));
        }
    }
    public void clear(ActionEvent e) { 
        logger.info("Called clear");
        ejb.deleteAll();
    }
    public List<Property> getCacheList() { 
        return ejb.getProperties();
    }
// getters and setters removed for brevity
}

这个类最重要的部分是@Named注解。使用@Named注解这个类可以让它作为一个 CDI 托管 Bean 被识别。传递给注解的名称定义了如何通过表达式语言(EL)引用这个 Bean。接下来,使用@EJB注解将SingletonBean注入到类中。

注意

你可以在 Java EE 教程中了解更多关于 JSF 托管 Bean 的信息:docs.oracle.com/javaee/7/tutorial/doc/jsf-develop.htm

添加 EJB 层

SingletonBean是一个 EJB,它带有特殊的@javax.ejb.Singleton注解。带有此类注解的类保证每个应用程序只实例化一次,并且存在于应用程序的生命周期中。在 Java EE 上下文中,单例 Bean 主要用于存储应用程序范围内的共享数据。现在,我们需要创建一个名为SingletonBean的新类。这个类的目的是保存和检索键/值对:

package com.packtpub.chapter4.ejb;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.ejb.LocalBean;
import javax.ejb.Remote;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import com.packtpub.chapter4.entity.Property;

@Singleton
@LocalBean
public class SingletonBean {

    private List<Property> cache = new ArrayList<>();

    @PostConstruct
    public void initCache() {
        this.cache = queryCache();
        if (cache == null) {
            cache = new ArrayList<Property>();
        }
    }

    public void deleteAll() {
        this.cache.clear();
    }

    public void save(String key, String value) {
        Property property = new Property(key, value);
        this.cache.add(property);
    }

    public List<Property> getProperties() {
        return cache;
    }
}

我们需要添加的最后一个类是Property,它是一个普通的JavaBean类:

package com.packtpub.chapter4.entity;

public class Property {
    private String key;
    private String value;
    // GETTERS & SETTERS omitted for brevity 
}

一旦达到这一点,您应该有一个包含以下截图所示内容的项的项目:

添加 EJB 层

选择应用程序的 Web 上下文

默认情况下,Web 应用程序从部署在应用程序服务器上的存档名称继承 Web 上下文名称。Maven 使用 artifact ID,后跟版本来命名存档。因此,在我们的示例中,如果我们部署一个名为 chapter4-0.0.1-SNAPSHOT.war 的存档,它将可以通过 chapter4-0.0.1-SNAPSHOT 的 Web 上下文名称访问,如下面的图像所示:

选择应用程序的 Web 上下文

上下文名称可以修改为更有意义的内容。最简单的方法(不更改存档名称)是在项目的 WEB-INF 文件夹中添加一个 jboss-web.xml 文件:

选择应用程序的 Web 上下文

此文件的内容将包括由 context-root 元素指定的自定义 Web 上下文:

<jboss-web>
  <context-root>chapter4</context-root>
</jboss-web>

部署 Web 应用程序

一旦您对设置满意,您就可以部署并验证您的应用程序。如果您在 Eclipse 内部部署应用程序,只需右键单击 WildFly 运行时服务器并选择 添加 移除 选项(假设您已按照 第一章 中所示安装了 WildFly 运行时,安装 WildFly)。接下来,将 Web 项目添加到已部署项目的列表中。

然后,您可以通过右键单击项目并选择 完全 发布 来部署应用程序:

部署 Web 应用程序

发布您的应用程序后,您会注意到 Eclipse 会将您的 Web 应用程序存档 (chapter4-0.0.1-SNAPSHOT.war) 复制到服务器。它还会创建一个名为 chapter4-0.0.1-SNAPSHOT.war.dodeploy 的文件。正如您将在 第六章 中学习到的,应用程序结构和部署,默认情况下,展开的存档需要 WildFly 中的一个标记文件来触发部署。Eclipse 知道这一点,并为您创建该文件。

部署成功后,chapter4-0.0.1-SNAPSHOT.war.dodeploy 文件将被一个名为 chapter4-0.0.1-SNAPSHOT.war.deployed 的标记文件替换,这表明您已成功部署了 Web 应用程序。您可以通过指向 http://localhost:8080/chapter4/index.xhtml 上的 index.xhtml 页面来验证您的应用程序是否正常工作,如下面的截图所示:

部署 Web 应用程序

将 Web 应用程序部署到根上下文

在我们的例子中,我们展示了如何使用 jboss-web.xml 将 Web 应用程序部署到自定义上下文。Web 上下文的一个特殊情况是 root 上下文。这通常解析为 http://localhost:8080,并由 Web 服务器提供一些欢迎上下文。但是,您可以通过将您的应用程序之一部署到 root 上下文来覆盖它。这需要两个简单的步骤:

  1. 首先,您需要从您的 Undertow 子系统中删除以下行:

    <location name="/" handler="welcome-content"/>
    
  2. 然后,在您的应用程序中添加一个包含应用程序 root 上下文的 jboss-web.xml 文件:

    <jboss-web>
        <context-root>/</context-root>
    </jboss-web>
    

添加远程 EJB 客户端

在为远程 EJB 客户端添加任何代码之前,我们需要向 pom.xml 添加两个依赖项。这确保了我们的代码将编译并运行而不会出错:

<!-- this is required for a security -->
<dependency>
    <groupId>org.jboss.sasl</groupId>
    <artifactId>jboss-sasl</artifactId>
    <version>1.0.4.Final</version>
    <scope>provided</scope>
</dependency>
<!-- this is required for the RemoteEJBClient.java to compile -->
<dependency>
    <groupId>org.jboss</groupId>
    <artifactId>jboss-ejb-client</artifactId>
    <version>2.0.2.Final</version>
    <scope>provided</scope>
</dependency>

为了使用远程客户端测试我们的应用程序,我们需要创建一个指向 EJB 的远程接口:

package com.packtpub.chapter4.ejb;

import java.util.List;
import com.packtpub.chapter4.entity.Property;

public interface SingletonBeanRemote {
    public void deleteAll();
    public void save(String key, String value);
    public List<Property> getProperties();
}

该接口的具体实现是 SingletonBeanRemoteImpl 类,它具有与我们在早期部分中展示的 SingletonBean 类相同的 Java 方法实现:

@Singleton
@LocalBean
@Remote(SingletonBeanRemote.class)
public class  SingletonBean implements SingletonBeanRemote  {
// Bean class unchanged
}

EJB 远程调用通过 Remoting 框架进行,该框架使用 简单 认证 和安全 SASL)进行客户端-服务器认证。您需要通过向测试客户端添加以下规范来显式设置安全提供程序:

static {
  Security.addProvider(new JBossSaslProvider());
}

下一个部分相当棘手。我们需要确定 EJB 的 Java 命名和目录接口JNDI)名称,为此我们需要查找远程 EJB。JNDI 名称取决于 EJB 是否有状态或无状态。以下表格概述了 SLSB 和 SFSB 的语法:

EJB 类型 JNDI 语法
无状态 EJB ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>
有状态 EJB ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>?stateful

以下表格分别列出这些属性:

参数 描述
app-name 这是应用程序名称,在应用程序已作为企业存档部署的情况下使用。它通常对应于不带 .ear 的企业存档名称。由于我们将应用程序打包在 Web 存档中,此参数将不会使用。
module-name 这是包含 EJB 的模块。由于我们将应用程序部署在名为 chapter4-0.0.1-SNAPSHOT.war 的文件中,它对应于 chapter4-0.0.1-SNAPSHOT
distinct-name 这是一个可选的名称,可以用来区分不同的 EJB 实现。在我们的例子中未使用。
bean-name 这是 EJB 名称,默认情况下,它是 EJB 实现类的类名,在我们的例子中,是 SingletonBeanRemoteImpl
fully-qualified-classname-of-the-remote-interface 这显然对应于你正在查找的接口的完全限定类名,在我们的例子中,是com.packtpub.chapter4.ejb.SingletonBeanRemote

注意

请注意,有状态的 EJB 需要在 JNDI 查找名称中添加一个额外的?stateful参数。

在了解 JNDI 命名空间的信息后,你将准备好理解客户端代码:

package com.packtpub.chapter4.client;

import java.security.Security;
import java.util.*;
import javax.naming.*;
import org.jboss.ejb.client.*;
import org.jboss.sasl.JBossSaslProvider;
import com.packtpub.chapter4.ejb.SingletonBean;
import com.packtpub.chapter4.ejb.SingletonBeanRemote;
import com.packtpub.chapter4.entity.Property;

public class RemoteEJBClient { 
    static { 
        Security.addProvider(new JBossSaslProvider());
    }
    public static void main(String[] args) throws Exception { 
        testRemoteEJB();
    }
    private static void testRemoteEJB() throws NamingException { 
        final SingletonBeanRemote ejb = lookupEJB();
        ejb.save("entry", "value");
        List<Property> list = ejb.getProperties();
        System.out.println(list);
    }
    private static SingletonBeanRemote lookupEJB() throws NamingException { 

        Properties clientProperties = new Properties();
        clientProperties.put("endpoint.name", "client-endpoint");
        clientProperties.put("remote.connections", "default");
        clientProperties.put("remote.connection.default.port", "8080");
        clientProperties.put("remote.connection.default.host", "localhost");
        clientProperties.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
        clientProperties.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");

        EJBClientConfiguration ejbClientConfiguration = new PropertiesBasedEJBClientConfiguration(clientProperties);
        ContextSelector<EJBClientContext> ejbContextSelector = new ConfigBasedEJBClientContextSelector(ejbClientConfiguration);

        EJBClientContext.setSelector(ejbContextSelector);

        final Hashtable<String, String> jndiProperties =           new Hashtable<>();
        jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        final Context context = new InitialContext(jndiProperties);
        final String appName = "";
        final String moduleName = "chapter4-webapp-example-0.0.1-SNAPSHOT";
        final String distinctName = "";

        final String beanName = SingletonBean.class.getSimpleName();
        final String viewClassName = SingletonBeanRemote.class.getName();
        return (SingletonBeanRemote) context.lookup("ejb:" + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName);
    }
}

如你所见,远程 EJB 客户端代码的主要复杂性在于 JNDI 查找部分。你可能已经注意到,在突出显示的部分,我们使用名为Context.URL_PKG_PREFIXES的属性初始化了 JNDI 上下文,以指定在加载 URL 上下文工厂时要使用的包前缀列表。在我们的例子中,我们将其设置为org.jboss.ejb.client.naming,这样 JNDI API 就知道哪些类负责处理ejb:命名空间。

使用属性文件配置客户端

最后,你可能想知道客户端实际上是如何知道远程 EJB 托管的服务器位置的。这可以通过向客户端类路径中添加以下名为jboss-ejb-client.properties的客户端属性文件来解决:

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=localhost
remote.connection.default.port = 8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

在此文件中,你可以指定一组以remote.connectionprovider.create.options为前缀的属性,这些属性将在远程连接期间使用。在我们的例子中,我们只是将org.xnio.Options.SSL_ENABLED属性设置为false,这意味着将使用明文传输来连接客户端和服务器。

remote.connections属性用于指定一组一个或多个映射到 EJB 接收器的连接。在我们的例子中,有一个名为default的单个远程连接,它映射到localhost和远程端口8080

最后,我们需要指定将使用 SASL 匿名连接;否则,如果没有认证,我们的连接将被拒绝。

程序化配置客户端

另一种配置客户端连接属性的方法是程序化配置。在这里,我们创建一个Properties对象,并用与jboss-ejb-client.properties配置文件中相同的键值对填充它。代码中的重要部分用粗体突出显示:

private static SingletonBeanRemote lookupEJB() throws NamingException {
    Properties clientProperties = new Properties();
    clientProperties.put("endpoint.name", "client-endpoint");
    clientProperties.put("remote.connections", "default");
    clientProperties.put("remote.connection.default.port", "8080");
    clientProperties.put("remote.connection.default.host", "localhost");
    clientProperties.put("remote.connectionprovider.
create.options.org.xnio.Options.SSL_ENABLED", "false");
    clientProperties.put("remote.connection.default.
connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");

    EJBClientConfiguration ejbClientConfiguration = new PropertiesBasedEJBClientConfiguration(clientProperties);
        ContextSelector<EJBClientContext> ejbContextSelector = new ConfigBasedEJBClientContextSelector(ejbClientConfiguration);

    EJBClientContext.setSelector(ejbContextSelector);

    final Hashtable<String, String> jndiProperties = new Hashtable<>();
    jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
    final Context context = new InitialContext(jndiProperties);
    final String appName = "";
    final String moduleName = "chapter4-webapp-example-0.0.1-SNAPSHOT";
    final String distinctName = "";

    final String beanName = SingletonBean.class.getSimpleName();
    final String viewClassName = SingletonBeanRemote.class.getName();
    return (SingletonBeanRemote) context.lookup("ejb:" + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName);
    }

配置数据持久化

我们现在将通过将键值对存储在关系数据库中而不是保留在内存中来进一步增强我们的应用程序。为此,我们需要创建一个持久化 上下文。再次提醒你,它的目的不是教授数据持久化的理论,而是展示如何在应用程序中配置它。

持久化子系统默认包含在所有服务器配置中:

<extension module="org.jboss.as.jpa"/>
<subsystem ></subsystem>

JPA 模块默认情况下不会在应用服务器中加载。然而,一旦应用服务器检测到你的应用程序包含persistence.xml或持久化注解,JPA 模块将会自动启动。

因此,让我们将 JPA 的 persistence.xml 配置文件添加到我们的项目中,它将引用用于将我们的实体映射到数据库的数据源:

<?xml version="1.0" encoding="UTF-8"?>
<persistence 

    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/persistence/persistence_2_1.xsd"
    version="2.1">
    <persistence-unit name="persistenceUnit" transaction-type="JTA">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/MySqlDS</jta-data-source>  
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
        </properties>
    </persistence-unit>
</persistence>

此文件的关键属性是持久化单元的 name,它将标识其唯一名称,以及 jta-data-source,它必须匹配有效的数据源定义。在早期章节中,我们定义了这个绑定到 MySQL 数据库的数据源。

注意

persistence.xml 文件可以指定 JTA 数据源或非 JTA 数据源。在 Java EE 环境中,你必须使用 JTA 数据源(即使在没有活动事务的情况下读取数据)。

最后,properties 元素可以包含底层持久化提供者的任何配置属性。由于 WildFly 使用 Hibernate 作为 EJB 持久化提供者,你可以在其中传递任何 Hibernate 选项。

一旦创建,此文件需要放置在 source/main/resources 文件夹的 META-INF 文件夹中,如下面的截图所示:

配置数据持久化

注意

持久化.xml 文件的实际路径

请注意,当 Maven 构建时,Eclipse src/main/resources 目录的内容将被放置在您的 Web 应用程序的 WEB-INF/classes 目录中。

为 JPA 子系统使用默认数据源

在此示例中,我们是从 persistence.xml 文件中引用数据源,因此遵循许多开发者都熟悉的规范方法。

然而,你可以通过在 JPA 子系统中添加 default-datasource 元素来为所有 JPA 应用程序选择默认数据源:

<subsystem >
  <jpa default-datasource="java:jboss/datasources/MySqlDS"/>
</subsystem>

这样,所有尚未在 persistence.xml 文件中定义 jta-data-source 元素的 JPA 应用程序将使用主服务器配置文件中配置的默认数据源。

配置实体

一旦定义了持久化配置,我们只需要在我们的应用程序中添加 javax.persistence 注解到我们的实体类。@Entity 注解表示该类将被注册为 JPA 实体:

package com.packtpub.chapter4.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Property implements Serializable {
    @Id
    @Column(name = "id")
    private String key;
    @Column(name = "value")
    private String value;
    //getters and setters omitted for brevity
}

我们的会话 Bean 也需要更改。我们将不再只向内存缓存读写,而是同时写入缓存和数据库,并且只从内存缓存中读取。当应用程序重新启动时,内存缓存将用从数据库查询的数据填充。尽管这并不复杂,但为了演示的目的,这完全是可以接受的:

import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.persistence.*;
import com.packtpub.chapter4.entity.Property;

@Singleton
public class  SingletonBean   {
  private  List<Property> cache;
  @PersistenceContext(unitName = "persistenceUnit")
  private EntityManager em;

  @PostConstruct
  public void initCache(){
    this.cache = queryCache();
    if (cache == null) cache = new ArrayList<Property>();
  }

  public void delete(){
    Query query = em.createQuery("delete FROM 
com.packtpub.chapter4.entity.Property");
    query.executeUpdate();
    this.cache.clear();
  }

  public void put(String key,String value){
    Property p = new Property();
    p.setKey(key);
    p.setValue(value);
    em.persist(p);
    this.cache.add(p);
  }

package com.packtpub.chapter4.ejb;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.ejb.LocalBean;
import javax.ejb.Remote;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import com.packtpub.chapter4.entity.Property;

@Singleton 
@LocalBean 
public class SingletonBean { 

    private List<Property> cache = new ArrayList<>();

    @PersistenceContext(unitName = "persistenceUnit")
    private EntityManager em;

    @PostConstruct 
    public void initCache() { 
        this.cache = queryCache();
        if (cache == null) { 
            cache = new ArrayList<Property>();
        }
    }

    public void deleteAll() { 
        Query query = em.createQuery("DELETE FROM Property");
        query.executeUpdate();
    }

    public void save(String key, String value) { 
        Property property = new Property(key, value);
        em.persist(property);
        this.cache.add(property);
    }

    private List<Property> queryCache() { 
        TypedQuery<Property> query = em.createQuery("FROM Property", Property.class);
        List<Property> list = query.getResultList();
        return list;
    }

    public List<Property> getProperties() { 
        return cache;
    }
}

以下代码段已被突出显示,以显示代码是如何修改以使用数据持久化的。最相关的部分是 @javax.persistence.PersistenceContext 注解,它引用了在 persistence.xml 文件中定义的 JPA 上下文。

一旦部署,此应用程序将数据持久化到您的 MySQL 数据库。

在其他应用程序存档中配置持久化

在我们的示例中,我们创建了一个由 Web 组件和 EJB 组成的 Java EE 7 应用程序,使用单个 Web 应用程序存档。这是绝对正常且预期的,因为 Java EE 允许在单个 Web 存档中混合和匹配前端组件和后端组件。

您可以部署一个应用程序,其中 Web 层与业务服务层分离。例如,假设您将实体部署在单独的 JAR 文件中;persistence.xml 文件的正确位置是在您的 JAR 存档的 META-INF 文件夹下。

注意

为了确认,如果您将您的 JPA 实体放置在 WAR 文件中,persistence.xml 文件应放置在 WEB-INF/classes/META-INF 文件夹中。如果您在 Web 应用程序内部将 JPA 实体打包在一个 JAR 文件中,您应将 persistence.xml 文件放置在 META-INF 文件夹中。

从技术角度讲,如果您在应用程序中有多个 JAR 文件,您可以将 persistence.xml 文件部署在单个存档中,并使用 jarName#unitName 语法引用持久化单元。例如,此应用程序的持久化单元可以通过以下注解从另一个 JAR 文件中引用:

@PersistenceContext(unitName="wildflyapp.jar#unitName")

切换到不同的提供商

默认情况下,WildFly 8.1 使用 Hibernate 4.3.5 作为持久化提供者。Hibernate JAR 包含在 org.hibernate 路径下的 modules 文件夹中。但是,如果您的应用程序需要 Hibernate 的不同版本,例如 3.5,您仍然可以通过在 pom.xml 文件中添加依赖项并将作用域设置为 runtime 来将 JAR 包捆绑到您的应用程序中:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>3.5.0-Final</version>
    <scope>runtime</scope>
</dependency>

此外,您需要在 persistence.xml 配置文件中将 jboss.as.jpa.providerModule 属性设置为 hibernate3-bundled。JPA 部署程序将检测到持久化提供者的不同版本,并激活该版本:

<persistence-unit>
    <properties>
        <property name="jboss.as.jpa.providerModule" value="hibernate3-bundled" />
    </properties>
</persistence-unit>

使用 Jipijapa

您还可以使用 Jipijapa 项目简化切换到不同的 JPA 提供商。如果您使用 Jipijapa,您需要确保您的持久化提供者被包含在 pom.xml 文件中的运行时依赖项中,并且您还需要包含正确的 Jipijapa 集成 JAR 文件。要使用 Hibernate 3,您需要在 pom.xml 中添加以下依赖项:

<dependency>
  <groupId>org.jipijapa</groupId>
  <artifactId>jipijapa-hibernate3</artifactId>
  <version>1.0.1.Final</version>
</dependency>

使用 Jipijapa,您可以轻松切换到 Hibernate 的不同版本,或者切换到不同的 ORM 提供商,例如 EclipseLink 或 OpenJPA。有关使用 Jipijapa 项目的更多详细信息,您可以参考 WildFly 文档,网址为 docs.jboss.org/author/display/WFLY8/JPA+Reference+Guide#JPAReferenceGuide-BackgroundontheJipijapaproject

摘要

在本章中,我们讨论了 Undertow 子系统配置,该配置位于主配置文件中。

Undertow 服务器配置分为两个主要部分:服务器配置,用于配置静态资源,例如 HTML 页面、图像、监听器和主机,以及 Servlet 容器配置,用于配置动态资源,例如 JSP。

然后,我们通过一个示例应用演示了如何在应用服务器上打包和部署 Java EE 7 Web 模块。

然后,我们讨论了 JPA 子系统,并展示了如何将数据持久性添加到初始示例中。我们概述了persistence.xml文件的正确位置,该文件必须放置在您的 Web 应用的WEB-INF/classes/META-INF文件夹中,或者您的 JAR 文件的META-INF文件夹中。

完成应用服务器的独立配置后,我们现在将进入下一章,探讨如何配置应用服务器域。

第五章:配置 WildFly 域

现在我们已经了解了服务器的核心配置,我们可以继续到域配置。塑造服务器域是管理员想要高效协调一组应用服务器时的关键任务。在本章中,我们将描述创建和配置 WildFly 实例域所需的所有步骤。

如我们很快就会看到的,子系统配置在独立和域配置之间没有变化。要处理域,我们还需要了解域控制器和主机控制器配置。这些负责处理和协调多个服务器上应用程序的生命周期。

因此,在本章中,我们将涵盖以下主题:

  • WildFly 域简介

  • 如何配置域组件

  • 选择域和独立服务器之间的标准

  • 介绍 WildFly 域模式

介绍 WildFly 域

域模式的概念可能觉得有点难以理解。这是因为,在 Java EE 范式下,人们习惯于处理服务器而不是域。

基本上,域是由一个服务器管理的 WildFly 服务器集合。管理域的服务器被称为域控制器。这个组属于一个管理单元——它是管理单元。重要的是要理解,域的概念不会干扰由管理服务器提供的功能。例如,您可能设置了一个运行在集群中的应用服务器节点域,提供负载均衡和高可用性。然而,您也可以通过一组独立的应用服务器实现相同的结果。

这两种场景的不同之处在于,在域模式下运行时,您可以从单个集中单元中高效地管理您的服务器集合。另一方面,管理一组独立实例通常需要复杂的多服务器管理能力,这些能力复杂得多,容易出错,且耗时更长。

从进程的角度来看,域由四个元素组成:

  • 域控制器:域控制器是您域的管理控制点。在域模式下运行的 AS 实例最多有一个进程实例作为域控制器。域控制器持有中央配置,该配置由属于域的节点实例共享。

  • 主机控制器:这是一个负责协调服务器进程的生命周期以及部署的分配(从域控制器到服务器实例),与域控制器一起。

  • 进程控制器:这是一个非常轻量级的进程,其主要功能是生成服务器和主机控制器进程,并管理它们的输入/输出流。这也允许主机控制器在不影响相关服务器的情况下进行修补和重启。

  • 应用程序 服务器 节点:这些是映射到应用程序服务器实例的常规 Java 进程。每个服务器节点反过来属于一个域组。当讨论域配置文件时,将详细解释域组。

为了理解如何配置这些组件,我们首先将查看基本域配置。此配置与应用程序服务器默认分发一起提供。

理解默认域配置

默认情况下,域配置(domain.xml)包括以下元素的基本配置:

  • 一个启动其他 JVM 进程的进程控制器

  • 一个充当域控制器的主机控制器

  • 三个服务器节点,前两个属于主服务器组,第三个(不活跃)属于其他服务器组

注意

服务器组是一组配置相同且作为一个整体管理的服务器。

下面的图像加强了这些概念:

理解默认域配置

您可以使用VisualVM实用程序从 JVM 的角度查看您域的低级细节。您可以从以下截图看到生成了四个 JVM 进程。首先启动的是进程控制器,然后它依次启动主机控制器进程和两个服务器节点。

理解默认域配置

注意

VisualVM是一个包含在默认 Java SE 分发的 Java 虚拟机监控工具。您可以在JAVA_HOME/bin文件夹中找到它。在 Windows 上,只需启动jvisualvm.exe,在 Linux 上则启动jvisualvm

从前面的截图可以注意到的重点是,在基本域设置中,主机控制器也充当域控制器,也就是说,主机控制器持有域的集中配置。这意味着主机控制器和域控制器共享同一个 JVM 进程。

在完成对应用程序服务器域的基本介绍后,我们将现在涵盖有关其配置的所有细节。

启动和停止域

启动 WildFly 域只需运行JBOSS_HOME\bin\domain.sh脚本(在 Windows 上为JBOSS_HOME\bin\domain.bat)。几秒钟内,您的域将启动并运行。请查看以下截图:

启动和停止域

为了停止应用程序服务器域,您可以在启动域的同一窗口中使用Ctrl + C快捷键,或者您可以使用命令行客户端并向主机控制器发出shutdown命令。

Unix/Linux 用户可以执行以下命令:

./jboss-cli.sh --connect command=/host=master:shutdown

Windows 用户可以执行以下命令:

jboss-cli.bat --connect command=/host=master:shutdown

注意

默认主机名是master,它在host.xml文件中定义,该文件位于JBOSS_HOME\domain\configuration文件夹中。我们将在下一节中了解更多关于它的信息。

一旦域启动,JBOSS_HOME\domain\log目录内将创建几个日志文件。主机控制器活动被写入host-controller.log文件,而进程控制器日志被写入process-controller.log文件。

配置域

设置 WildFly 域的主要优势之一是能够从单个集中点控制和管理服务器配置和部署。主要域配置由以下两个文件组成,这些文件位于JBOSS_HOME\domain\configuration文件夹中,如下所示:

  • domain.xml:此文件描述了你的域服务器的功能,并定义了域中的服务器组。虽然此文件可以在每个主机上找到,但只有位于域控制器上的domain.xml文件被使用。

  • host.xml:此文件存在于安装域的每个主机上,并指定了特定于在主机上运行的服务器的元素。

覆盖默认配置文件

可以使用除了在标准安装中提供的默认文件之外的配置文件。你可以通过在 shell 命令中添加以下参数来使用自己的自定义配置文件:

./domain.sh --domain-config=custom_domain.xml
./domain.sh –host-config=custom_host.xml

Windows 用户使用相同的参数,但显然使用domain.bat文件。

注意,如果你没有提供任何自定义配置文件的路径,它将被假定为相对于jboss.server.config.dir目录的相对路径。否则,你需要提供文件的绝对路径。

配置 domain.xml 文件

domain.xml文件包含了域中所有服务器共享的域子系统配置。文件内容遵循独立文件的结构,但有一个明显且重要的区别——域中可以定义多个配置文件。默认情况下,定义了四个配置文件:一个默认配置文件、一个完整配置文件、一个 ha 配置文件,最后是一个全 ha 配置文件,后两个用于集群域。然而,你也可以定义自己的自定义配置文件,例如消息配置文件,如下面的图像所示:

配置 domain.xml 文件

提示

从一个配置文件切换到另一个配置文件是推荐的方式来扩展或缩小你域中运行的服务器的功能。

每个 WildFly 域可以进一步拆分为服务器组,每个组绑定到不同的配置文件。服务器组的概念可以看作是域管理作为一个单一单元的一组服务器。实际上,您可以使用服务器组进行节点细粒度配置;例如,每个服务器组能够定义自己的设置,如定制的 JVM 设置、套接字绑定接口和部署的应用程序。以下图解说明了可以应用于服务器组内服务器的一些常见属性:

配置 domain.xml 文件

例如,以下是一个更完整的与default配置文件绑定的服务器组定义。此服务器组定义了一个名为sample.war的 Web 应用程序,该应用程序可供组内的所有服务器使用。它还定义了一个定制的 JVM 配置和一些系统属性(在启动时加载),并将其服务绑定到standard-sockets定义,如下所示:

<server-group name="custom-server-group" profile="default">
  <deployments>
    <deployment name="sample.war_v1" runtime-name="sample.war" /> 
  </deployments>
  <jvm name="default">
    <heap size="512m" max-size="1g"/>
  </jvm>
  <socket-binding-group ref="standard-sockets"/>
  <system-properties>
    <property name="foo" value="bar" boot-time="true"/>
    <property name="key" value="value" boot-time="true"/>
  </system-properties>
</server-group>

配置 host.xml 文件

另一个域配置文件名为host.xml,位于JBOSS_HOME\domain\configuration文件夹中。此文件基本上定义并配置了作为域一部分运行在主机上的服务器节点。这里使用的“主机”一词表示物理或虚拟主机。在每一个主机中,都包含域中服务器的一部分。每个主机可以有零个或多个服务器实例。以下图解说明了这些细节:

配置 host.xml 文件

如您所见,一个域可以包含多个主机(host1host2)以及多个组(主服务器组其他服务器组)。然而,虽然服务器组是服务器节点(可以位于任何位置)的逻辑关联,但主机指的是位于同一物理或虚拟机器上的节点集合。在提供了我们对主机的定义之后,我们现在来看看主机配置文件,它允许您配置以下一组核心域元素:

  • 用于控制域的管理接口

  • 域控制器定义

  • 绑定服务的网络接口

  • 定义好的 JVM 的配置

  • 域的一部分服务器

在下一节中,我们将详细查看host.xml文件的每个元素,并学习如何适当地配置它。

配置管理接口

管理接口包括用于管理域的本地命令行****接口CLI)和http接口的定义。以下示例取自host.xml文件:

<management-interfaces>
    <native-interface security-realm="ManagementRealm">
        <socket interface="management" port="9999"/>
    </native-interface>
    <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
        <socket interface="management" port="9990"/>
    </http-interface>
</management-interfaces>

使用默认配置,两个服务都绑定到management网络接口。CLI 和管理接口监听端口9990。如果您出于某种原因想要恢复到 JBoss AS 7 设置并在端口9999上运行,则native接口配置应保持不变。

配置网络接口

我们刚刚提到了网络接口。正如其名称所暗示的,一个网络接口指的是一个网络地址或一组网络地址。默认情况下,服务器包含三个网络接口定义,即管理公共不安全,所有这些都被绑定到回环地址(127.0.0.1)。

通过更改网络接口的inet-address值,您可以配置应用程序服务器的监听地址。例如,如果我们想将管理接口绑定到回环地址(127.0.0.1),并将公共接口绑定到地址192.168.1.1,您可以使用以下配置:

<interfaces>
 <interface name="management">
        <inet-address value="127.0.0.1"/>
    </interface>
 <interface name="public">
        <inet-address value="192.168.1.1"/>
    </interface>
</interfaces>

您也可以通过运行以下命令通过命令行更新这些属性:

[standalone@localhost:9990 /] /interface=management:write-attribute(name=inet-address,value=127.0.0.1)
[standalone@localhost:9990 /] /interface=public:write-attribute(name=inet-address,value=192.168.1.1)

实际上,这意味着管理接口(http管理控制台和 CLI)将被绑定到回环地址,而与应用程序相关的服务(绑定到公共接口)将被绑定到 IP 地址192.168.1.1。以下配置来自domain.xml文件。在这里,您可以看到它是如何使用之前定义的公共接口的:

<socket-binding-group name="standard-sockets" default-interface="public">
  <socket-binding name="http" port="8080"/>
  <socket-binding name="https" port="8443"/>
  ...
</socket-binding-group>

配置域控制器

默认情况下,域控制器位于您启动域的同一台机器上。请看以下命令:

<domain-controller>
  <local/>
</domain-controller>

您可以按照以下方式配置您的宿主以使用位于远程宿主上的域控制器:

<domain-controller>
  <remote host="192.168.100.1" port="9999" security-realm="ManagementRealm"/>
</domain-controller>

注意

如果管理接口绑定到localhost,则此操作不会生效。请确保您正确更新了管理接口。

在远程宿主机上配置域控制器意味着将不会使用本地配置(domain.xml),并且该宿主机上的所有服务器节点都将使用集中化的远程配置。您需要授权才能访问域控制器。我们将在本章末尾的域示例中详细介绍这一点。

配置 JVM

域配置的一个关键方面是为给定主机定义 JVM 参数。JVM 的元素在host.xml文件中定义。在这里,您可以定义 JVM 设置并将它们与一个名称关联:

<jvms> 
    <permgen size="256m" max-size="256m"/> 
    <jvm-options> 
      <option value="-server"/> 
    </jvm-options> 
</jvms>

小贴士

目前,没有可用的元素来配置 Java 8 Metaspace 属性。要配置这些属性,您需要将它们作为option元素添加。要设置初始大小,使用-XX:MetaspaceSize=256m,要设置最大大小,使用-XX:MaxMetaspaceSize=256m

然后,您可以通过在server-group配置中引用jvm名称属性,将此 JVM 定义用作服务器组配置的一部分。请注意,server-group中的任何 JVM 定义都会覆盖jvms定义中的定义。例如,main-server-groupdomain.xml)服务器组使用所有服务器节点的default JVM,但重新定义了heap max-sizesize值。请看以下代码:

<server-group name="main-server-group" profile="full">
  <jvm name="default">
    <heap size="64m" max-size="512m"/>
  </jvm>
  <socket-binding-group ref="full-sockets"/>
</server-group>

定义好的 JVM 也可以关联到单个服务器,从而覆盖服务器组定义。例如,在这里,server-one(在 host.xml 中定义)继承了 default JVM 配置,但随后覆盖了最小(512 MB)和最大堆大小(1 GB):

<server name="server-one" group="main-server-group" auto-start="true">
  <jvm name="default">
    <heap size="512m" max-size="1G"/>
  </jvm>
</server>

向服务器定义添加 JVM 选项

如果你想进一步专业化你的 JVM 配置,例如,通过向虚拟机添加非标准选项,你可以使用 jvm-options 元素(host.xml)。在这个例子中,我们将并发、低延迟垃圾收集器添加到 default JVM 选项中:

<jvms>
    <jvm name="default">
        <heap size="64m" max-size="128m"/>
        <jvm-options>
            <jvm-option value="-XX:+UseConcMarkSweepGC"/>
        </jvm-options>
    </jvm>
</jvms>

元素之间的优先级顺序

在上一节中,我们向您展示了如何在不同的配置文件(host.xmldomain.xml)中使用 default JVM 定义。事实上,JVM 定义是配置在文件之间重叠的典型例子,这意味着 JVM 可以在任何以下级别进行配置:

  • 主机级别:此配置将应用于 host.xml 中定义的所有服务器

  • 服务器组级别:此配置适用于组中所有服务器

  • 服务器级别:此配置仅用于单个主机

到目前为止,一切顺利。然而,如果我们定义了多个级别具有相同名称的元素,会发生什么?应用程序服务器通过允许最具体的元素覆盖其父配置来解决此问题。换句话说,如果你在主机级别定义了一个通用的 JVM,它将被服务器组级别的相同 JVM 覆盖。看看以下代码:

<!-- host.xml -->
<jvms>
  <jvm name="default">
    <heap size="64m" max-size="256m"/>
  </jvm>
</jvms>

<!—- domain.xml -->

<!—- Here the "default" jvm will be overridden by the server group jvm definition -->

<server-group name="other-server-group" profile="default">
  <jvm name="default">
    <heap size="64m" max-size="512m"/>
  </jvm>
  <socket-binding-group ref="standard-sockets"/>
</server-group>

如果你也在服务器级别定义它,那么这就是该服务器的最终选择。看看以下代码:

<!- Here, the server definition overrides any other host/group definition -->
<server name="server-one" group="main-server-group">
  <jvm name="default">
    <heap size="256m" max-size="768m"/>
  </jvm>
</server>

以下图描述了可以在不同配置级别定义(和可能覆盖)的元素:

元素优先级顺序

如您所见,此列表还包括一些元素,如 <path> 元素、<interface> 元素和 <system-properties> 元素,我们已在 第二章 中讨论过,配置 WildFly 核心子系统

配置服务器节点

主机配置的最后元素包括构成域的服务器节点列表。配置服务器至少需要服务器的名称和服务器所属的组。看看以下代码:

<!-- host.xml configuration file -->
<servers>
  <server name="server-one" group="main-server-group" />
</servers>

这个服务器定义在很大程度上依赖于应用程序服务器节点的默认属性。然而,你可以通过添加特定的路径、套接字绑定接口、系统属性和 JVM 来高度自定义你的服务器。看看以下代码:

<server auto-start="true" name="sample" group="sample-group" >
  <paths>
    <path name="example" path="example" relative-to="jboss.server.log.dir"/>
  </paths>
  <socket-bindings port-offset="259" socket-binding-group="standard-sockets" />
  <system-properties>
    <property boot-time="true" name="envVar" value="12345"/>
  </system-properties>
  <jvm name="default">
    <heap size="256m" max-size="512m"/>
  </jvm>
</server>

如果你想了解服务器节点配置的所有适用属性,我们建议你查看位于服务器发行版JBOSS_HOME/docs/schema文件夹中的jboss-as-config_2_1.xsd模式,在 Eclipse 中,你可以右键单击模式文件,然后点击生成 | XML 文件

应用域配置

对于刚开始接触域概念的新用户来说,一个常见的误解是认为域几乎等同于节点集群,因此它可以用来实现重要功能,如负载均衡和高可用性。

重要的是要理解,域与你的应用程序提供的功能无关——域的设计是基于服务器管理的概念。因此,你可以用它来管理集群应用程序和非集群应用程序。

为了更好地理解这一点,让我们举一个例子。假设你的服务器拓扑由多个服务器组成,并且你已经定义了一个将被应用程序使用的数据源。因此,无论你是否使用集群,你都需要在你的所有独立服务器配置中配置你的数据源(这意味着在每个standalone.xml文件中添加数据源的定义)。在这种情况下,使用域的优势是显而易见的:数据源定义仅包含在域控制器中,它提供了一个中心点,用户可以通过它保持配置的一致性。它还有将配置更改协调一致地部署到服务器的优势。域的另一个重要方面是能够提供比集群更细粒度的配置。例如,你可以定义服务器组,每个组都有自己的自定义配置。为了使用集群配置实现相同的功能,你必须管理每台机器的独立配置,并适应你的需求。

然而,域和集群并不是相互排斥的场景,它们通常是更大图景的一部分。例如,使用域可以在需要管理多个 AS 实例的启动和停止的高级配置中进一步提高集群的效率。同时,集群提供了典型的负载均衡和高可用性功能,这些功能并未集成到域管理中。

另一方面,也存在一些情况,使用域可能并不那么有用。例如,可能你的系统管理员已经购买或开发了他们自己的复杂的多服务器管理工具,这些工具可以做到与域配置所能做到的大同小异。在这种情况下,可能并不希望替换已经配置好的内容。

另一个不需要域的经典例子是开发阶段,在这个阶段,你从域名安装中得不到任何好处。相反,它可能给你的架构增加不必要的额外复杂性。

此外,在某些情况下,独立模式是唯一可用的选择。例如,如果你在嵌入式模式下运行应用程序服务器,那么选择域名是不兼容的。例如,在使用 Arquillian 项目时,你可以使用嵌入式容器测试你的企业项目,该容器由 Arquillian 使用独立配置管理。

总结一下,由于在运行域名模式或独立模式时,单个服务器配置不会发生变化,因此你可以在独立模式下轻松开发你的应用程序,然后在准备部署生产应用程序时切换到域名模式。

创建我们自己的域名配置

我们现在将提供一个详细的域名配置示例。在这个例子中,我们包括两个独立的主机控制器配置,每个配置都有一个包含三个节点的列表。你需要两个独立的 WildFly 8 安装,这些安装可以在两台不同的机器上执行,也可以在同一台机器上执行。当在同一台机器上运行时,为你的机器分配一个虚拟 IP 地址是实用的,这样你就不需要在域名中遇到任何端口冲突。

下图显示了我们的域名项目:

创建我们自己的域名配置

我们需要做的第一件事是将网络接口绑定到一个有效的 inet 地址,无论是公共接口还是管理接口。因此,假设第一个域名安装(master)将绑定到 inet 地址 192.168.1.1,打开 host.xml 文件并相应地更改它,如下所示:

<interfaces>
    <interface name="management">
        <inet-address value="192.168.1.1"/>
    </interface>
    <interface name="public">
        <inet-address value="192.168.1.1"/>
    </interface>
</interfaces>

在第二个域名安装(slave)中,将 host.xml 中的 inet 地址更改为 192.168.1.2,如下所示:

<interfaces>
    <interface name="management">
 <inet-address value="192.168.1.2"/>
    </interface>
    <interface name="public">
 <inet-address value="192.168.1.2"/>
    </interface>
</interfaces>

下一步是为每个安装定义一个独特的主机名。因此,对于第一个 host.xml 文件,使用以下代码:

<host name="master"/>

对于第二个文件,只需使用以下方法:

<host name="slave"/>

接下来,最重要的步骤是选择域控制器所在的位置。正如我们之前在图像中所示,域控制器将位于第一个安装(master)中,因此,在 host.xml 文件中,你应该包含以下默认内容:

<domain-controller>
 <local/>
</domain-controller>

现在,看看另一个安装(slave),指向运行在主机 192.168.1.1(master)上的域控制器,如下所示:

<domain-controller>
 <remote host="192.168.1.1" port="9999"/>
</domain-controller>

从属服务器连接到域控制器需要认证,因此接下来我们将向包含主域的安装中添加一个用户。为此,你需要在你的 WildFly 安装 bin 目录中运行 add-user 脚本,如下所示:

JBOSS_HOME/bin/add-user.sh

执行以下步骤:

  1. 当被问及 你希望添加哪种类型的用户? 时,输入 a(管理用户)。

  2. 当被要求输入用户名时,输入 slave

  3. 当被要求输入密码时,输入 password

  4. 当被问及您希望此用户属于哪些组?时,请留空。

  5. 接下来,您将被询问这是否正确。键入yes

  6. 最后,也是最重要的,您将被询问是否希望此新用户用于一个 AS 进程连接到另一个 AS 进程。您需要再次键入yes。这将导致打印出 XML,我们将将其用于从属配置:

创建我们自己的域配置

最后,在从属服务器上,我们需要在host.xml文件中的server-identities元素内添加secret value(打印到控制台),如下所示:

<security-realm name="ManagementRealm">
    <server-identities>
        <secret value="YXNkZg==" />
    </server-identities>
    ...
</security-realm>

域配置现在已完成。让我们使用domain.bat/domain.sh脚本启动包含域控制器(主控制器)的安装,然后启动第二个安装(从属)。

如果一切配置正确,你将看到从属主机已注册在域控制器(主控制器)上,如下所示:

创建我们自己的域配置

现在,让我们从管理控制台查看域。管理接口将在下一章详细讨论,但我们需要简要地看看它们,以便展示我们的域示例。

提示

默认情况下,您需要创建一个管理用户才能登录到管理控制台。目前,您可以使用为从属服务器创建的用户名和密码,但在生产环境中,您很可能会创建不同的管理用户。

如果您将浏览器指向主服务器的管理界面(http://192.168.1.1:9990),您将无法访问从属服务器的管理界面。

从管理控制台的主页,有几个选项可以查看您的域配置。目前,我们感兴趣的是查看构成域的主机控制器。因此,在上面的菜单栏中,选择菜单。从这里,您可以从左侧的组合框中选择您感兴趣的主机。

如您所见,您可以根据主机分组找到所有服务器,如下所示:

创建我们自己的域配置

现在,从菜单中选择运行时。从这里,您可以查看每个服务器的状态,按服务器组分组,并启动/停止每个节点。例如,根据默认配置,每个分布包含三个节点:启动时激活两个,第三个按需启动。将鼠标悬停在每个节点上,将出现选项,允许您启动/停止单个节点。您还可以启动/停止整个服务器组。此外,请注意,有一个选项可以更改当前正在查看的主机,如下面的屏幕截图所示:

创建我们自己的域配置

现在应该很清楚,每个主机都有自己的节点列表,所有这些节点都是域的一部分。同时,请记住,每个主机都依赖于domain.xml文件中profiles部分定义的配置,该配置包含您域使用的域配置文件。如前所述,域相对于单个安装的一个最明显的优势是能够集中管理服务的配置以及部署的资源。

在 Web 控制台中,您还可以部署应用程序或安装模块,如 JDBC 驱动程序。在下一章中,我们将深入讨论如何将模块部署和安装到域中。域模式与独立模式的主要区别在于,一旦数据源被添加到域控制器(master),其定义就成为默认配置文件的一部分,并且连接到域的每个主机都会继承其配置。

看看下面的截图:

创建我们自己的域配置

在运行时更改域配置

到目前为止,我们在启动域之前修改了配置文件,但在域运行时也可以动态更改配置。由于这些更改是通过管理控制台完成的,因此无需重新启动服务器即可生效。例如,您可能需要动态创建一个新的服务器组,并将其与一些服务器和应用程序关联。可能您的某个生产应用程序存在需要修复的问题。您可以在开发环境中尝试重现该问题,但结果可能并不总是准确,因为开发和生产通常使用不同的数据库和类版本。

因此,您可以快速解决问题的方法之一是创建一个新的服务器组,将其与一个或多个服务器关联,然后在上面部署和测试应用程序。

这可以通过管理员控制台(或 CLI)在几分钟内完成。执行以下步骤:

  1. 打开您的浏览器,导航到管理员控制台。然后,在顶部选择菜单选项。从那里,在左侧列中选择服务器组选项卡。此界面允许您通过点击添加按钮来添加服务器组,如下面的截图所示:在运行时更改域配置

  2. 然后,为您的组选择一个有意义的名称,例如staging-server-group,并选择一个配置文件套接字绑定配置,新的组将基于此配置,如下面的截图所示:在运行时更改域配置

  3. 现在,是时候将一个或多个服务器与新的组关联起来。在左侧点击服务器配置菜单,然后点击添加按钮。看看下面的截图:在运行时更改域配置

  4. 这将弹出一个对话框,要求您输入新的服务器名称和相关的服务器组。在这个例子中,我们将称之为testServer。然后,将其与具有750(在实际中,每个服务都绑定到默认端口号(+ 750))端口号偏移的staging-server-group关联。请查看以下屏幕截图:在运行时更改域配置

一旦您已设置一个新的服务器组并将一个或多个服务器分配给它,您就可以将应用程序部署到服务器组。应用程序的部署可以从运行时页面进行。在左侧单击管理部署,它会显示是否已部署了应用程序。请查看以下屏幕截图:

在运行时更改域配置

从这里,您可以向您的组添加和删除部署,我们将在下一章介绍。

摘要

在本章中,我们介绍了 WildFly 域的设置和配置。通过配置服务器域,您可以从单个集中点管理您的服务器,这在您需要管理大量服务器节点时是非常理想的。

每个域由四个主要元素组成:域控制器、主机控制器、进程控制器和服务器。

域控制器负责处理域配置,而主机控制器协调服务器进程的生命周期和部署的分配。进程控制器处理域服务器进程并管理它们的 I/O 流。

每个域由一个或多个服务器组组成,这允许对域进行精细配置。每个服务器组可以定义自己的 JVM 属性、套接字绑定接口和系统属性,这些属性在启动时加载。您还可以将应用程序部署到域内的每个节点。

服务器组在domain.xml配置文件中定义,包括为域启用的企业服务。

服务器组的组成包含在host.xml文件中。此文件还包含域控制器的位置、默认 JVMs 以及networkmanagement接口。

我们将在下一章详细介绍应用程序部署,即应用程序结构和部署

第六章:应用结构及部署

部署是将资源或应用程序上传到应用程序服务器的过程。在软件开发生命周期中,它是开发阶段之后的逻辑步骤,可以手动执行或以自动化的方式执行。

在本章中,我们将使用服务器发行版提供的工具来探索这两种方法。我们还将介绍如何使用 WildFly 插件为 Eclipse 部署资源。由于快速部署时间,这是 Java 开发者的首选选择。

在本章的最后部分,我们将介绍 WildFly 类加载器架构的细节。简而言之,本章的议程包括以下主题:

  • 可以在 WildFly 上部署的资源类型

  • 在 WildFly 独立实例上部署应用程序

  • 在 WildFly 域上部署应用程序

  • 理解 WildFly 的类加载架构

在应用程序服务器上部署资源

在 Java 企业应用程序中,我们基本上处理三种文件类型,如下所示:

  • JAR:这是最基本的包,可用于应用程序和通用资源

  • WAR:此文件用于打包 Web 应用程序

  • EAR:此文件打包多个 WAR 文件或包含一系列模块

除了这些,WildFly 还能够处理以下存档,为应用程序服务器提供额外的功能:

  • RAR:这是资源适配器文件,用于定义资源适配器组件(资源适配器子系统由IronJacamar项目提供;更多信息,请访问www.jboss.org/ironjacamar)

  • SAR:此文件使服务存档的部署成为可能,其中包含MBean服务,这是由应用程序服务器的早期版本支持的

在本章中,我们将讨论前三种类型的存档,它们构成了 Java 企业应用程序的典型打包解决方案。在讨论应用程序部署之前,让我们更详细地了解一下单一存档。

JAR 文件

Java 存档JAR)文件用于将多个文件打包成一个单一的存档。其内部物理布局类似于 ZIP 文件,实际上,它使用与 zip 实用程序相同的算法来压缩文件。

JAR 文件通常用于分发 Java 类和相关元数据。在 Java EE 应用程序中,JAR 文件通常包含实用代码、共享库和企业 JavaBeans(EJBs)。

WAR 文件

Web 应用程序 存档 (WAR) 文件本质上是一个用于封装 Web 应用程序的存档。Web 应用程序通常包括一组与 Web 相关的资源,例如 Java 服务器 页面 (JSP)、servlets、XHTML/HTML 文件等。它还包括 Java 类文件,以及可能的其他文件类型,具体取决于所使用的技术。自 Java EE 6 以来,可以使用适用于 Web 应用程序类的相同打包指南将 EJBs 打包到 WAR 存档中。这意味着您可以将 EJB 类以及其他类文件放置在 WEB-INF 下的 classes 目录中。或者,您可以将 EJBs 打包到一个 JAR 文件中,然后将此 JAR 文件放置在 WAR 的 WEB-INF\lib 目录中。

因此,开发者更倾向于使用 WAR 文件来分发 Java EE 应用程序。

EAR 文件

企业 存档 (EAR) 文件代表一个应用程序存档,它充当一组模块或 WAR 文件的容器。EAR 文件可以包含以下任何一种:

  • 打包在 WAR 文件中的一个或多个 Web 模块

  • 打包在 JAR 文件中的一个或多个 EJB 模块

  • 一个或多个应用程序客户端模块

  • 应用程序所需的任何附加 JAR 文件

  • JBoss 特定的存档,如 SAR 文件

注意

使用企业存档文件有两个明显的优势。首先,它有助于使用单个存档分发所有应用程序组件,而不是分发每个单独的模块。其次,也是最重要的,是事实上的情况,即 EAR 文件中的应用程序由单个类加载器加载。这意味着每个模块都可以看到同一存档中打包的其他模块。

可以通过向主配置文件(domain.xmlstandalone.xml)中添加 ear-subdeployments-isolated 元素来修改 EAR 文件中包含的应用程序模块的隔离级别。默认值是 false,这意味着 WAR 文件中的类可以访问 ejb.jar 文件中的类。同样,ejb.jar 文件中的类可以相互访问。如果出于某种原因,您不希望这种行为并希望限制类的可见性,请将以下行添加到您的配置文件中:

<subsystem >
 <ear-subdeployments-isolated>true</ear-subdeployments-isolated>
</subsystem>

解释 WildFly 类加载 部分,我们将深入讨论应用程序服务器类加载架构。我们还将向您展示如何在应用程序级别覆盖此配置设置。

在独立 WildFly 服务器上部署应用程序

在 JBoss 上部署应用程序传统上是一个相当简单的任务,因此您可能会想知道为什么有一个完整的章节被专门用于它。这个答案在于,在 WildFly 上部署应用程序可以通过几种方式实现,每种方式我们都会查看。

首先,我们将查看通过 deployments 文件夹自动部署应用程序,但在这样做之前,我们需要解释在部署应用程序时可以使用的两种模式:

  • 自动 部署模式:此模式由部署扫描器在deployments文件夹中的资源被修改时触发

  • 手动部署模式:此模式不依赖于部署扫描器来触发部署,而是依赖于一组标记文件来决定应用程序是否需要部署/重新部署

自动应用部署

自动应用部署包括将您的应用程序放置在deployments文件夹中,该文件夹位于以下路径:

JBOSS_HOME\standalone\deployments

默认情况下,放置在此文件夹中的每个应用程序归档(WAR、JAR、EAR 和 SAR)都会在服务器上自动部署,如下面的截图所示:

自动应用部署

扫描已部署资源的服务称为部署扫描器,它配置在standalone.xml配置文件中。您可以通过搜索deployment-scanner域来找到它。以下代码片段显示了默认部署扫描器配置:

<subsystem >
    <deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="false"/></subsystem>

如您所见,默认情况下,服务器每5000毫秒扫描一次deployments文件夹。此服务可以通过多种方式自定义。接下来,我们将探讨如何进一步配置部署扫描器。

将应用程序部署到自定义文件夹

如果您想更改部署文件夹的位置,您需要修改relative-topath属性。如果您提供了这两个属性,则deployments文件夹是这两个属性的叠加。例如,假设您已定义了wildfly8deployments路径,您可以在以后将其作为部署的相对路径引用,如下所示:

<paths>
  <path name="wildfly8deployments" path="/opt/applications" />
</paths>

<subsystem >
     <deployment-scanner path="deployments" relative-to="wildfly8deployments" scan-interval="5000" runtime-failure-causes-rollback="false"/>
</subsystem>

在此配置中,部署扫描器在/opt/applications下的deployments文件夹中查找应用程序。

可以使用绝对路径来设置您的部署,省略relative-to属性并配置path元素,如下例所示:

<deployment-scanner scan-interval="5000" runtime-failure-causes-rollback="false" path="/opt/applications/deployments" />

修改部署扫描器的行为

默认情况下,放置在deployments文件夹中的每个打包归档都会自动部署。另一方面,展开的应用程序需要额外一步才能部署(请参阅手动应用部署部分)。

我们可以轻松地改变部署扫描器的这种行为。控制auto-deploy功能的属性分别是auto-deploy-zippedauto-deploy-exploded,如下面的代码所示:

<deployment-scanner scan-interval="5000" relative-to="jboss.server.base.dir"path="deployments" auto-deploy-zipped="true" auto-deploy-exploded="false"/>

您可以将auto-deploy-exploded属性设置为true以实现展开归档的自动部署,如下所示:

<deployment-scanner scan-interval="5000" relative-to="jboss.server.base.dir"path="deployments" auto-deploy-zipped="true" auto-deploy-exploded="true"/>

部署回滚

WildFly 8 引入了一个新的选项,可以回滚失败的部署。为此,只需将runtime-failure-causes-rollback属性更新为true,如下面的代码片段所示。默认行为是false

<subsystem >
    <deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="true"/></subsystem>

注意

如果failure-causes-rollback属性设置为true,部署失败也会触发作为同一扫描部分处理的任何其他部署的回滚。

使用 CLI 部署应用程序

复制应用程序存档通常被许多开发者所青睐,因为它可以通过开发环境自动执行。然而,我们强调使用 CLI 界面(命令行界面)的优点,它提供了广泛的附加选项,在部署时使用,并允许你远程部署应用程序。

部署应用程序存档只需登录 CLI,无论是本地还是远程实例,然后发出deploy命令。当不带参数使用时,deploy命令会打印出当前已部署的应用程序列表。看看以下命令:

[disconnected /] connect
[standalone@localhost:9990 /] deploy MyApp.war

要将你的应用程序部署到独立服务器,传递存档的相对(或绝对)路径。显然,如果你连接到远程服务器,此路径与客户端机器相关。这立即将你的应用程序部署到服务器。看看以下截图:

[standalone@localhost:9990 /] deploy ./target/MyApp.war

当你指定一个相对路径时,它是相对于你启动 CLI 实用程序的位置而言的。然而,在指定存档位置时,你可以使用绝对路径。CLI 自动完成功能(使用Tab键)可以轻松完成这项工作。看看以下命令:

[standalone@localhost:9990 /] deploy /opt/workspace/my-app/target/MyApp.war

默认情况下,当你通过 CLI 部署时,应用程序会被部署并启用,以便用户可以访问它。如果你想只执行应用程序的部署并在稍后启用它,你可以添加--disabled开关,如下所示:

[standalone@localhost:9990 /] deploy ./target/MyApp.war --disabled

为了启用应用程序,只需发出另一个不带--disabled开关的deploy命令,如下所示:

[standalone@localhost:9990 /] deploy --name=MyApp.war

注意

你注意到新增的可选--name开关了吗?使用此开关时,你可以使用tab 完成功能,以便自动找到不活动的部署单元。

重新部署应用程序需要向deploy命令添加一个额外的标志。如果你不使用此标志尝试两次部署相同的应用程序,将会得到错误。–f参数强制重新部署应用程序,如下所示:

[standalone@localhost:9990 /] deploy -f ./target/MyApp.war

通过undeploy命令可以卸载应用程序,该命令将部署名称作为参数,如下所示:

[standalone@localhost:9990 /] undeploy MyApp.war

在检查配置文件standalone.xml时,你会注意到你的应用程序的deployment元素已被删除。

使用 Web 管理控制台部署应用程序

应用程序部署也可以通过 Web 管理控制台完成:

  1. 在你的浏览器中启动控制台超链接,http://localhost:9990/console

  2. 您需要添加至少一个管理用户以访问 Web 控制台。要添加新用户,请在您的 WildFly 安装目录的 bin 文件夹中执行 add-user.batadd-user.sh 脚本,并输入所需信息。有关更多详细信息,请参阅第十章 [Chapter 10. Securing WildFly],Securing WildFly

  3. 服务器部署通过在顶部菜单中选择 运行时 并然后选择 管理部署 选项来管理。如果您想向 WildFly 添加新应用程序,只需单击控制台的 添加 按钮即可,如下面的屏幕截图所示:使用 Web 管理控制台部署应用程序

    一个直观的向导将引导您选择应用程序并为它提供一个运行时名称,如下面的屏幕截图所示:

    使用 Web 管理控制台部署应用程序

向导中显示的两个属性可能会引起一些混淆:

  • 名称 属性是部署在服务器运行时中应知的名称,例如,MyApp-1.0.0.war。这用作模块名称的基础,通常是存档的名称。

  • 运行时名称 通常与 名称 相同,但可能存在您希望具有相同运行时名称的两个部署的情况。例如,您可能在内容库中有 MyApp-1.0.0.warMyApp-1.0.1.war,但两个存档的运行时名称都是 MyApp.war。它们不能同时部署,其中一个需要被禁用。

默认情况下,管理员控制台部署应用程序但不启用它。通过单击 启用 按钮,现在可以访问应用程序,如下面的屏幕截图所示:

使用 Web 管理控制台部署应用程序

使用 WildFly Eclipse 插件部署应用程序

Eclipse 是 Java 开发者最广泛使用的应用程序开发环境,也是 JBoss 开发者的首选 IDE,因为 JBoss Tools 项目 (www.jboss.org/tools) 通过提供一组插件来支持 Eclipse 环境。

在本书的第一章中,我们概述了 Eclipse 和 JBoss 工具的安装步骤。我们还设置了 WildFly 服务器适配器,它允许您使用独立模式在 WildFly 上启动、停止、调试和部署应用程序。

一旦安装了 WildFly Eclipse 插件,部署应用程序到 WildFly 就变得简单:

  1. 简单地导航到 服务器 选项卡,右键单击 WildFly 运行时服务器,然后选择 添加和移除。您将看到一个窗口,如下面的屏幕截图所示:使用 WildFly Eclipse 插件部署应用程序

  2. 接下来,点击你的应用程序,选择添加,然后点击完成。项目现在将发布到服务器。如果你需要重新部署,点击你想要部署的项目,并选择完全 发布,如图所示:使用 WildFly Eclipse 插件部署应用程序

配置 Eclipse 部署

通过双击 WildFly 运行时,你可以访问一个带标签的菜单,其中包含两个选项:概览部署部署选项是针对 JBoss 工具的,允许你选择部署位置和部署打包风格。请查看以下截图:

配置 Eclipse 部署

在勾选将项目作为压缩存档部署选项后,你的应用程序将被压缩并打包。

注意

如果你选择将应用程序作为展开的存档部署,一旦应用程序被复制到deployments文件夹,Eclipse 就会添加一个.dodeploy标记文件。这会触发应用程序的即时部署。有关标记文件的更多信息,请参阅下一节。

手动应用程序部署

当使用手动应用程序部署方法时,部署扫描器不会自动部署放置在deployments文件夹中的应用程序。相反,它使用一组标记文件,这些文件用于触发应用程序重新部署并捕获操作的结果。

你可能会想知道应用程序服务器为什么使用标记文件,以及为什么默认服务器配置设置为使用展开部署。

实际上,这个选择有几个原因,它们都与操作系统的文件系统工作方式有关。展开存档涉及在文件系统中移动/替换文件,这应该自动执行。通过原子操作,我们指的是文件系统操作需要作为一个单一操作执行。不幸的是,一些操作系统,如 Windows,不会将复杂的文件系统操作,如文件移动,视为原子操作。

大多数 Windows 用户在 JBoss AS 7 之前的 WildFly 版本发布时经常遇到部署问题。这是因为 JVM 拒绝释放对META-INF/application.xml或 EJB 描述符文件的文件句柄。这是因为 Windows 使用强制文件锁定,这阻止任何应用程序访问该文件。另一方面,像 UNIX 这样的操作系统使用建议性文件锁定,这意味着除非应用程序检查文件锁定,否则不会阻止它访问文件。

此外,使用标记文件,应用程序服务器能够解决与大型部署文件相关的一个常见问题。如果你曾经尝试部署一个大型包单元(尤其是在网络上),你可能已经经历过部署错误,因为部署扫描器在复制操作完成之前就开始部署,导致部署不完整。默认情况下,标记文件用于展开部署。它们由用户或容器添加的空文件组成,带有后缀,用于指示操作的成果。

最相关的标记文件是 .dodeploy,它触发应用程序重新部署。实际上,当我们添加一个展开的部署,并且部署扫描器配置中的 auto-deploy-exploded 属性为 false 时,控制台日志会警告我们应用程序尚未部署,如下所示:

21:51:54,915 INFO  [org.jboss.as.server.deployment.scanner] (DeploymentScanner-threads - 1) JBAS015003: Found MyApp.war in deployment directory. To trigger deployment create a file called MyApp.war.dodeploy

Windows 和 Unix 用户可以通过简单地运行以下命令来触发部署:

echo "" > MyApp.war.dodeploy

一旦你开始部署过程,应用程序服务器会回复两种可能的结果。部署扫描器服务将部署标记文件(例如,MyApp.war.deployed)放置在 deployments 目录中,以指示给定内容已部署到服务器,并且你的日志应该确认结果,如下所示:

22:23:18,887 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: Deployed "MyApp.war" (runtime-name : "MyApp.war")

注意

如果你删除了 .deployed 文件,应用程序将被卸载,并在 deployments 文件夹中添加一个 .undeployed 标记文件(例如,MyApp.war.undeployed)。如果你尝试删除 .undeployed 文件,应用程序将再次部署。这是一个有用的快捷方式,可以快速卸载(或重新部署)应用程序,而无需在文件系统中删除它。

另一种可能的结果是部署失败,这由一个 .failed 标记来指示。文件内容包含一些关于失败原因的信息;然而,你应该检查服务器日志以获取有关错误原因的更详细信息。

当使用自动部署模式时,你可以删除 .failed 标记文件,当部署扫描器重新扫描文件夹时,它将重新部署应用程序。此外,用户可以放置 .skipdeploy 标记文件(例如,MyApp.war.skipdeploy),这将禁用内容在标记文件存在期间进行 auto-deploy。如果你依赖自动部署并希望确保在更新尚未完成时不会触发任何部署,请使用此功能。

让我们看看一个示例脚本,该脚本可以在使用 Linux 操作系统时,安全地重新部署名为 MyApp.war 的网络应用程序:

touch $JBOSS_HOME/standalone/deployments/MyApp.war.skipdeploy
cp -r MyApp.war/  $JBOSS_HOME/standalone/deployments
rm $JBOSS_HOME/standalone/deployments/MyApp.war.skipdeploy

Windows 的等效定义如下:

echo "" > "%JBOSS_HOME%\standalone\deployments\MyApp.war.skipdeploy"
xcopy MyApp.war %JBOSS_HOME%\standalone\deployments\MyApp.war /E /I
del %JBOSS_HOME%\standalone\deployments\MyApp.war.skipdeploy

最后,应用程序服务器提供了一些额外的临时标记文件,如.isdeploying.isundeploying.pending,这些文件由部署扫描器放置,以指示资源部署或卸载的转换。有关标记文件的完整详细信息,请参阅放置在服务器分发deployments文件夹中的README.txt文件。以下表格显示了应用程序服务器使用的可用标记文件的简要总结:

标记 创建者 描述
.dodeploy 用户 创建此文件将触发应用程序部署。触摸此文件将导致应用程序重新部署。
.skipdeploy 用户 只要此文件存在,应用程序的自动部署就被禁用。
.deployed WildFly 应用程序已部署。移除它将导致应用程序卸载。
.undeployed WildFly 应用程序已被卸载。移除它将导致应用程序重新部署。
.failed WildFly 应用程序部署失败。
.isdeploying WildFly 应用程序部署正在进行中。
.isundeploying WildFly 应用程序卸载正在进行中。
.pending WildFly 有一个条件阻止应用程序部署(例如,文件正在复制中)。

在 WildFly 域上部署应用程序

在 WildFly 域上部署应用程序并不像在独立服务器上部署那样简单。域安装中没有预定义的deployments文件夹。这是因为,在domain模式下,可以有属于不同服务器组的多个服务器,每个服务器运行不同的配置文件。在这种情况下,一个单独的deployments文件夹会引发一个明显的问题:哪个服务器组将使用该文件夹?

接下来,我们将探讨在 WildFly 域上部署应用程序时可用的一些选项。这两个选项如下:

  • 命令行界面(CLI)

  • 管理员 Web 界面

使用 CLI 部署到域

让我们看看如何使用命令行界面(CLI)部署应用程序。首先启动 CLI,然后连接到域控制器,如下所示:

[disconnected /] connect
domain@localhost:9990 /]

当您使用域模式部署应用程序时,您必须指定部署关联的服务器组。CLI 允许您在以下两个选项之间进行选择:

  • 部署到所有服务器组

  • 部署到单个服务器组

部署到所有服务器组

当选择将应用程序部署到所有服务器组的选项时,应用程序将被部署到所有可用的服务器组。可以使用--all-server-groups标志将应用程序部署到所有可用的服务器组。例如,使用以下命令:

[domain@localhost:9990 /] deploy ../application.ear --all-server-groups

如果您想从属于域的所有服务器组中卸载应用程序,您必须发出undeploy命令,如下所示:

[domain@localhost:9990 /] undeploy application.ear --all-relevant-server-groups

您可能已经注意到,undeploy命令使用--all-relevant-server-groups而不是--all-server-groups。这种差异的原因是部署可能并未在所有服务器组上启用,因此使用此选项实际上只是从所有那些已启用部署的服务器组卸载它。

注意

将应用程序作为disabled部署可能很有用,如果您有一些启动豆(当应用程序启用时被激活)并且您想加载它们,但又不想触发它们的执行,例如,如果数据库或其他企业信息系统暂时不可用。

部署到单个服务器组

单独部署到单个服务器组的选项允许您仅对您指定的服务器组执行选择性部署,如下所示:

[domain@localhost:9990 /] deploy application.ear --server-groups=main-server-group

您不受单个服务器组的限制。要部署到多个服务器组,请用逗号分隔它们,如下所示:

[domain@localhost:9990 /] deploy application.ear --server-groups=main-server-group,other-server-group

记住,您可以使用自动完成功能(Tab键)显示可用的--server-groups列表。

现在,假设我们只想从单个服务器组中卸载应用程序。可能会有两种可能的结果。如果应用程序仅在该服务器组上可用,您将成功完成卸载:

[domain@localhost:9990 /] undeploy MyApp.war --server-groups=main-server-group

另一方面,如果您的应用程序在其他服务器组上可用,CLI 将返回以下错误:

部署到单个服务器组

此错误发生是因为当您从服务器组中删除应用程序时,域控制器会验证应用程序是否未被任何其他服务器组引用。如果是,则undeploy命令将失败。

如果您希望从单个服务器组中删除应用程序,您需要指定-keep-content参数。这将导致域控制器从服务器组卸载应用程序,同时保留内容:

[domain@localhost:9990 /] undeploy application.ear --server-groups=main-server-group --keep-content

我们已经介绍了许多将应用程序部署到域的可用选项。在转到管理控制台之前,让我们回顾以下表格中显示的 CLI 部署选项:

命令 选项 影响
deploy --all-server-groups 将应用程序部署到所有服务器组。
undeploy --server-groups 将应用程序部署到一个或多个服务器组。
undeploy --all-relevant-server-groups 从所有服务器组卸载并删除应用程序。
undeploy --server-groups 从一个服务器组卸载应用程序。如果它在另一个服务器组中被引用,则此操作将失败。
undeploy --server-groups -keep-content 从一个服务器组卸载应用程序而不删除它。

使用管理控制台部署到域

使用管理控制台部署应用程序非常直观,只需要几个简单的步骤:

  1. 首先通过默认地址http://localhost:9990登录到 Web 应用程序。

  2. 然后,在顶部菜单中选择Runtime标签页,并在屏幕左侧面板中选择Manage Deployments,如图所示:使用管理员控制台部署到域名

  3. 在您可以将应用程序部署到服务器组之前,您需要将其上传到服务器,在那里它被存储在内容仓库中。为此,点击CONTENT REPOSITORY,然后点击Add

    这将显示以下对话框,允许您上传您的应用程序:

    使用管理员控制台部署到域名

  4. 完成上传向导后,应用程序将被上传到域名仓库。为了将其部署/取消部署到单个服务器组,您需要选择SERVER GROUPS标签页,然后点击您希望部署到的服务器组上的View按钮,如图所示:使用管理员控制台部署到域名

  5. 下一屏显示了此服务器组的所有部署。现在,点击Assign按钮。这允许您从内容仓库中当前存储的应用程序中选择。勾选您的应用程序的复选框,然后点击Save,如图所示:使用管理员控制台部署到域名

  6. 在此阶段,应用程序已部署但尚未启用。选择En/Disable按钮以完成应用程序的部署,如图所示:使用管理员控制台部署到域名

SERVER GROUPS标签页中点击Remove按钮会从所选的服务器组中移除部署,而CONTENT REPOSITORY标签页中的另一个Remove按钮实际上会从临时域名仓库中删除部署,该仓库用于打包上传的应用程序。

解释 WildFly 类加载

管理依赖关系有两种方法,第一种是Class-Path方法,第二种是Dependencies方法。在本节中,我们将介绍这两个主题,但在我们这样做之前,让我们看看 WildFly 中类加载的历史,以便了解为什么类加载以这种方式工作。根据 Java EE 规范的要求,应用程序服务器需要提供一个环境,其中任何已部署的应用程序都可以访问特定版本的任何类或类库。

这也被称为类命名空间隔离(Java EE 5 规范,第 EE.8.4 节)。然而,从不同的命名空间加载类可能会引发一些不易解决的问题。例如,如果你将一个实用库的新版本打包到你的应用程序中,而应用程序服务器已经加载了该库的旧版本,会发生什么?或者,你如何在同一个应用程序服务器的同一个实例中同时使用同一实用库的两个不同版本?

JBoss AS 的类加载策略在多年中已经合理地发生了变化。应用程序服务器的 4.x 版本使用了UnifiedClassLoader,旨在减少运行应用程序之间的通信开销,因为类数据可以通过引用或简单的复制共享。

使用UnifiedClassLoader未能解决的问题之一是类加载依赖。其理念是,如果一个应用程序(A)使用了另一个应用程序(B)的类,当 B 被重新部署时,系统应该知道如何重新部署 A;否则,它将引用过时的类。实际上,曾有两个不同的尝试试图在不让用户进行任何配置的情况下使这一机制工作。但这两个尝试都没有真正成功,并且都被放弃了。

在 JBoss AS 5.0 中,一个新的类加载器基于新的虚拟文件系统VFS)。VFS 被实现以简化并统一应用程序服务器内的文件处理。新的类加载器,命名为 VFS 类加载器,使用 VFS 来定位 JAR 和类文件。尽管这代表了 JBoss AS 5.0 中类加载方式的重大变化,但最终的行为与之前版本的 JBoss AS 非常相似。

一个常见的错误来源是将 API 类包含在由容器也提供的部署中。这可能导致创建多个版本的类,并且部署无法正确部署。

自从 JBoss AS 7 以来,类加载标志着与之前尝试的彻底转变。类加载现在基于 JBoss 模块项目,任何部署的应用程序实际上都是一个模块。这一事实可能会引发一些问题,例如,应该分配什么模块名称给部署的应用程序,以及应用程序服务器如何处理模块之间的依赖关系。

这些问题将在接下来的几节中得到解答。

了解模块名称

了解模块名称并非是一项学术练习。我们甚至可以进一步建立模块之间的依赖关系。因此,在许多情况下,你需要知道模块名称是如何分配给应用程序的。

被打包为顶级归档(如 WAR、JAR 和 SAR)的应用程序被分配以下模块名称:

deployment.[归档名称]

例如,一个名为WebExample1.war的 Web 应用程序使用以下模块名称进行部署:

deployment.WebExample1.war

另一方面,对于包含嵌套模块的应用程序(如 EAR),每个存档都使用以下约定分配一个模块名称:

deployment.[ear archive name].[sub deployment archive name]

因此,例如,前面的 Web 归档,如果包含在名为 EnterpriseApp.ear 的 EAR 文件中,将使用以下名称进行部署:

deployment.EnterpriseApp.ear.WebExample1.war

查找隔离级别

在 WildFly 8 中,一个通用规则是每个部署的应用程序模块都与其他模块隔离,也就是说,默认情况下,应用程序无法看到 AS 模块,AS 模块也无法看到应用程序。

使用应用服务器模块相对简单,可以总结为一句:向所需模块添加依赖项,AS 将会使用它。一些依赖项会自动添加到应用服务器中,而其他依赖项则需要用户进行标记:

  • 核心模块库(即,Enterprise 类)被视为隐式依赖项,因此当部署者检测到它们的用法时,它们会自动添加到您的应用程序中

  • 其他模块库需要用户在应用程序的 MANIFEST 文件中或在一个名为 jboss-deployment-structure.xml 的自定义 JBoss 部署文件中显式声明(关于此文件的更多信息,请参阅 高级部署策略 部分)

隐式依赖项

重复声明企业应用程序中常用的依赖项变得非常繁琐。这就是为什么应用程序服务器会自动为您添加核心模块。其中一些核心模块仅在应用程序服务器检测到特定技术的注解或配置文件时才添加。例如,添加 beans.xml 文件会自动触发 Weld 依赖项(Weld 是 WildFly 中使用的上下文和依赖注入实现)。

下表概述了自动添加到您的应用程序中的模块:

子系统 自动依赖项 触发依赖项 触发条件
核心服务器 javax.api sun.jdk org.jboss.vfs
EE javaee.api
EJB3 javaee.api 存在 ejb-jar.xml 或 EJB 注解
JAX-RS javax.xml.bind.api org.jboss.resteasy 存在 JAX-RS 注解
JPA javax.persistence javaee.api org.jboss.as.jpa org.hibernate 存在 @PersistenceUnit@PersistenceContext 或等效 XML
日志记录 org.jboss.logging org.apache.commons.logging org.apache.log4j org.slf4j
安全性 org.picketbox
Web javaee.api com.sun.jsf-impl org.hibernate.validator org.jboss.as.web org.jboss.logging 部署 WEB 归档;如果使用,则添加 JSF
Web 服务 org.jboss.ws.api org.jboss.ws.spi
焊接 javax.persistence.api javaee.api org.javassist org.jboss.interceptor org.jboss.as.weld org.jboss.logging org.jboss.weld.core org.jboss.weld.api org.jboss.weld.spi beans.xml 文件的存在

如果你的应用程序使用了所指示的任何核心模块,那么你不需要指定其依赖项,因为应用程序服务器会自动链接该模块。如果你使用 Maven,则可以将这些依赖项标记为提供。

显式依赖项

需要由用户声明的模块不是隐式依赖项。假设你想使用打包在应用程序服务器发行版中的 log4j 库。实现这一点的最简单和推荐的方法是在 META-INF/MANIFEST.MF 中包含 Dependencies: [module] 声明。本章的示例代码使用 Maven 来填充 MANIFEST.MF 文件:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1.1</version>
    <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
        <archive>
            <manifestEntries>
                <Dependencies>org.apache.log4j</Dependencies>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

这将导致在您的 MANIFEST.MF 文件中添加以下内容:

显式依赖关系

注意

请注意,模块名称不一定与库的包名称匹配。实际的模块名称由 module.xml 文件中 module 元素的 name 属性指定。

你不受单个依赖项的限制,因为你可以通过逗号分隔添加多个依赖项。例如,为了添加对 log4j 和 Apache Velocity API 的依赖项,请使用以下命令:

Dependencies: org.apache.log4j,org.apache.velocity

你甚至可以通过添加 export 关键字将一个应用程序模块使用的依赖项导出到其他应用程序。例如,除了前面的示例之外,我们现在将依赖项导出到其他模块,如下所示:

显式依赖关系

注意

可以使用 export 参数将依赖项导出到 EAR 中包含的所有子部署。因此,如果你从 EAR 的顶层(或 ear/lib 目录中的 JAR)导出依赖项,则该依赖项也对所有子部署单元可用。

标记为依赖于 deployment.WebApp1.war 模块的应用程序也可以访问其依赖项:

显式依赖关系

META-INF/MANIFEST.MF 中,你也可以指定可以修改服务器部署者行为的附加命令。例如,可以添加 optional 属性来指定如果模块在部署时未找到,则部署不会失败。

最后,当指定 services 关键字时,部署者会尝试加载存放在存档的 META-INF/services 目录中的服务。

注意

在 Java SE 6 中,service API 已变为 public服务可以定义为一组编程接口和类,它们提供对某些特定应用程序功能或特性的访问。服务提供者接口SPI)是服务定义的 public 接口和 abstract 类的集合。

您可以通过实现服务提供者 API 来定义服务提供者。通常,您创建一个 JAR 文件来存放您的提供者。要注册您的提供者,您必须在 JAR 文件的META-INF/services目录中创建一个提供者配置文件。当您将services属性添加到您的META-INF/MANIFEST.MF文件中时,您实际上能够加载META-INF/services目录中包含的服务。

一个关于 SPI API 的优秀介绍可以在这里找到。

设置全局模块

设置全局模块类似于旧 AS 加载常用库的方法,您过去通常将它们放在JBOSS_HOME/common下的lib文件夹中。

如果您在standalone.xml/domain.xml中定义了一个名为global-modules的部分,那么您使该模块对其他 AS 模块可访问。例如,您可以使用以下部分而不是声明对 log4j 的依赖:log4j 依赖激活

<subsystem >
  <global-modules>
    <module name="org.apache.log4j" />
  </global-modules>
</subsystem>

虽然这种方法通常不推荐,因为它使我们回到了单体应用程序服务器的概念,但它仍然可以带来一些好处,例如在迁移一些较老的应用程序时,以及当您不想或无法指定存档的依赖项时。

高级部署策略

到目前为止涵盖的主题对于绝大多数应用程序来说已经足够了。如果您使用的是复杂的存档配置,例如包含多个模块和依赖项的 EAR 存档,那么在单个文件中定义您的类加载策略可能很有用。配置文件jboss-deployment-structure.xml正是这样做的。使用此文件的一些优点如下:

  • 您可以在单个文件中定义所有应用程序模块的依赖项

  • 您可以通过包含/排除所有或部分模块的所有部分以细粒度方式加载模块类

  • 您可以为打包在企业存档中的应用程序定义类加载隔离策略

让我们通过查看一些实际示例来看看jboss-deployment-structure.xml能为您做什么。

设置单个模块依赖

我们已经学习了如何使用存档的MANIFEST文件中的Dependencies属性激活log4j依赖。使用jboss-deployment-structure.xml文件也可以达到相同的效果。让我们回顾一下存档结构,它基本上由一个名为WebApp.war的 Web 应用程序组成。

如以下图所示,jboss-deployment-structure.xml文件需要放置在 EAR 的META-INF文件夹中:

设置单个模块依赖

以下为jboss-deployment-structure.xml的内容:

<jboss-deployment-structure>
  <sub-deployment name="WebApp.war">
    <dependencies>
      <module name="org.apache.log4j" />
    </dependencies>
  </sub-deployment>
</jboss-deployment-structure>

jboss-deployment-structure文件不仅限于用于 EAR,还可以通过将其放置在WEB-INF文件夹中来在 WAR 存档中使用。然而,它仅适用于顶级存档。因此,如果将jboss-deployment-structure.xml文件放置在 WAR 的WEB-INF文件夹中,并且 WAR 被打包在 EAR 文件中,那么jboss-deployment-structure.xml文件将被忽略。该文件的相关部分是子部署元素,它引用了 Web 应用程序,包括dependencies元素。预期的结果是应用程序服务器触发对 Log4J API 的依赖,因此它对我们 Web 应用程序是可见的。

排除服务器的自动依赖项

在本章的早期部分,我们讨论了当满足某些条件时,应用程序服务器可以自动触发依赖项。例如,如果你部署了一个 JSF 应用程序(包含faces-config.xml文件),那么 JSF 2.2 API 实现将自动添加。

这可能不是始终是期望的选项,因为你可能希望为该模块提供另一个发布实现。你可以通过在jboss-deployment-structure.xml文件中使用exclusion元素轻松实现这一点,如下面的代码片段所示:

<jboss-deployment-structure>
  <deployment>
    <exclusions>
      <module name="javax.faces.api" />
      <module name="com.sun.jsf-impl" />
    </exclusions>
    <dependencies>
      <module name="javax.faces.api" slot="2.1"/>
      <module name="com.sun.jsf-impl" slot="2.1"/>
    </dependencies>
  </deployment>
</jboss-deployment-structure>

注意在dependencies部分,我们添加了我们的替代 JSF 2.1 实现,这是由你的应用程序使用的。你需要安装前面代码中显示的这两个模块,如第二章中所述,配置 WildFly 核心子系统。它们可以通过创建一个名为2.1的文件夹与 WildFly 提供的实现并排放置。以下命令行中突出显示了 JSF 2.1 存档的新文件夹,加粗显示:

$JBOSS_HOME/modules/system/layers/base/javax/faces/api/main
$JBOSS_HOME/modules/system/layers/base/javax/faces/api/main/jboss-jsf-api_2.2_spec-2.2.6.jar
$JBOSS_HOME/modules/system/layers/base/javax/faces/api/main/module.xml
$JBOSS_HOME/modules/system/layers/base/javax/faces/api/2.1
$JBOSS_HOME/modules/system/layers/base/javax/faces/api/2.1/jsf-api-2.1.jar
$JBOSS_HOME/modules/system/layers/base/javax/faces/api/2.1/module.xml

你还需要将slot属性添加到module.xml文件中,如下面的代码片段所示:

<module  name="javax.faces.api" slot="2.1" >
    ...
</module>

隔离子部署

考虑到你有一个由 Web 应用程序、EJB 模块和包含实用类 JAR 文件组成的 EAR 应用程序,所有子部署都放置在存档的根目录,以便它们可以相互看到。但是,假设你的 Web 应用程序包含一些相同的 EJB 实现。这是绝对可能的,因为 Java EE 允许你的 Web 应用程序在WEB-INF/classesWEB-INF/lib文件夹中包含 EJB 类,如下面的图所示:

隔离子部署

类加载器是如何解决这个冲突的呢?应用程序服务器类加载器在加载类时有一个优先级列表,因此可以减少加载类之间的任何冲突,如下所示:

  • 容器自动将最高优先级赋予模块,包括 Java EE API。包含在modules文件夹中的库属于这一类别。

  • 下一个优先级是给那些在打包归档的 MANIFEST.MF 文件中指定为依赖项(或在 jboss-deployment-structure.xml 文件中)的库。

  • 上一优先级是给那些打包在应用程序本身的库,例如 WEB-INF/libWEB-INF/classes 中包含的类。

  • 最后,最低优先级是给那些打包在同一 EAR 归档内的库(位于 EAR 的 lib 文件夹中)。

因此,在这个例子中,位于 WEB-INF 文件夹中的 EJB 库隐藏了 EJB.jar 顶级部署的实现。如果这不是你想要的结果,你可以简单地覆盖它,如下所示:

<jboss-deployment-structure>
  <ear-subdeployments-isolated>false</ear-subdeployments-isolated>
  <sub-deployment name="WebApp.war">
    <dependencies>
      <module name="deployment.App.ear.EJB.jar" />
    </dependencies>
  </sub-deployment>
</jboss-deployment-structure>

在前面的代码片段中,我们添加了对 EJB.jar 部署的依赖,该部署位于 EAR 的根目录,并覆盖了在 Web 应用程序中打包的实现。

注意

注意文件顶部的 ear-subdeployments-isolated 元素。通过设置 EAR 隔离级别,你将能够指示子部署模块是否彼此可见。

ear-subdeployments-isolated 元素的默认值是 false,这意味着子部署模块可以彼此看到。如果你将隔离设置为 true,那么每个模块将由不同的类加载器加载,这意味着 Web 应用程序将无法找到 EJB.jarUtility.jar 库中包含的类)。

如果你想要保持部署隔离但允许某些依赖项之间的可见性,那么你有两个选择可用:

  • 将库移动到 EAR/lib 文件夹,以便它作为一个单独的模块被选中

  • 在调用应用的 MANIFEST.MF 文件中使用 Dependencies 或 Class-Path 指定依赖

在下面的图中,你可以看到如何正确设置你的 EAR,通过将公共库放在 lib 文件夹中,并添加对 EJB 类的依赖:

隔离子部署

下面是 jboss-deployment-structure.xml 中所需的相应配置:

<jboss-deployment-structure>
  <ear-subdeployments-isolated>true</ear-subdeployments-isolated>
  <sub-deployment name="WebApp.war">
    <dependencies>
      <module name="deployment.App.ear.EJB.jar" />
    </dependencies>
  </sub-deployment>
</jboss-deployment-structure>

注意

在你的 EAR 中将库打包成共享库是一个选项。从 Java EE 5 开始,可以将这些文件放置在名为 lib 的共享库文件夹中。你可以使用 META-INF/application.xml 文件中的 library-directory 元素来覆盖默认文件夹名称。例如,假设你想使用 common 文件夹来存放你的共享库,在这种情况下,你可以在你的 application.xml 中添加以下行:

<library-directory>common</library-directory>

作为旁注,你应该避免在共享文件夹中放置组件声明注解(如 EJB3),因为它可能会对部署过程产生意外的后果。因此,强烈建议你将你的实用类放在共享库文件夹中。

使用 Class-Path 声明解决依赖

到目前为止,我们使用 JBoss 方式配置了模块之间的依赖关系,这是推荐的选择。尽管如此,我们也应该考虑到 Java 的可移植方式来引用包含在 EAR 文件中的一个或多个库。这可以通过向MANIFEST.MF文件添加Class-Path属性来实现。这允许一个模块引用另一个对应用程序不可见的库(回想一下之前示例中的部署单元,其隔离设置为true)。

例如,考虑到你需要从你的 Web 应用程序中引用Utility.jar应用程序,你只需直接在你的 EAR 文件中的META-INF/MANIFEST.MF文件中添加以下内容即可:

Manifest-Version: 1.0
Class-Path: Utility.jar

你实际上可以将多个库包含到Class-Path属性中,通过逗号分隔它们。

注意

Dependencies属性不同,Class-Path属性指向实际的 JAR 文件名(而不是模块名称),以引用依赖库。

在选择类路径方法与 JBoss 依赖方法之间取决于你的应用程序结构:使用 JBoss 依赖方法为你提供更丰富的选项,特别是能够将依赖项导出到其他部署中,正如我们之前所展示的。支持 JBoss 依赖方法的一个额外点是能够引用实际上未打包在应用程序内的模块。

另一方面,类路径方法的主要优势在于应用程序的可移植性。因此,如果你将完全可移植的解决方案作为优先考虑,你可以考虑切换到Class-Path清单属性。

摘要

在本章中,我们涵盖了与应用程序部署相关的各种功能。应用程序的部署方式不同,这取决于它们是部署到独立服务器还是服务器域。

就独立服务器而言,应用程序可以自动或手动部署。默认情况下,打包的存档会自动部署。这意味着你所需做的只是将存档放置在应用程序服务器的standalone/deployments文件夹中。手动部署的应用程序(默认情况下为展开的存档)需要标记文件来激活部署。

就域服务器而言,由于应用程序服务器无法确定你希望将部署指向哪个服务器组,因此在使用命令行界面或 Web 管理界面时,你需要指定此信息。

使用服务器域的一个巨大优势是能够在单个或多个服务器组上部署应用程序,这些服务器组甚至可以在运行时创建和配置。

在本章的后续部分,我们介绍了应用服务器使用的类加载机制。部署到 WildFly 的每个应用都被视为一个模块,所有这些模块都与应用服务器分发中的其他模块隔离。代表 Java EE API 类的模块会隐式地添加到你的应用程序的类路径中作为依赖项,这意味着你不需要任何特殊的配置来部署 Java EE 应用程序。

如果你想引用应用服务器中包含的其他模块,你只需在应用的META-INF/MANIFEST.MF文件中添加一个Dependencies属性。企业存档也可以通过在META-INF/MANIFEST.MF文件中设置Class-Path属性来指定对其他模块的依赖。

如果你希望将所有依赖项维护在一个单独的文件中,你可以使用jboss-deployment-structure.xml文件。这允许你在存档内定义所有依赖项,包括覆盖默认的 EAR 隔离级别和过滤进/出类,这些类是应用服务器部署的一部分。

在下一章中,我们将通过详细探讨命令行界面和 Web 管理控制台来介绍应用服务器的管理。

第七章。使用管理接口

在本章中,我们将描述 WildFly 提供的管理工具,这些工具可以用来控制您的应用程序服务器实例。

WildFly 提供了几个管理通道。其中之一是 CLI,它包含许多独特的功能,使得日常系统管理和监控应用程序服务器资源变得方便。

管理工具还包括一个提供应用程序服务器子系统优雅视图的 Web 管理控制台,允许您以简单的方式执行管理任务。

在本章中,我们将描述以下管理工具:

  • 命令行界面 (CLI)

  • Web 管理控制台

我们还将涵盖以下主题:

  • 创建和修改数据源

  • 从 CLI 获取帮助

  • 批处理脚本

  • 配置服务器配置文件

  • 添加 JMS 目的地

  • 配置 JMS 目的地

  • 配置套接字绑定组

  • 在 CLI 和 Web 控制台之间进行选择

命令行界面 (CLI)

终端和控台是系统管理员和机器之间最早的通信接口类型之一。由于这种长期存在,大多数系统管理员更喜欢使用命令行的原始力量来执行管理任务。使用低级接口,如 shell,的一个最明显的优点是任务通常可以作为批处理或宏的一部分执行,用于重复性操作。

提示

正如我们在本书开头所指出的,CLI 位于 JBOSS_HOME/bin 文件夹中,并由 jboss-cli.sh(对于 Windows 用户,是 jboss-cli.bat)包装。

通过启动 shell 脚本,您将以断开连接的会话开始。您可以在任何时候使用 connect [standalone/domain controller] 命令连接,默认情况下,该命令连接到位于 localhost 的服务器控制器,端口号为 9990

You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /]

您可以通过修改以下代码片段中突出显示的行来调整原生接口运行的默认端口,该代码片段位于 standalone.xmldomain.xml 配置文件中:

<management-interfaces>

   <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
       <socket-binding http="management-http"/>
   </http-interface>
 </management-interfaces>

 <socket-binding-group name="standard-sockets" default-interface="public" port-offset="0">
...

 <socket-binding name="management-http" interface="management" port="9990"/>
 <socket-binding name="management-https" interface="management" port="9993"/>
...
</socket-binding-group>

如您从前面的代码片段中看到的,套接字管理别名定义在 management-interfaces 部分,而相应的端口包含在 socket-binding 部分。

一个方便的开关是 --connect,可以在启动 CLI 时自动连接到您的独立/域控制器,如下所示:

$JBOSS_HOME/bin/jboss-cli.sh --connect

在 Windows 机器上,使用以下命令:

$JBOSS_HOME/bin/jboss-cli.bat --connect

退出 CLI 的相应命令可以是 quitexit,这将关闭与主控制器的连接:

[standalone@localhost:9990 /] quit

重新加载服务器配置

虽然通过命令行对配置所做的更改大多数会立即生效,但有些更改不会立即生效,需要重新加载服务器配置,例如,更改套接字绑定组。要重新加载服务器配置,您需要发出 :reload 命令,如下所示:

[standalone@localhost:9990 /] :reload
{
 "outcome" => "success",
 "result" => undefined
}

使用 CLI

CLI 最有趣的功能之一是其自动补全功能,这有助于你找到资源和命令的正确拼写。这可以通过简单地按Tab键实现。你甚至可以使用它来查找特定命令所需的参数,而无需查阅参考手册。

这引导我们进入旅程的第一部分,我们将学习可用的命令。所以,一旦你成功连接,按下Tab键,它将列出你可用选项。以下截图显示了输出:

使用 CLI

如你所见,有超过 30 个选项可用。然而,我们可以将所有与 CLI 发生的交互分为两大类:

  • 操作:这些包括它们所执行的资源路径(地址)。

  • 命令:这些命令独立于当前资源的路径执行动作。这些命令不包括资源路径。

在资源间导航并执行操作

操作严格绑定到应用程序服务器资源路径。资源树中的路径由/字符表示,它实际上代表树的根,就像在 Unix 文件系统中一样。

当在服务器资源上执行操作时,你必须使用一个定义良好的语法:

[node-type=node-name (,node-type=node-name)*] : operation-name [( [parameter-name=parameter-value (,parameter-name=parameter-value)*] )]

初看可能有些不自然;然而,我们将通过以下示例来尝试揭开它的神秘面纱:

[standalone@localhost:9990 /] /subsystem=deployment-scanner/scanner=default:write-attribute(name=scan-interval,value=2500)
{"outcome" => "success"}

在这里,我们告诉 CLI 导航到默认扫描资源下的deployment-scanner子系统,并使用write-attribute操作将scan-interval属性设置为2500毫秒。

此示例还展示了资源、属性和操作之间的区别。

资源是位于路径下的配置元素。所有被分类为资源的元素都可以通过 WildFly 的接口进行管理。例如,deployment-scanner是位于subsystem路径下的资源。它有一个名为default的子元素(当没有指定名称属性时,名称默认为default)。在单个资源或子资源上,你可以调用一些操作,例如读取或写入属性的值(scan-interval)。

最后,请注意,操作由:前缀引入,而资源由/字符引入。以下截图有助于你巩固基本概念:

在资源间导航并执行操作

为了在资源路径中移动,你可以要么指定完整的树路径(如早期示例所示),要么使用cd命令或等效的cn(更改节点)命令导航到路径,然后发出所需的命令。例如,前面的代码片段也可以重写为:

[standalone@localhost:9990 /] cd /subsystem=deployment-scanner/scanner=default

[standalone@localhost:9990 scanner=default] :write-attribute(name=scan-interval,value=2500)
{"outcome" => "success"}

小贴士

CLI 修改的属性在服务器重启后是否仍然存在?

当使用 CLI 时,每次更改都会持久化到服务器配置文件中。这意味着你必须小心通过 CLI 更改服务器的配置。为了安全起见,在做出重大更改之前,拍摄服务器配置的快照是明智的。请参阅拍摄配置快照部分。

就像操作系统 shell 一样,发出 cd .. 将将资源指针移动到父资源:

[standalone@localhost:9990 scanner=default] cd ..
[standalone@localhost:9990 subsystem=deployment-scanner]

你可以随时通过发出一个空的 cd 命令或仅仅 pwd 来检查你所在的位置的资源路径,就像你在 Unix shell 中做的那样,如下所示:

[standalone@localhost:9990 scanner=default] pwd
/subsystem=deployment-scanner/scanner=default

最后,为了简化你的导航,我们将通过提供应用服务器树或资源的鸟瞰图来结束本节,如下所示:

在资源间导航并执行操作

如你所见,资源树包括八个子资源,每个子资源处理应用服务器的一个核心方面。在附录中,CLI References,你可以找到一个有用的命令列表,这些命令可以用于你的日常系统管理。大多数时候,你会导航到包含所有应用服务器核心模块的子系统资源。你可能还想了解更多关于核心服务的信息,它处理管理接口(例如 CLI 本身),部署资源,它可以用来操作已部署的工件,以及socket-binding-group,这是你需要更改应用服务器使用的端口的资源。

可在资源上发出的操作

在学习了通过资源导航的基本知识后,让我们看看可以在资源上发出的命令。操作由 : 字符触发。你可以通过使用自动完成功能(Tab 键)来获取它们的列表。以下是一个命令列表:

命令 含义
read-resource 此命令读取模型资源的属性值,以及关于任何子资源的基本或完整信息。
read-resource-description 此命令为选定的资源输出一个描述。
read-operation-names 此命令读取节点上可用的操作名称。
read-operation-description 此命令为可用的操作输出一个描述。
read-children-names 此命令获取选定资源下所有子资源的名称。
read-children-resources 此命令读取关于给定类型的所有子资源的信息。
read-children-types 此命令提供选定资源下子资源的列表。
read-attribute 此命令获取选定资源的属性值。
write-attribute 此命令为选定的资源写入一个属性。

read-resource命令值得进一步解释。在没有额外参数的情况下,它提供关于资源属性和直接子节点的信息。

例如,以下是对数据源子系统的资源扫描,它包括默认数据源名为ExampleDS

[standalone@localhost:9990 /] /subsystem=datasources:read-resource()
{
 "outcome" => "success",
 "result" => {
 "xa-data-source" => undefined,
 "data-source" => {"java:jboss/datasources/ExampleDS" => undefined},
 "jdbc-driver" => {"h2" => undefined}
 }
}

你可能已经注意到了某些元素的undefined属性。read-resource命令提供的信息仅限于列出子资源名称。如果你想要读取关于所有子资源的信息,包括它们对应的属性,你必须使用额外的(recursive=true)参数执行命令,如下所示:

[standalone@localhost:9990 /] /subsystem=datasources:read-resource(recursive=true)
{
 "outcome" => "success",
 "result" => {
 "data-source" => {
 "ExampleDS" => {
 "connection-properties" => undefined,
 "connection-url" => "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
 "datasource-class" => undefined,
 "driver-name" => "h2",
 "enabled" => true,
 ...
 }
 },
 "jdbc-driver" => {
 "h2" => {
 "driver-module-name" => "com.h2database.h2",
 "driver-name" => "h2",
 "driver-xa-datasource-class-name" => "org.h2.jdbcx.JdbcDataSource",
 ...
 }
 },
 "xa-data-source" => undefined
 }
}

如你所见,通过添加recursive=true参数,CLI 还包含了配置参数列表,这些参数作为数据源元素的子元素存储。为了简洁起见,我们有意只包括了一些数据源参数。

此外,一些资源可以生成指标,这些指标作为运行时属性被收集。除非你提供include-runtime=true参数,否则默认情况下这些属性不会显示。例如,在数据源子系统内,你可以查看与数据库连接池相关的统计信息:

[standalone@localhost:9990 statistics=pool] :read-resource(include-runtime=true) 
{
 "outcome" => "success",
 "result" => {
 "ActiveCount" => "0",
 "AvailableCount" => "20",
 "AverageBlockingTime" => "0",
 "AverageCreationTime" => "0",
 "AverageGetTime" => "0",
 "BlockingFailureCount" => "0",
 "CreatedCount" => "0",
 "DestroyedCount" => "0",
 "IdleCount" => "0",
 "InUseCount" => "0",
 "MaxCreationTime" => "0",
 "MaxGetTime" => "0",
 "MaxUsedCount" => "0",
 "MaxWaitCount" => "0",
 "MaxWaitTime" => "0",
 "TimedOut" => "0",
 "TotalBlockingTime" => "0",
 "TotalCreationTime" => "0",
 "TotalGetTime" => "0",
 "WaitCount" => "0"
 }
}

如果你想要了解更多关于资源的信息,可以使用read-resource-description命令,它提供简短描述。它还包括资源运行时属性描述。输出可能相当冗长,所以这里我们只包括其头部部分:

[standalone@localhost:9990 statistics=pool] :read-resource-description
{
 "outcome" => "success",
 "result" => {
 "description" => "Runtime statistics provided by the resource adapter.",
 "attributes" => {
 "DestroyedCount" => {
 "description" => "The destroyed count",
 "type" => INT,
 "required" => false,
 "access-type" => "metric",
 "storage" => "runtime"
 },
 "WaitCount" => {
 "description" => "The number of requests that had to wait to obtain a physical connection",
 "type" => INT,
 "required" => false,
 "access-type" => "metric",
 "storage" => "runtime"
 }
 }
 }
}

read-operation-namesread-operation-description命令提供关于特定资源上可用的操作及其描述的列表。这些生成与先前表格中概述的信息,因此我们在此不再重复描述。

接下来,可以使用read-children操作来收集关于子节点的信息。read-children-types命令提供关于子资源的信息,并且与简单的ls命令非常相似。例如,在root资源上,它将生成以下内容:

[standalone@localhost:9990 /] :read-children-types()
{
 "outcome" => "success",
 "result" => [

 "core-service",
 "deployment",
 "deployment-overlay",
 "extension",
 "interface",
 "path",
 "socket-binding-group",
 "subsystem",
 "system-property"
 ]
}

read-children-names提供关于单个子资源的信息,并且几乎等同于执行cd资源后跟一个ls命令。例如,如果我们想了解 AS 上部署的资源列表,我们将输入以下内容:

[standalone@localhost:9990 /] :read-children-names(child-type=deployment)
{
 "outcome" => "success",
 "result" => [
 "Enterprise.ear",
 "EJB.jar",
 "Utility.jar"
 ]
}

最后,read-children-resources命令返回关于特定类型子节点的信息,这需要作为参数提供。此命令相当于对每个子资源执行read-resource操作。在先前的例子中,当我们对一个假设的Enterprise.ear部署资源执行此命令时,它将提供子部署信息,如下所示:

[standalone@localhost:9990 deployment=Enterprise.ear] :read-children-resources(child-type=subdeployment)
{
 "outcome" => "success",
 "result" => {
 "WebApp.war" => {
 "subdeployment" => undefined,
 "subsystem" => {"web" => undefined}
 },
 "Utility.jar" => {
 "subdeployment" => undefined,
 "subsystem" => undefined
 }
 }
}

可选地,你还可以将include-runtime=true作为参数添加,以包含运行时属性,以及recursive=true,它提供了关于所有子资源的递归信息。

使用 CLI 执行命令

如前所述,CLI 还包括一组不绑定到您在 AS 树中的导航路径的操作,但可以在任何地方发出以创建和修改资源。

例如,可以发出version命令以检索 WildFly 运行时关于应用程序服务器和环境的某些基本信息:

[standalone@localhost:9990 /] version
JBoss Admin Command-line Interface
JBOSS_HOME: /opt/wildfly-8.1.0.Final
JBoss AS release: 8.1.0.Final "Kenny"
JAVA_HOME: /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home
java.version: 1.8.0_05
java.vm.vendor: Oracle Corporation
java.vm.version: 25.5-b02
os.name: Mac OS X
os.version: 10.8.5

在大多数情况下,命令用作创建一些资源的别名,例如 JMS 目标和数据源。

让我们在以下章节中看看如何实现这一点。

添加 JMS 目标

您可以使用jms-queue add命令添加 JMS 队列。

注意

如您所见,操作和命令之间的重要区别之一也是传递参数时使用的样式。操作使用括号传递参数(例如,recursive=true)。命令使用格式(--parameter)传递参数,就像在 Unix shell 中一样。

以下是对jms-queue add命令的概述:

jms-queue add --queue-address=queue_name --entries=jndi-name(,jndi-name)* [--profile=profile_name] [--selector=selector_name] [--durable=(true|false)]

这里唯一的必需元素是queue-address,它指定了队列名称以及队列将绑定到的 JNDI 名称条目。可选条目包括selector参数,可以添加以在队列上指定选择器以过滤消息,以及durable参数,它指定队列是否应该是持久的(默认为true)。最后,请注意可选的profile元素,它可以在域配置中使用,以指定将在哪个profile上创建queue

提示

记得使用-c standalone-full.xml参数启动服务器,以便拥有包含消息子系统的服务器配置。如果不这样做,这些命令将导致错误。

以下命令创建了一个新的 JMS 队列(queue1),该队列绑定在 JNDI queues/queue1命名空间下:

jms-queue add [--profile=profile_name] --queue-address=queue1 --entries=java:/jms/queues/queue1

添加 JMS 主题的等效命令是jms-topic add,其语法如下:

jms-topic add [--profile=profile_name]--topic-address=topic_name [--entries=entry(,entry)*] [--profile=profile_name] 

这与 JMS 队列非常相似,只是 JMS 主题的参数较少。在这里不需要selectordurable参数。看看以下命令:

jms-topic add [--profile=profile_name] --topic-address=topic1 --entries=topics/topic1

创建和修改数据源

CLI 提供了一个有用的data-source命令来创建数据源。由于该命令的语法相当长,您可能发现将其保存为 CLI 脚本并适应您的需求很有用。

以下是对data-source命令的概述:

data-source [--profile=<profile_name>] add/remove --jndi-name=<jndi_name> --driver-name=<driver_name> --name=<pool_name>  --connection-url=<connection_url>

除了profile_name之外,前面代码片段中显示的所有其他参数都是必需的。也就是说,如果您想添加或删除数据源,则需要指定它们。就参数而言,您至少需要声明数据源的服务器名称(jndi-name)、驱动程序名称(driver-name)、连接池名称(name)和连接 URL(connection-url)。

您可以通过添加一些可选参数来进一步自定义数据源,就像您在standalone.xml文件中所做的那样。让我们看看一个具体的例子,其中我们创建了一个 MySQL 数据源。首先,我们需要做的是通过部署 JAR 存档提供 JDBC 兼容的驱动程序。考虑到您正在使用独立模式,只需将 JDBC JAR 文件复制到deployments文件夹中。请看以下截图:

创建和修改数据源

一种更简单的方法是通过命令行部署 JDBC 驱动程序。假设您从包含驱动程序的文件夹中启动命令行界面,您将运行以下命令:

[standalone@localhost:9990 /] deploy ./mysql-connector-java-5.1.30-bin.jar

注意

您还可以选择将 JDBC 驱动程序作为模块安装,这是首选方式。此过程在第三章 配置企业服务 中展示。为了本例的目的,我们只是部署了驱动程序,因为这加快了安装过程。

现在,让我们验证驱动程序是否已正确安装在数据源子系统上。我们可以通过在数据源子系统上的installed-drivers-list命令来完成此操作,如下所示:

[standalone@localhost:9990 /] /subsystem=datasources:installed-drivers-list
{
 "outcome" => "success",
 "result" => [
 {
 "driver-name" => "mysql-connector-java-5.1.30-bin.jar_com.mysql.jdbc.Driver_5_1",
 "deployment-name" => "mysql-connector-java-5.1.30-bin.jar_com.mysql.jdbc.Driver_5_1",
 "driver-module-name" => undefined,
 "module-slot" => undefined,
 "driver-datasource-class-name" => undefined,
 "driver-xa-datasource-class-name" => undefined,
 "driver-class-name" => "com.mysql.jdbc.Driver",
 "driver-major-version" => 5,
 "driver-minor-version" => 1,
 "jdbc-compliant" => false
 },
 {
 "driver-name" => "h2",
 "deployment-name" => undefined,
 "driver-module-name" => "com.h2database.h2",
 "module-slot" => "main",
 "driver-datasource-class-name" => "",
 "driver-xa-datasource-class-name" => "org.h2.jdbcx.JdbcDataSource",
 "driver-class-name" => "org.h2.Driver",
 "driver-major-version" => 1,
 "driver-minor-version" => 3,
 "jdbc-compliant" => true
 }
 ]
}

如您所见,现在已安装了两个驱动程序:默认的H2驱动程序和我们之前安装的MySQL驱动程序。

现在,我们已准备好使用 MySQL JDBC 驱动程序创建一个新的数据源:

[standalone@localhost:9990 /] data-source add --jndi-name=java:/MySqlDS --name=MySQLPool --connection-url=jdbc:mysql://localhost:3306/MyDB --driver-name=mysql-connector-java-5.1.30-bin.jar_com.mysql.jdbc.Driver_5_1 --user-name=myuser --password=password --max-pool-size=30

在这个例子中,我们仅创建了一个使用最大连接数30的自定义连接池的 MySQL 绑定数据源。

注意

您不需要记住所有数据源参数的名称。只需使用Tab键来自动完成参数名称。同时,请注意您的驱动程序名称与您运行installed-drivers-list命令时创建的输出匹配。

data-source命令也可以用来从配置中删除数据源。这可以通过传递remove参数和datasourcename来实现,如下所示:

[standalone@localhost:9990 /] data-source remove --name=MySQLPool

注意

您还可以通过在数据源系统资源上执行的操作来添加和删除数据源。请参阅附录 CLI 参考,其中包含最有用的 CLI 命令集。

创建和修改 XA 数据源

修改用于您连接的 XA 数据源类与data-source类似。主要区别在于您将使用xa-data-source命令,如下所示:

[standalone@localhost:9990 /] xa-data-source add --name=MySQLPoolXA --jndi-name=java:/MySqlDSXA --driver-name=mysql-connector-java-5.1.30-bin.jar_com.mysql.jdbc.Driver_5_1 --xa-datasource-properties=[{ServerName=localhost}{PortNumber=3306}]

创建 XA 数据源需要三个参数。您需要一个唯一的namejndi-name,最后是driver-name

这将在您的配置文件中添加以下代码片段:

<xa-datasource jndi-name="java:/MySqlDSXA" pool-name="MySQLPoolXA" enabled="true">
    <xa-datasource-property name="ServerName">
        localhost
    </xa-datasource-property>
    <xa-datasource-property name="PortNumber">
        3306
    </xa-datasource-property>
    <driver>mysql-connector-java-5.1.30-bin.jar_com.mysql.jdbc.Driver_5_1</driver>
</xa-datasource>

从 CLI 获取帮助

如果 CLI 命令的语法对您来说有点令人望而生畏,请不要绝望!除了 Tab 自动完成功能外,CLI 还有每个命令的主页,就像 Unix shell 一样。

如果你发出一个通用的 help 命令,CLI 将返回一个通用的快速入门指南到该界面。另一方面,当作为命令的参数传递时,它提供了对命令概要及其参数的有帮助描述。看看下面的代码片段:

[standalone@localhost:9990 /] cd --help 
SYNOPSIS 

 cn [node_path] 
 cd [node_path] 

DESCRIPTION 

 Changes the current node path to the argument. 
 The current node path is used as the address for operation requests that don't contains the address part. If an operation request does include the address, the included address is considered relative to the current node path. The current node path may end on a node-type. In that case, to execute an operation specifying a node-name would be sufficient (e.g. logging:read-resource).
ARGUMENTS 

 node_path      - the new value for the current node path following the format 
 [node-type [=node-name (,node-type[=node-name])*]].

The following navigation signs are supported in the node-path: 
 /      - the root node (e.g. 'cd /' or 'cd /some=thing'); 
 ..     - parent node (e.g. 'cd ..'); 
 .type  - node type of the current node (e.g. 'cd .type'). 

批处理执行 CLI 脚本

batch 模式允许将多个 CLI 命令作为一个原子单元执行。正如你从普通事务中期望的那样,如果任何命令或操作失败,更改将被回滚。另一方面,如果执行结束而没有错误,更改将被提交。

并非每个命令都可以成为批处理的一部分。例如,导航命令如 cdpwdhelp 被排除,因为它们不会反映对服务器配置的任何更改。

你可以使用 batch 命令标记批处理的开始。当你处于 batch 模式时,你会知道,因为提示符将被 # 符号标记。

为了标记批处理序列的结束,你必须使用 run-batch 命令。一旦完成,所执行的批处理将被丢弃,CLI 将退出 batch 模式。看看下面的示例:

[standalone@localhost:9990 /] batch
[standalone@localhost:9990 /#] jms-queue add --queue-address=queue1 --entries=queues/queue1
[standalone@localhost:9990 /#] deploy MDBApplication.jar
[standalone@localhost:9990 /#] run-batch

在通过输入 run-batch 执行批处理之前,你可以通过发出 list-batch 命令来获取迄今为止输入的所有 batch 命令的列表:

[standalone@localhost:9990 /] batch
[standalone@localhost:9990 /#] jms-queue add --queue-address=queue1 --entries=queues/queue1
[standalone@localhost:9990 /#] deploy MDBApplication.jar
[standalone@localhost:9990 /] list-batch
#1 jms-queue add --queue-address=queue1 --entries=queues/queue1
#2 deploy MDBApplication.jar

高级批处理命令

脚本批处理确实可能比仅仅启动和执行一系列命令更复杂。事实上,当你处于 batch 模式时,按 Tab 完成键,你应该会看到几个额外的命令可用。其中最有用之一是 holdback-batch 命令,它可以用来暂时暂停命令序列,如下所示:

[standalone@localhost:9990 /# ] holdback-batch

为了继续你的命令序列,只需再次发出 batch 命令,如下所示:

[standalone@localhost:9990 /] batch

你甚至可以通过分配一个唯一的名称来保存批处理,这样你可以在脚本中有多个保存点,如下所示:

[standalone@localhost:9990 /# ] holdback-batch step1

之后,你可以通过指定保留名称来继续执行,如下所示:

[standalone@localhost:9990 /] batch step1

当使用 -l 参数执行时,batch 命令提供所持有的批处理文件列表:

[standalone@localhost:9990 /] batch -l
step1

以下表格列出了所有与批处理相关的命令:

命令 描述
batch 此命令启动一个批处理命令。当批处理暂停时,它将重新激活批处理。
list-batch 此命令列出已添加到批处理的命令。
run-batch 此命令执行当前活动的批处理命令并退出 batch 模式。
holdback-batch 此命令保存当前活动的批处理并退出 batch 模式,而不执行批处理。保留的批处理可以在以后通过调用批处理命令重新激活。
clear-batch 此命令从当前活动的批处理中删除所有现有命令行。在命令执行后,CLI 仍处于 batch 模式。
discard-batch 此命令丢弃当前活动的批次。所有添加到批次的命令将被移除,批次将被丢弃,CLI 将退出批次模式。
edit-batch-line 此命令将当前活动的批次中指定行号的现有命令行替换为新命令行。
remove-batch-line 此命令从当前活动的批次中移除指定行号参数的现有命令行。
move-batch-line 此命令将现有行从指定位置移动到新位置。

在文件中执行脚本

到目前为止,我们已将 CLI 命令视为交互会话的一部分。然而,您也可以以非交互方式执行命令,将它们添加到文件中,就像 shell 脚本一样。假设您创建了一个用于发布重新部署命令的示例test.cli文件:

connect
deploy Utility.jar --force

然后使用以下方式通过-file参数启动 CLI:

./jboss-cli.sh --file=test.cli

Windows 用户可以使用以下等效方法:

jboss-cli.bat --file=test.cli

注意

如果您需要在管理接口上进行身份验证,可以将--user--password参数传递给jboss-cli.shjboss-cli.bat调用。

以非交互方式执行命令的另一种方法是向 CLI 传递包含以逗号分隔的命令行列表的--commands参数。例如,前面的脚本也可以这样执行(Unix 用户):

./jboss-xli.sh --commands="connect,deploy Utility.jar --force"

Windows 用户的等效脚本如下:

jboss-cli.bat --commands="connect,deploy Utility.jar --force"

我们将得到以下输出:

'Utility.jar' re-deployed successfully.

重定向非交互式输出

当您以非交互方式执行 CLI 时,可以将输出重定向到文件,否则将打印在屏幕上。就像您会对 shell 命令做的那样,使用>运算符来重定向输出:

./jboss-cli.sh --file=test.cli > out.log   # Linux
jboss-cli.bat --file=test.cli > out.log    # Windows

对配置进行快照

每个人都会犯错误,但其中许多是可以预防的。每次您对配置进行多项更改时,保存工作副本总是一个好主意。这就是快照的作用;使用 CLI 的一个优点是能够创建配置的快照,这些快照存储在其history文件夹中。

history文件夹位于configuration文件夹的下一级。独立服务器有一个名为standalone_xml_historyhistory文件夹,在启动时包含以下文件:

对配置进行快照

另一方面,域名配置提供了两个备份目录,分别用于域名配置文件和主机配置文件。这些文件夹分别命名为domain_xml_historyhost_xml_history。为了使阅读更加简洁,我们将使用独立服务器来描述快照机制。相同的规则也适用于域名服务器,需要注意的是 AS 会同时快照domain.xmlhost.xml文件。

让我们看看历史文件的内容。standalone.initial.xml 文件包含原始应用服务器的配置文件。此文件永远不会被 WildFly 覆盖。

注意

如果您需要恢复初始配置,请不要丢弃您的应用服务器安装!只需将 standalone.xml 文件替换为 standalone_xml_history/standalone.initial.xml

standalone.boot.xml 文件包含服务器上次成功启动时使用的 AS 配置。每次服务器成功启动时,这个文件都会被覆盖。

注意

如果您想撤销当前会话中的所有更改,只需将 standalone.xml 文件替换为 standalone_xml_history/standalone.boot.xml

最后,standalone.last.xml 文件包含应用服务器提交的最后一个成功配置。

应用服务器为您保存的内容

current 文件夹用作临时文件夹,用于存储当前会话中发生的配置更改。应用服务器配置模型中的每次更改都会创建一个名为 standalone.v[n].xml 的文件。在这里,n 是应用更改的次数(初始配置为 standalone.v1.xml,第一次更改为 standalone.v2.xml,依此类推)。

当应用服务器重新启动时,这些文件会被移动到 standalone_xml_history 文件夹中的一个带时间戳的文件夹中。正如您在以下屏幕截图中所见,上次会话中的更改在重启时被移动到 20140702-215555794 文件夹中:

应用服务器为您保存的内容

注意

应用服务器每 30 天轮换带时间戳的文件夹。如果您需要存储应用服务器配置的核心视图,您应该拍摄应用服务器模型的快照。下一节将展示如何进行此操作。

拍摄自己的快照

如早期警告所建议的,您还可以根据需要随时拍摄快照。用户创建的快照直接存储在 snapshot 文件夹中。为了拍摄配置的快照,只需发出 take-snapshot 命令,CLI 将备份您的配置。请查看以下代码块:

[standalone@localhost:9990 /] :take-snapshot 
{
 "outcome" => "success",
 "result" => "/opt/wildfly-8.1.0.Final/standalone/configuration/standalone_xml_history/snapshot/20140702-230647552standalone.xml"
}

您可以使用 list-snapshots 命令检查可用的快照列表:

[standalone@localhost:9990 /] :list-snapshots
{
 "outcome" => "success",
 "result" => {
 "directory" => "/opt/wildfly-8.1.0.Final/standalone/configuration/standalone_xml_history/snapshot",
 "names" => [
 "20140702-230647552standalone.xml",
 "20140702-230817640standalone.xml",
 "20140702-230825599standalone.xml",
 "20140702-230828191standalone.xml"
 ]
 }
}

您可以随时使用 delete-snapshot 命令删除特定的快照,该命令需要快照名称作为参数。假设我们需要删除我们刚刚创建的快照:

[standalone@localhost:9990 /] :delete-snapshot(name=20140702-230828191standalone.xml)
{"outcome" => "success"}

CLI 历史记录

在 CLI 会话中执行的 所有命令都存储在历史记录中,就像 Unix 系统的 shell 命令一样。CLI 命令保存在内存中,并在用户的家目录中名为 .jboss-cli-history 的文件系统中持久化。您会注意到,之前会话中输入的最新 500 个命令(默认历史记录大小)是历史记录的一部分。

如果你想查看 CLI 历史记录,只需输入 history 命令:

[standalone@localhost:9990 /] history

你也可以使用箭头键在命令和操作的历史记录中前后导航,就像你在 Linux bash shell 中做的那样。

history 命令支持三个可选参数,可以用来暂时禁用/启用或清除历史记录。在以下表中,我们提到了它们的输出结果:

参数 影响
disable 此命令禁用历史扩展(但不会清除之前记录的历史)。
enable 此命令重新启用历史扩展(从历史扩展被禁用之前的最后一个记录的命令开始)。
clear 此命令清除内存中的历史记录(但不会清除文件历史记录)。

网络管理控制台

从历史上看,JBoss AS 总是提供基于 Web 的应用程序来执行一些管理和任务。4.x 版本及更早版本使用 jmx-console 来读取/写入并显示 MBeans 的值,MBeans 是应用程序服务器的主干。jmx-console 确实是一个有用的工具;然而,它也需要一定程度的经验才能开始使用。此外,此应用程序中包含的信息分布在许多 MBeans 中。例如,数据源信息包含在四个 MBeans 中,这使得管理此资源变得繁琐。

5.x 和 6.x 版本提出了一种更易于使用的方法,由管理控制台组成,该控制台作为一个基于 seam 的 Web 应用程序构建。尽管新的管理控制台是一个整洁且简单的应用程序,但有些人批评它因为消耗了大量的内存和启动时间。

WildFly 继续使用在 JBoss AS 7 中引入的网络控制台,你已经在之前的章节中看到了它。它是使用 Google Web ToolkitGWT)构建的,并使用 HTTP 管理 API 来配置管理域或独立服务器。

与许多 GWT 应用程序一样,网络控制台使用 JSON 编码协议和去类型的 RPC 风格 API 来描述和执行对托管域或独立服务器的管理操作。

访问管理控制台

WildFly 默认使用端口 9990 来服务管理控制台。你可以通过 http://localhost:9990 访问它,如你的 standalone.xml/domain.xml 配置所示:

<socket-binding name="management-http" interface="management" port="9990"/>

一旦你登录到网络管理控制台,你将进入应用程序首页。在独立模式下,你会看到四个主要选项卡:首页配置运行时管理。以下是对这些选项卡的说明:

  • 首页:此选项卡包含每个选项卡的简要描述,各种快速链接以完成常见任务,以及许多指向其他有用资源的链接,如下面的截图所示:访问管理控制台

  • 配置:此选项卡可用于建模应用程序服务器配置,如下面的截图所示:访问管理控制台

  • 运行时:此选项卡可用于管理部署,正如我们在第六章 应用程序结构和部署 中所学。在下一节中,我们将展示使用 Web 管理控制台配置服务器配置文件有多简单:访问管理控制台

  • 管理:此选项卡用于配置用户、组和角色。我们将在第十章 保护 WildFly 中更详细地介绍这一部分。

配置服务器配置文件

服务器配置文件位于 Web 应用的左侧,可以在配置选项卡下找到。在运行域模式时,你可以通过在页面左上角的组合框中选择相关配置文件来切换配置文件。

一旦你打开配置选项卡,你将看到可以通过 Web 界面配置的一组子系统。

在第二章 配置核心 WildFly 子系统 和 第三章 配置企业服务 中,我们展示了如何使用主要配置文件配置各种资源。如果你是那种喜欢窗口、图标、菜单和指针WIMP)界面的系统管理员,那么接下来的部分就是为你准备的。通过 Web 控制台配置资源非常直观,为了给你一个体验,我们只需涵盖以下主题:

  • 配置数据源

  • 配置 JMS 资源

  • 配置套接字绑定组

配置数据源

你可以直接从主页上的常见任务列表导航到数据源配置面板。否则,你需要点击配置选项卡,然后点击左侧的子系统 | 连接器 | 数据源链接。这将切换主面板到数据源配置面板。此面板包含两个上方的选项卡,用于配置数据源XA 数据源。让我们看看第一个选项卡包含什么。

在面板中间,你可以找到配置的数据源列表。可以应用的操作位于数据源列表上方。你可以通过点击添加按钮来创建一个新的数据源。你还可以在添加按钮旁边找到删除禁用按钮。

编辑或删除现有数据源是一个简单的任务,只需点击按钮即可执行。同样,启用和禁用所选数据源也是如此。

在这里,我们将展示如何将新的数据源添加到您的独立配置中,这需要完成几个简单的步骤。一旦您点击添加按钮,一个三步向导将引导您完成数据源创建。让我们通过以下步骤配置一个示例 MySQL 数据源:完成以下步骤:

  1. 需要的第一个信息将是数据源名称及其 JNDI 绑定,如下截图所示:配置数据源

  2. 下一个步骤将是选择适合您数据源的正确 JDBC 驱动程序。假设您已经在您的 AS 上成功安装了一个 JDBC 驱动程序,它应该列在可用驱动程序中:配置数据源

  3. 选择 MySQL JDBC 驱动程序,在接下来的(最后)步骤中,您将需要输入数据源的 JDBC URL 以及用户名密码凭据,如下截图所示:配置数据源

  4. 点击完成完成向导,您将被重定向到主面板,其中新的数据源现在列在数据源列表中。最后,您需要通过点击它然后点击启用来启用新的数据源:配置数据源

创建新的 XA 数据源

如我们在命令行界面(CLI)部分所展示的,一个 XA 数据源需要您将 JDBC URL 输入为 XA 属性。在通过管理控制台创建 XA 数据源时也是如此。

因此,数据源 JNDI 命名和驱动程序选择与非 XA 数据源相同。在以下截图中,我们说明了完成 XA 数据源创建所需的最后两个步骤:

创建新的 XA 数据源

在第四步,如下截图所示,在用户名密码下方,您将注意到添加安全域的选项。您现在可以留空。我们将在第十章保护 WildFly中讨论安全域。

创建新的 XA 数据源

配置 JMS 目的地

使用 Web 控制台创建新的队列主题更加简单。执行以下步骤:

  1. 配置菜单中,在子系统菜单中选择消息传递选项。主面板将切换以显示消息传递提供者。现在,选择所需的提供者,然后点击查看配置 JMS 目的地

  2. 从那里,选择您想要创建的资源(队列主题)。然后,点击添加按钮来创建一个新的:配置 JMS 目的地

  3. 如果您需要创建一个新的队列,您只需完成下一个简单的对话框,如下截图所示:配置 JMS 目的地

  4. 当您点击保存时,新的 JMS 资源将被列入 JMS 子系统面板(并且也会保存在主配置文件中),如下面的截图所示:配置 JMS 目标

配置套接字绑定组

更改应用程序服务器的套接字绑定可以用来解决与其他应用程序或 WildFly 的其他实例的端口冲突。如果您正在以domain模式运行应用程序,您可以做的最好的事情是为您的服务器指定一个端口号偏移量,正如第四章中指出的,该章节全部关于域服务器。

然而,如果您正在以standalone模式运行,并且只需更改一个或多个端口号,那么通过 Web 控制台可能更容易实现。

要到达套接字绑定组选项,请执行以下步骤:

  1. 在左侧单击套接字绑定,然后单击您想要修改的套接字绑定组的查看

  2. 然后,选择您想要更改的套接字绑定,例如http服务器端口。然后,向下滚动以显示编辑选项。单击编辑按钮并更新端口号,如下面的截图所示:配置套接字绑定组

  3. 完成后,单击保存按钮。

小贴士

需要服务器重启吗?

更改套接字绑定组不会立即产生更改服务器端口的即时效果。更新的配置必须由 AS 重新加载。您可以简单地通过发出restart命令重启应用程序服务器,或者,更好的是,通过从 CLI 发出reload命令。

CLI 或 Web 控制台?

这两个管理接口都是强大的工具,在某些情况下,一个可能比另一个更好。

例如,CLI 为应用程序服务器提供了巨大的补充,在相对较短的时间内,它将让您以精细的细节配置其每个资源,包括运行时指标。

另一方面,Web 控制台提供了一个简单而优雅的方式来管理您的 AS 资源,几乎不需要学习曲线。特别是,我们在第三章中展示了如何轻松地使用它来管理基本域功能,例如配置、启动和停止服务器组和主机。

以下表格显示了每个接口的主要益处的摘要:

工具 最佳用途
CLI 作为专家系统管理员的无价工具,深入访问服务器属性,例如指标执行宏或批量等操作
Web 控制台 是执行大多数基本管理任务的便捷工具管理顶级域资源

摘要

在本章中,你学习了如何使用 AS 发行版中的工具来管理应用程序服务器。

你已经熟悉了 CLI,它允许你遍历 AS 资源树并发出可以读取/修改或显示属性的命令。

CLI 的一个优点是,你可以利用其自动完成功能轻松构建复杂的管理操作。CLI 还允许你批量列出命令,以便你可以以全有或全无的方式执行它们,这在事务性系统中很典型。

另一个管理工具是 Web 界面,它允许你使用直观且简单的界面操作服务器配置。对于需要执行基本管理任务的系统管理员来说,这是一个理想的工具,因为它使用起来几乎不需要或只需要很少的经验。

到目前为止,你已经拥有了足够的专业知识来处理更复杂的话题。因此,在下一章中,我们将讨论应用程序服务器集群,这可以使你的应用程序提供可伸缩性和高可用性。

第八章。集群

本章将介绍 WildFly 的集群功能。集群一词用于描述跨越多个机器的系统。系统组件在多台机器上同步通常可以提高性能和可用性。

集群是提供应用程序可伸缩性和高可用性的基本组件。使用集群的一个主要好处是,您可以通过负载均衡将流量负载分散到多个 AS 实例。

负载均衡是企业应用程序的一个正交方面,通常通过在应用程序服务器前面使用配置正确的 Web 服务器来实现。因此,负载均衡将在下一章中讨论,而本章我们将讨论以下主题:

  • 所有可用于设置 WildFly 集群的选项,无论是使用独立配置还是服务器域

  • 如何有效地配置集群所需的各个组件

  • JGroups 子系统,用于节点之间的底层通信

  • Infinispan 子系统,它使用其高级数据网格平台处理集群一致性

  • 消息传递子系统,它使用 HornetQ 可集群实现

设置 WildFly 集群

为了满足那些不耐烦的读者的需求,我们将立即向您展示如何快速设置并运行 WildFly 节点集群。

您要塑造一个新的服务器配置文件只需创建一个新的 XML 配置文件。由于独立服务器只包含一个配置文件,您可能希望使用名为 standalone-ha.xmlstandalone-full-ha.xml 的配置文件。这两个配置文件都包含在 WildFly 中。此配置文件包含所有集群子系统。

另一方面,域服务器能够存储多个配置文件在核心 domain.xml 配置文件中,因此您可以使用此文件既用于集群域,也用于非集群域服务器。

注意

集群和域是两个不同的概念,每个的功能不重叠。虽然集群的目的是提供可伸缩性、负载均衡和高可用性,但域是服务器的一个逻辑分组,这些服务器共享集中式域配置,并且可以作为一个单一单元进行管理。

现在我们将描述组装和启动独立服务器和域服务器集群的不同方法。

设置独立服务器集群

将 WildFly 集群配置为独立服务器可以分为两种主要可能性:

  • 在不同机器上运行的 WildFly 节点集群

  • 在同一机器上运行的 WildFly 节点集群

我们将依次查看这些内容。

在不同机器上运行的节点集群

如果你决定在每个专用机器上安装 WildFly 服务器,你正在水平扩展你的集群。在配置方面,这需要最少的努力——你所要做的就是将服务器绑定到配置文件中的 IP 地址,并使用standalone-ha.xml配置启动服务器。让我们通过以下图示的简单、双节点集群构建一个示例:

运行在不同机器上的节点集群

在每个 WildFly 发行版的standalone-ha.xml文件上打开,并导航到interfaces部分。在嵌套的接口元素中,插入独立服务器的 IP 地址。对于第一台机器(192.168.10.1),我们将定义以下内容:

<interfaces>
        <interface name="management">
            <inet-address value="192.168.10.1"/>
        </interface>
        <interface name="public">
            <inet-address value="192.168.10.1"/>
        </interface>
</interfaces>

在第二台机器(192.168.10.2)上,我们将绑定到另一个 IP 地址:

<interfaces>
        <interface name="management">
            <inet-address value="192.168.10.2"/>
        </interface>
        <interface name="public">
            <inet-address value="192.168.10.2"/>
        </interface>
</interfaces>

这是你需要更改配置的唯一内容。要启动集群,你必须使用standalone-ha.xml配置文件启动你的独立服务器,如下所示:

./standalone.sh -c standalone-ha.xml

注意

而不是在standalone-ha.xml文件中更新每个服务器的 IP 地址,你可以使用-b选项,它允许你在服务器启动时提供绑定 IP 地址。此外,你可以使用-bmanagement标志来指定管理接口地址。使用这些选项,第一个服务器的先前配置可以重写为:

standalone.sh -c standalone-ha.xml –b 192.168.10.1 -bmanagement 192.168.10.1

对于第二个服务器,它可以重写为:

standalone.sh -c standalone-ha.xml –b 192.168.10.2 -bmanagement 192.168.10.2

几秒钟内,你的服务器将开始运行;然而,我们没有提到与控制台中的集群节点相关的任何细节。这是因为,在 WildFly 中,核心服务仅在需要时启动。这意味着集群服务仅在服务器检测到需要时启动,并在不再需要时停止。因此,仅使用包含集群子系统的配置启动服务器不会启动集群服务。为此,我们需要部署一个集群启用的应用程序。

因此,为了验证我们的安装,我们将部署一个基本的、集群启用的、名为Example.war的 Web 应用程序。要启用你的 Web 应用的集群,你必须将它们标记为*distributable*web.xml描述符中:

<web-app>
   <distributable/>
</web-app>

当你将应用程序部署到两台机器上时,你会看到集群服务现在已启动,并且每台机器都能在集群中找到其他成员,如下所示:

运行在不同机器上的节点集群

运行在同一台机器上的节点集群

当你的服务器节点(全部或部分)位于同一台机器上时,独立配置的第二种变体开始发挥作用。这种情况通常适用于你通过向计算机添加更多硬件资源来垂直扩展你的架构时。

在同一台机器上配置服务器节点显然需要你在文件系统中复制你的 WildFly 发行版。为了避免服务器发行版之间的端口冲突,你必须在这两个选项之间进行选择:

  • 在同一台机器上定义多个 IP 地址

  • 为每个服务器发行版定义端口偏移量

在同一台机器上使用多个 IP 地址设置集群

这也被称为 多宿主,需要少量配置才能工作。每个操作系统都有不同的方法来实现这一点。本书不涉及配置多宿主的可能方法,但如果你对多宿主感兴趣,我们提供了有关如何在 Linux 和 Windows 上设置多宿主的详细说明链接。

如果你使用 Linux,这篇教程详细介绍了如何将多个 IP 地址分配给单个网络接口,也称为 IP 别名

www.tecmint.com/create-multiple-ip-addresses-to-one-single-network-interface/

Windows 用户可以参考以下博客,了解如何在 Windows 7 中设置多宿主:

shaheerart.blogspot.com/2011/05/how-to-configure-multihomed-server-in.html

一旦你正确设置了网络接口,你将需要更新你的 standalone-ha.xml 文件。你需要将每个 IP 绑定到不同的 WildFly 实例,就像我们在设置多主机集群时做的那样。在配置文件中,导航到 interfaces 部分,并在嵌套的 interface 元素中插入要绑定到该独立服务器的 IP 地址:

<interfaces>
        <interface name="management">
            <inet-address value="192.168.10.2"/>
        </interface>
        <interface name="public">
            <inet-address value="192.168.10.2"/>
        </interface>
</interfaces>

在这个例子中,第一个服务器分配绑定到 IP 地址 192.168.10.1,第二个绑定到 192.168.10.2。(记住,你还可以使用前面描述的 -b-bmanagement 开关)。

下图展示了这个场景:

在同一台机器上使用多个 IP 地址设置集群

在同一台机器上使用端口偏移设置集群

配置多宿主并不总是可行的选择,因为它需要一定量的网络管理经验。一个更简单、更直接的选择是为每个集群成员定义一个端口偏移量。通过为每个服务器定义端口偏移量,所有默认服务器绑定接口都将按固定数值移动,因此你不会有两个服务器在相同的端口上运行,从而避免端口冲突。

当使用端口偏移时,你将每个服务器绑定到相同的 IP 地址。因此,对于所有你的服务器发行版,你将按照以下方式配置 standalone-ha.xml 文件:

<interfaces>
        <interface name="management">
            <inet-address value="192.168.10.1"/>
        </interface>
        <interface name="public">
            <inet-address value="192.168.10.1"/>
        </interface>
</interfaces>

然后你将第一个服务器配置保持不变。它将使用默认的套接字绑定端口:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="0">
...
</socket-binding-group>

对于第二个服务器配置,你将指定 port-offset 值为 150

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="150"
...
</socket-binding-group>

您的集群配置现在已完成。您可以通过将配置文件作为参数传递给每个服务器分布来验证此操作:

standalone.sh -c standalone-ha.xml

从以下屏幕截图可以看出,已应用 150 的端口偏移:

在同一台机器上使用端口偏移设置集群

设置域服务器的集群

当您配置域集群时,您会发现聚类子系统已经包含在主配置文件domain.xml中。

实际上,WildFly 域将聚类处理得就像应用程序服务器使用的另一个配置文件一样。打开domain.xml文件,您会看到应用程序服务器附带以下四个配置文件:

  • 针对非集群环境的default配置文件

  • 针对集群环境的ha配置文件

  • 针对非集群环境的full配置文件,包含所有子系统

  • 针对集群环境的full-ha配置文件,包含所有子系统

因此,为了在域上使用聚类,您必须首先配置您的服务器组以指向其中一个ha配置文件。

让我们看看一个使用两个服务器组的示例配置。以下代码片段来自domain.xml

<server-groups>
 <server-group name="main-server-group" profile="ha">
        <jvm name="default">
            <heap size="64m" max-size="512m"/>
        </jvm>
 <socket-binding-group ref="ha-sockets"/>
    </server-group>
 <server-group name="other-server-group" profile="ha">
        <jvm name="default">
            <heap size="64m" max-size="512m"/>
        </jvm>
 <socket-binding-group ref="ha-sockets"/>
    </server-group>
</server-groups>

socket-binding-group元素所强调的,我们正在引用包含集群中所有套接字绑定的ha-sockets组。请看以下代码:

<socket-binding-group name="ha-sockets" default-interface="public">
    <socket-binding name="ajp" port="8009"/>
    <socket-binding name="http" port="8080"/>
    <socket-binding name="https" port="8443"/>
    <socket-binding name="jgroups-mping" port="0" multicast-address="230.0.0.4" multicast-port="45700"/>
    <socket-binding name="jgroups-tcp" port="7600"/>
    <socket-binding name="jgroups-tcp-fd" port="57600"/>
    <socket-binding name="jgroups-udp" port="55200" multicast-address="230.0.0.4" multicast-port="45688"/>
    <socket-binding name="jgroups-udp-fd" port="54200"/>
    <socket-binding name="modcluster" port="0" multicast-address="224.0.1.105" multicast-port="23364"/>
    <socket-binding name="txn-recovery-environment" port="4712"/>
    <socket-binding name="txn-status-manager" port="4713"/>
    <outbound-socket-binding name="mail-smtp">
        <remote-destination host="localhost" port="25"/>
    </outbound-socket-binding>
</socket-binding-group>

接下来,我们需要定义属于域(和集群)的服务器。为了使事情简单,我们将重用默认host.xml文件中找到的域服务器列表,如下面的代码片段所示:

<servers>
    <server name="server-one" group="main-server-group">
        <jvm name="default">
    </server>
    <server name="server-two" group="main-server-group" auto-start="true">
        <socket-bindings port-offset="150"/>
    </server>
    <server name="server-three" group="other-server-group" auto-start="false">
        <socket-bindings port-offset="250"/>
    </server>
</servers>

我们不需要为每个服务器指定套接字绑定组,因为这在domain.xml文件中已经配置好了。如果我们想覆盖套接字绑定组,则可以在host.xml文件中添加以下内容:

<servers>
    ...
    <server name="server-one" group="other-server-group" auto-start="false">
        <socket-bindings socket-binding-group="ha-sockets"/>
    </server>
</servers>

以下图显示了此配置的概述:

设置域服务器的集群

您现在可以使用标准批处理脚本(domain.shdomain.bat)启动您的集群域。服务器组现在将指向ha配置文件,并形成一个由两个节点组成的集群。

聚集故障排除

通过集群中的节点进行通信是通过 UDP 和多播实现的。

注意

多播是一种协议,通过该协议数据同时传输到所有属于多播组的宿主。您可以将多播想象成一个只有调谐到特定频率的接收器才能接收数据的无线电频道。

如果您遇到问题,通常是由于以下原因之一:

  • 节点位于防火墙之后。如果您的节点在不同的机器上,那么防火墙可能正在阻止多播。您可以通过为每个节点禁用防火墙或添加适当的规则来测试这一点。

  • 您正在使用家庭网络或位于网关之后。通常,家庭网络会将任何 UDP 流量重定向到 互联网服务提供商ISP),然后 ISP 要么丢弃它,要么它就丢失了。为了解决这个问题,您需要在防火墙/网关中添加一个路由,将任何多播流量重定向回本地网络。

提示

Mac OS X

如果您使用的是 Mac,当尝试以 ha 模式启动域时,可能会遇到 java.io.IOException: Network is unreachable 错误。为了解决这个问题,您需要创建一个合适的网络路由来使用 UDP,如下所示:

sudo route add 224.0.0.0 127.0.0.1 -netmask 240.0.0.0

为了让您检查您的机器是否已正确设置以进行多播,JGroups 随带两个测试应用程序,可用于测试 IP 多播通信。测试类是 McastReceiverTestMcastSenderTest

为了测试服务器上的多播通信,您首先需要导航到 modules 目录中 jgroups JAR 文件的位置,如下所示:

JBOSS_HOME/modules/system/layers/base/org/jgroups/main

在此目录中,您将找到包含测试程序的 jgroups-3.4.3.Final.jar 文件。

现在,通过运行以下命令来运行 McastReceiverTest

java -classpath jgroups-3.4.3.Final.jar org.jgroups.tests.McastReceiverTest -mcast_addr 224.10.10.10 -port 5555

在同一台机器上,但在不同的终端中,运行 McastSenderTest 命令,如下所示:

java -classpath jgroups-3.4.3.Final.jar org.jgroups.tests.McastSenderTest -mcast_addr 224.10.10.10 -port 5555

如果多播工作正常,您应该在 McastSenderTest 窗口中输入,并在 McastReceiverTest 窗口中看到输出,如下面的截图所示:

集群故障排除

您应该在集群中的每台机器上执行此测试。一旦完成,您需要确保通过在一台机器上运行 McastSenderTest 和在另一台机器上运行 McastReceiverTest 来确保集群中每台机器之间的 UDP 通信正常工作。

最后,如果您在使用默认的多播地址或端口时遇到问题,您可以通过修改 domain.xml 文件中的 jgroups-udp socket-binding group 来更改它:

<socket-binding-groups>
    ...
    <socket-binding-group name="ha-sockets" default-interface="public">
        ...
        <socket-binding name="jgroups-udp" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
        ...
    </socket-binding-group>
</socket-binding-groups>

配置 WildFly 集群

WildFly 支持开箱即用的集群功能。有几个库协同工作以提供对集群的支持。以下图显示了 WildFly 采纳的基本集群架构:

配置 WildFly 集群

JGroups 库是 WildFly 集群的核心。它通过多播传输提供集群节点之间的通信通道。这些通道在部署集群应用程序时创建,并用于在集群中传输会话和上下文。

集群中的另一个重要组件是 Infinispan。Infinispan 通过分布式缓存的方式处理您的应用程序数据在集群中的复制。

配置 JGroups 子系统

在 JGroups 的范围内,节点通常被称为 成员,而集群被称为

节点是在主机上运行的一个进程。JGroups 跟踪组内所有进程。当一个节点加入一个组时,系统会向该组的所有现有成员发送消息。同样,当一个节点离开或崩溃时,该组的所有其他节点都会收到通知。

如我们在本章前面概述的,一个组的进程(节点)可以位于同一主机上,也可以位于网络上的不同机器上。一个成员也可以是多个组的成员。以下图展示了 JGroups 架构的详细视图:

配置 JGroups 子系统

JGroups 进程大致由三部分组成,即通道构建块协议栈

  • 通道是一个类似于套接字的简单接口,应用程序程序员使用它来构建可靠的群组通信应用程序。

  • 构建块共同形成了一个抽象接口,该接口位于通道之上,可以在需要高级接口时替代通道使用。

  • 协议栈包含一个双向列表中的多个协议层。所有发送的消息都必须通过所有协议。一个层不一定对应一个传输协议。例如,一个分片层可能会将一个消息分成几个较小的消息,为每个片段添加一个带有 ID 的头部,并在接收方重新组装这些片段。

在前一个图中,当发送消息时,首先执行PING协议,然后是MERGE2,接着是FD_SOCK,最后是FD协议。当收到消息时,这个顺序会被颠倒,这意味着它首先会遇到FD协议,然后是FD_SOCK,接着是MERGE2,最后是PING。在 WildFly 中,JGroups 配置位于主standalone-ha.xml/domain.xml配置文件中的 JGroups 子系统内。

在 JGroups 子系统中,你可以找到配置的传输堆栈列表。以下代码片段显示了节点之间通信使用的默认 UDP 堆栈:

<subsystem  default-stack="udp">
    <stack name="udp">
        <transport type="UDP" socket-binding="jgroups-udp"/>
        <protocol type="PING"/>
        <protocol type="MERGE3"/>
        <protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
        <protocol type="FD_ALL"/>
        <protocol type="VERIFY_SUSPECT"/>
        <protocol type="pbcast.NAKACK2"/>
        <protocol type="UNICAST3"/>
        <protocol type="pbcast.STABLE"/>
        <protocol type="pbcast.GMS"/>
        <protocol type="UFC"/>
        <protocol type="MFC"/>
        <protocol type="FRAG2"/>
        <protocol type="RSVP"/>
    </stack>
    ...
</subsystem>

UDP 是 JGroups 的默认协议,使用多播(如果不可用,则使用多个单播消息)来发送和接收消息。一个多播 UDP 套接字可以向多个客户端发送和接收数据报。多播的另一个特性是,客户端可以使用单个数据包联系多个服务器,而不需要知道任何主机的特定 IP 地址。

注意

切换到 TCP 协议就像更改default-stack属性一样简单:

<subsystem  default-stack="tcp">

当 IP 多播由于某些原因无法使用时,通常使用 TCP 堆栈。例如,当你想在 WAN 上创建一个网络时。我们将在本章后面介绍 TCP 配置。

所有 JGroups 协议的详细描述超出了本书的范围,但为了方便,你可以在以下表中找到每个协议的简要描述。要了解更多关于这些协议或关于 JGroups 的信息,你可以参考 JGroups 网站jgroups.org/manual/html/index.html

类别 用途 协议
传输 这负责在网络中发送和接收消息 IDP, TCP, 和 TUNNEL
发现 这用于发现集群中的活动节点并确定协调者是谁 PING, MPING, TCPPING, 和 TCPGOSSIP
故障检测 此功能用于轮询集群节点以检测节点故障 FD, FD_SIMPLE, FD_PING, FD_ICMP, FD_SOCK, 和 VERIFY_SUSPECT
可靠投递 这确保消息确实以正确的顺序(FIFO)发送到目标节点 CAUSAL, NAKACK, pbcast.NAKACK, SMACK, UNICAST, 和 PBCAST
组成员 这用于在节点加入、离开或崩溃时通知集群 pbcast.GMS, MERGE, MERGE2, 和 VIEW_SYNC
流控制 这用于调整节点间数据发送速率以适应数据接收速率 FC
分片 这会将大于特定大小的消息分片,并在接收方处解分片 FRAG2
状态转移 此功能将现有节点上的应用程序状态(序列化为字节数组)与新加入的节点同步 pbcast.STATE_TRANSFERpbcast.STREAMING_STATE_TRANSFER

自定义协议栈

如果你想要在较低级别自定义传输配置,那么你可以覆盖 JGroups 使用的默认属性,甚至可以覆盖单个协议属性。例如,以下配置可以用来更改 JGroups UDP 堆栈使用的默认发送或接收缓冲区:

<subsystem  default-stack="udp">
  <stack name="udp">
    <transport type="UDP" socket-binding="jgroups-udp" diagnostics-socket-binding="jgroups-diagnostics">
      <property name="ucast_recv_buf_size">50000000</property>
      <property name="ucast_send_buf_size">1280000</property>
      <property name="mcast_recv_buf_size">50000000</property>
      <property name="mcast_send_buf_size">1280000</property>
    </transport>
    ...
  </stack>
</subsystem>

如果你想查看 JGroups 子系统内可用的所有属性,无论是在传输层还是在协议层,你可以查阅你的服务器发行版JBOSS_HOME/docs/schema文件夹中的 JGroups XSD 文件,jboss-as-jgroups_2_0.xsd

配置 Infinispan 子系统

集群的一个要求是数据在其成员之间同步。这是因为,如果某个节点发生故障,应用程序及其会话可以在集群的其他成员上继续运行。这被称为高可用性

WildFly 使用 Infinispan 作为其集群功能背后的分布式缓存解决方案。尽管 Infinispan 嵌入在应用程序服务器中,但它也可以作为一个独立的数据网格平台。

我们现在将快速查看 Infinispan 的配置,该配置位于主standalone-ha.xmldomain.xml配置文件中的 Infinispan 子系统内。

以下是 Infinispan 配置的核心:

<subsystem >
    <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
    <transport lock-timeout="60000"/>
    <replicated-cache name="default" mode="SYNC" batching="true">
        <locking isolation="REPEATABLE_READ"/>
    </replicated-cache>
    </cache-container>
    <cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
        ...
    </cache-container>
    <cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
        ...
    </cache-container>
    <cache-container name="hibernate" default-cache="local-query" module="org.hibernate">
        ...
    </cache-container>
</subsystem>

独立 Infinispan 配置与 WildFly 内部的 Infinispan 子系统之间的一个关键区别是,WildFly 配置公开了多个cache-container元素,而本地配置文件包含单个缓存容器的配置。

每个cache-container元素包含一个或多个缓存策略,这些策略定义了特定缓存容器中数据的同步方式。缓存容器可以使用以下缓存策略:

  • 本地: 在这种缓存模式下,条目仅存储在本地节点上,无论是否形成了集群。Infinispan 通常作为本地缓存运行。

  • 复制: 在这种缓存模式下,所有条目都复制到所有节点。Infinispan 通常作为临时数据存储运行,并不提供增加的堆空间。

  • 分布: 在这种缓存模式下,条目仅分布到节点子集。Infinispan 通常作为提供增加堆空间的数据网格运行。

  • 失效: 在这种缓存模式下,条目仅存储在缓存存储中(例如数据库)并从所有节点中失效。当一个节点需要条目时,它将从缓存存储中加载它。在这种模式下,Infinispan 作为由规范数据存储(如数据库)支持的分布式缓存运行。

在接下来的章节中,我们将更详细地查看一些缓存配置,例如session缓存(web缓存和SFSB缓存)以及hibernate缓存。如果您要正确配置您的集群应用程序,理解这些配置是至关重要的。

配置会话缓存容器

在本节中,我们将查看 HTTP 会话、有状态和单例会话 bean 的缓存配置。这三个缓存配置的方式非常相似。因此,我们将一起讨论它们,并展示它们之间的相似性。因此,以下是web缓存、ejb缓存和server缓存的cache-container配置。web缓存指的是 HTTP 会话缓存,ejb缓存与有状态会话 bean(SFSBs)相关,server缓存与单例会话 bean 相关:

<subsystem >

    <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
        <transport lock-timeout="60000" />
        <replicated-cache name="default" mode="SYNC" batching="true">
            <locking isolation="REPEATABLE_READ" />
        </replicated-cache>
    </cache-container>

    <cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
        <transport lock-timeout="60000" />
        <distributed-cache name="dist" mode="ASYNC" batching="true" l1-lifespan="0" owners="2">
            <file-store />
        </distributed-cache>
    </cache-container>

    <cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
        <transport lock-timeout="60000" />
        <distributed-cache name="dist" mode="ASYNC" batching="true" l1-lifespan="0" owners="2">
            <file-store />
        </distributed-cache>
    </cache-container>
</subsystem>

每个容器的配置可以包含一个或多个缓存策略元素。这些元素如下:

  • 复制缓存

  • 分布式缓存

  • 失效缓存

  • 本地缓存

每个这些缓存元素都可以定义零次或多次。要指定用于缓存容器的缓存元素,只需将缓存名称作为default-cache属性的属性引用。在下一节中,我们将详细探讨这些缓存模式之间的差异。在每个缓存定义中,您可能已经注意到与等效数据库隔离级别相对应的locking属性。Infinispan 支持以下隔离级别:

  • NONE: 没有隔离级别意味着没有事务支持。

  • READ_UNCOMMITTED:最低的隔离级别,允许脏读,这意味着一个事务可能看到来自另一个事务未提交的数据。行仅在写入数据时锁定,而不是在读取时锁定。

  • READ_COMMITTED:事务在检索的所有数据上获取读和写锁。写锁在事务结束时释放,读锁在数据被选中时立即释放。

  • REPEATABLE_READ:这是 Infinispan 使用的默认隔离级别。事务在检索的所有数据上获取读和写锁,并保持到事务结束。可能会发生幻读。幻读是指你在同一个事务中执行相同的查询,但得到的结果数量不同。

  • SERIALIZABLE:最严格的隔离级别。所有事务都以隔离方式发生,就像它们是按顺序(一个接一个)执行一样,而不是并发执行。

缓存配置中嵌套的另一个元素是file-store。此元素配置了存储缓存数据的路径。默认数据写入与缓存容器同名的文件夹下的jboss.server.data.dir目录。

例如,以下图显示了独立web缓存容器的默认file-store路径:

配置会话缓存容器

如果你愿意,可以使用relative-topath元素自定义file-store路径,就像我们在第二章中做的那样,配置核心 WildFly 子系统,对于路径元素:

<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
    <distributed-cache name="dist" mode="ASYNC" batching="true" l1-lifespan="0" owners="2">
        <file-store relative-to="jboss.server.data.dir" path="my-cache"/>
    </distributed-cache>
</cache-container>

在继续之前,让我们简要地看看消息如何在各个节点之间发送。

成员之间的数据同步可以通过同步消息(SYNC)或异步消息(ASYNC)来完成,具体定义如下:

  • 同步消息是两者中效率最低的,因为每个节点都需要等待来自其他集群成员的消息确认。然而,如果需要高一致性,同步模式是有用的。

  • 异步消息是两者中较快的,但缺点是一致性会受到影响。异步消息在启用 HTTP 会话复制和粘性会话时特别有用。在这种情况下,会话总是从同一个集群节点访问。只有当节点失败时,数据才会从不同的节点访问。

同步(SYNC)和异步(ASYNC)属性是在缓存元素的mode属性中设置的:

<distributed-cache name="dist" mode="ASYNC"    batching="true" l1-lifespan="0" owners="2">

选择复制和分布之间的选择

当使用复制缓存时,Infinispan 将在集群网格中的每个节点上存储每个条目。这意味着添加到这些缓存实例中的任何条目都将复制到集群中的所有其他缓存实例,并且可以从任何缓存中检索任何条目。箭头指示数据复制的方向。在下面的图中,您可以看到来自节点 1 的会话数据正在被复制到节点 2、3 和 4:

选择复制和分布

复制的可伸缩性是集群大小和平均数据大小的函数。如果我们有多个节点和/或大数据集,我们将遇到可伸缩性的上限。

注意

如果DATA_SIZE * NUMBER_OF_HOSTS小于每个主机可用的内存,那么复制是一个可行的选择。

另一方面,当使用分布式缓存时,Infinispan 将在网格的节点子集上存储每个集群条目。

分布式缓存利用一致性哈希算法来确定条目应在集群中存储的位置。您可以配置跨集群维护的缓存条目副本数量。您在这里选择的值是在性能和数据持久性之间的平衡。您维护的副本越多,性能越低,但服务器故障导致数据丢失的风险越低。

注意

您可以使用owners参数(默认值为2)来定义每个缓存条目的集群范围副本数:

<distributed-cache name="dist" mode="ASYNC" batching="true" l1-lifespan="0" owners="2">
    <file-store/>
</distributed-cache>

以下图显示了当owners参数设置为2时,会话数据如何在节点之间进行复制。每个节点将其会话数据复制到另外两个节点:

选择复制和分布

复制和分布之间的选择在很大程度上取决于集群大小。例如,复制提供了一种快速简单的方法在集群之间共享状态;然而,它仅在小型集群(少于十个服务器)中表现良好。这是因为随着集群大小的增加,需要发送的复制消息数量增加。在分布式缓存中,为了提供冗余和容错,节点间维护了条目的多个副本。保存的副本数量通常远少于集群中的节点数量。这意味着分布式缓存比复制缓存提供了更大的可伸缩性。

配置 Hibernate 缓存

Hibernate 缓存容器是您配置的关键部分,因为它处理数据层的缓存。WildFly 使用 Hibernate 作为默认的 JPA 实现,因此本章中描述的概念既适用于 Hibernate 应用程序,也适用于基于 JPA 的应用程序。Hibernate 缓存在概念上与基于会话的缓存不同。它们基于这样的假设,即您有一个永久存储数据的地方(数据库)。这意味着为了实现高可用性,不需要在集群中复制或分发实体的副本。您只需在数据被修改时通知您的节点,以便缓存中的条目可以被失效。如果缓存配置为失效而不是复制,每次缓存中的数据发生变化时,集群中的其他缓存都会收到一条消息,告知它们的数据现在已过时,应该从内存中移除。

这种方法的优点是双重的。首先,由于失效消息与复制更新数据相比非常小,因此网络流量最小化。其次,集群中的缓存只有在数据过时时才需要执行数据库查找。每次从数据库读取新的实体或集合时,它只本地缓存,以减少节点之间的流量:

<cache-container name="hibernate" default-cache="local-query" module="org.hibernate">
    <transport lock-timeout="60000"/>
    <local-cache name="local-query">
        <transaction mode="NONE"/>
        <eviction strategy="LRU" max-entries="10000"/>
        <expiration max-idle="100000"/>
    </local-cache>
    ...
</cache-container>

默认情况下,local-query 缓存配置为在 LRU 向量中存储最多 10,000 个条目。如果条目在 100,000 毫秒内处于空闲状态,则根据 max-idle 属性,它将自动从缓存中驱逐。

以下是对 Infinispan 支持的驱逐策略的总结:

  • NONE: 此值禁用驱逐线程

  • UNORDERED: 现在已弃用。使用此值将导致使用 LRU

  • LRU: 此值会导致基于最近最少使用模式的驱逐发生

  • LIRS: 此值解决了 LRU 的不足。驱逐依赖于缓存条目的互引用最近性

小贴士

要了解更多关于 LIRS 如何工作的信息,请参阅 Infinispan 文档infinispan.org/docs/6.0.x/user_guide/user_guide.html#_eviction_strategies

一旦本地缓存实体被更新,缓存将向集群中的其他成员发送消息,告知它们实体已被修改。这就是invalidation-cache发挥作用的时候。看看以下代码:

<invalidation-cache name="entity" mode="SYNC">
     <transaction mode="NON_XA"/>
     <eviction strategy="LRU" max-entries="10000"/>
     <expiration max-idle="100000"/>
</invalidation-cache>

无效化缓存的默认配置使用与本地查询缓存相同的驱逐和过期设置。最大条目数设置为 10,000,过期前的空闲时间为 100,000 毫秒。无效化缓存也可以配置为同步(SYNC)或异步(ASYNC)。如果您将无效化缓存配置为同步,则您的缓存将阻塞,直到集群中的所有缓存都收到无效化消息的响应。另一方面,异步无效化缓存不会阻塞并等待响应,这会导致性能提高。默认情况下,hibernate 配置为使用REPEATABLE_READ作为缓存隔离级别。对于大多数情况,默认的隔离级别REPEATABLE_READ将足够。如果您想将其更新为,例如,READ_COMMITTED,那么您需要将以下内容添加到您的配置中:

<invalidation-cache mode="SYNC" name="entity">
    ...
 <locking isolation="READ_COMMITTED"/>
</invalidation-cache>

我们将在 Infinispan 子系统中查看的最后一点配置是timestamp缓存。timestamp缓存跟踪每个数据库表最后更新的时间。

timestamp缓存严格相关于查询缓存。它用于存储对数据库运行的查询的结果集。如果启用了查询缓存,在查询运行之前,会检查查询缓存。如果表上最后更新的时间戳大于查询结果缓存的時間,则条目将从缓存中删除,并执行新的数据库查找。这被称为缓存未命中。请看以下代码:

<replicated-cache name="timestamps" mode="ASYNC">
          <transaction mode="NONE"/>
          <eviction strategy="NONE"/>
</replicated-cache>

默认情况下,timestamps缓存配置为异步复制作为集群模式。由于所有集群节点都必须存储所有时间戳,因此不允许本地或失效的缓存类型,也不允许驱逐/过期。

使用复制为 Hibernate 缓存

可能存在您想要在其他集群节点上复制实体缓存,而不是使用本地缓存和无效化的情况。这可能是在以下情况下:

  • 执行的查询相当昂贵

  • 查询很可能在不同的集群节点上重复

  • 查询不太可能从缓存中失效(当查询的WHERE子句中涉及的实体类之一发生变化时,Hibernate 会从缓存中使查询结果失效)

为了切换到复制缓存,您必须配置您的default-cache属性,如下面的代码片段所示,以及添加相关的replicated-cache配置:

<cache-container name="hibernate" default-cache="replicated-cache" module="org.hibernate">
    <replicated-cache name="replicated-cache" mode="SYNC">
        <locking isolation="REPEATABLE_READ"/>
    </replicated-cache>
</cache-container>

高级 Infinispan 配置

到目前为止,我们已查看使用集群应用程序所需的基本组件。Infinispan 提供了丰富的选项来进一步自定义您的缓存。

小贴士

要了解有关通过 Infinispan 子系统的高级配置的更多信息,你可以查看以下文档:infinispan.org/docs/6.0.x/infinispan_server_guide/infinispan_server_guide.html

配置 Infinispan 传输

Infinispan 子系统依赖于 JGroups 子系统在节点之间传输缓存数据。JGroups 使用 UDP 作为默认传输协议,这是由 JGroups 子系统中的default-stack属性定义的:

<subsystem  default-stack="udp">
    ...
</subsystem>

然而,你可以为每个缓存容器配置不同的传输。如果你想将 TCP 作为 Web 缓存容器的传输协议,则可以添加stack属性并将其设置为tcp

<cache-container name="web" default-cache="dist">
  <transport lock-timeout="60000" stack="tcp"/>
</cache-container>

默认的 UDP 传输通常适用于大型集群。如果你正在使用复制或失效,它也可能适用,因为它可以最小化打开过多的套接字。

要了解 TCP 和 UDP 之间的区别,请参阅此外部链接:www.skullbox.net/tcpudp.php

配置 Infinispan 线程

重要的是要注意,在 WildFly 8 中,线程池子系统已被弃用。在 WildFly 9 中,它很可能将被完全移除。本节中的配置仍可用于 WildFly 8,但你需要将线程子系统添加到你的配置文件中。请看以下代码:

<extensions>
    ...
    <extension module="org.jboss.as.threads"/>
</extensions>

就像你可以为 JGroups 传输外部化你的 Infinispan 线程配置一样,你可以将其移动到线程池子系统。以下线程池可以按缓存容器配置:

Thread pool 描述
transport 这给出了负责在网络中传输数据的有限线程池的大小
listener-executor 这给出了用于注册和接收某些缓存事件发生时的通知的线程池的大小
replication-queue-executor 这给出了用于复制缓存数据的预定复制执行器的大小
eviction-executor 这给出了用于定期运行驱逐清理任务的预定执行器服务的大小

在某些情况下可能需要自定义线程池,例如,你可能想应用缓存复制算法。你可能需要选择用于复制数据线程的数量。在以下示例中,我们通过定义最大 25 个线程用于有界队列线程池和 5 个线程用于复制数据来外部化cache-container的线程池:

<subsystem >

  <cache-container name="web" default-cache="repl" listener-executor="infinispan-listener" eviction-executor="infinispan-eviction" replication-queue-executor="infinispan-repl-queue">
    <transport executor="infinispan-transport"/>
  </cache-container>
</subsystem>
...
<subsystem >
  <thread-factory name="infinispan-factory" priority="1"/>
  <bounded-queue-thread-pool name="infinispan-transport"/>
    <core-threads count="1"/>
    <queue-length count="100000"/>
    <max-threads count="25"/>
    <thread-factory name="infinispan-factory"/>
  </bounded-queue-thread-pool>
  <bounded-queue-thread-pool name="infinispan-listener"/>
    <core-threads count="1"/>
    <queue-length count="100000"/>
    <max-threads count="1"/>
    <thread-factory name="infinispan-factory"/>
  </bounded-queue-thread-pool>
  <scheduled-thread-pool name="infinispan-eviction"/>
    <max-threads count="1"/>
    <thread-factory name="infinispan-factory"/>
  </scheduled-thread-pool>
  <scheduled-thread-pool name="infinispan-repl-queue"/>
    <max-threads count="5"/>
    <thread-factory name="infinispan-factory"/>
  </scheduled-thread-pool>
</subsystem>

集群消息子系统

我们将通过讨论消息子系统来结束本章。

WildFly 中使用的 JMS 提供者是 HornetQ。为了共享消息处理负载,HornetQ 服务器可以一起组成一个集群。集群中的每个活动节点都包含一个活动的 HornetQ 服务器。HornetQ 管理自己的消息并处理自己的连接。在幕后,当一个节点与其他节点建立集群连接时,它们之间会创建一个核心桥接连接。一旦建立连接,消息就可以在各个节点之间流动。

如果在 HornetQ 中定义了一个或多个 cluster-connection 元素,则会自动启用集群。以下示例取自默认的 full-ha 配置文件:

<subsystem >
    <hornetq-server>
        ...
        <cluster-connections>
             <cluster-connection name="my-cluster">
                 <address>jms</address>
                 <connector-ref>http-connector</connector-ref>
                 <discovery-group-ref discovery-group-name="dg-group1"/>
             </cluster-connection>
        </cluster-connections>
    </hornetq-server>
</subsystem>

现在,让我们看看如何配置 cluster-connection。以下是一个典型的集群连接配置。您可以选择更新默认的 cluster-connection,或者您可以在 <hornetq-server> 定义中添加自己的 cluster-connection 元素。

<subsystem >
    <hornetq-server>
        ...
        <cluster-connections>
             <cluster-connection name="my-cluster">
                 <address>jms</address>
                 <connector-ref>http-connector</connector-ref>
                 <discovery-group-ref discovery-group-name="dg-group1"/>
                 <retry-interval>500</retry-interval>
                 <forward-when-no-consumers>false</forward-when-no-consumers>
                 <max-hops>1</max-hops>
             </cluster-connection>
        </cluster-connections>
    </hornetq-server>
</subsystem>

cluster-connectionname 属性显然定义了集群连接的名称,这是我们将要配置的。在您的消息子系统中可以配置零个或多个集群连接。

address 元素是一个必填参数,它确定消息如何在集群中分配。在这个例子中,集群连接将只负载均衡发送到以 jms 开头的地址的消息。实际上,这个集群连接将应用于所有 JMS 队列和主题订阅。这是因为它们映射到以子字符串 jms 开头的核心队列。

connector-ref 元素引用了在消息子系统的 connectors 部分中定义的连接器。在这种情况下,我们使用的是 http 连接器(有关可用连接器的更多信息,请参阅第三章 和 随机 (org.hornetq.api.core.client.loadbalance.RandomConnectionLoadBalancingPolicy)。您还可以通过实现 org.hornetq.api.core.client.loadbalance.ConnectionLoadBalancingPolicy 接口添加自己的策略。

以下示例显示了如何为连接工厂使用随机策略:

<connection-factory name="InVmConnectionFactory">
    ...
    <connection-load-balancing-policy-class-name>org.hornetq.api.core.client.loadbalance.RandomConnectionLoadBalancingPolicy
    </connection-load-balancing-policy-class-name>
</connection-factory>

最后,可选的max-hops值设置为1(默认),这是消息在节点之间可以转发最大次数。值为1表示消息仅被负载均衡到直接连接到此服务器的其他 HornetQ 服务器。HornetQ 还可以配置为将消息负载均衡到间接连接到它的节点,即其他 HornetQ 服务器是链中的中介。

小贴士

您还可以参考jboss-as-messaging_2_0.xsd以获取可用参数的完整列表。这可以在您的服务器发行版的JBOSS_HOME/docs/schema文件夹中找到。

配置消息凭据

如果您尝试启动使用full-ha配置文件的集群,您将在控制台看到以下错误日志:

ERROR [org.hornetq.core.server] (default I/O-1) HQ224018: Failed to create session: HornetQClusterSecurityException[errorType=CLUSTER_SECURITY_EXCEPTION message=HQ119099: Unable to authenticate cluster user: HORNETQ.CLUSTER.ADMIN.USER]

这是因为,当尝试在节点之间建立连接时,HornetQ 使用集群用户和集群密码。正如您在默认配置中看到的,您需要更新密码值:

<subsystem >
    <hornetq-server>
        <cluster-password>${jboss.messaging.cluster.password:CHANGE ME!!}</cluster-password>
        <journal-file-size>102400</journal-file-size>
        ...
    </hornetq-server>
</subsystem>

一旦您更改了此密码,启动您的集群,您应该会看到节点之间成功建立桥接:

配置消息凭据

在您的应用程序中配置集群

我们现在将通过查看如何对以下内容进行集群来完成我们对集群系统的探索:

  • 会话 Bean

  • 实体

  • Web 应用程序

集群会话 Bean

在第三章 配置企业服务 中,我们讨论了无状态会话 BeanSLSB)、有状态会话 BeanSFSB)和单例会话 Bean之间的区别。

SLSB 无法在调用之间保留状态,因此集群 SLSB 的主要好处是在服务器数组之间平衡负载:

@Stateless
@Clustered
public class ClusteredBean {
   public void doSomething() {
   // Do something
   }
}

如果您想进一步专业化您的 SLSB,那么您可以选择用于在您的 EJB 之间分配负载的负载均衡算法。以下是为您的 SLSB 提供的可用负载均衡策略:

负载均衡策略 描述
RoundRobin 这是默认的负载均衡策略。智能代理以固定顺序遍历 WildFly 服务器实例列表。
RandomRobin 在此策略下,每个请求都由智能代理重定向到集群中的随机节点。
FirstAvailable 它意味着对节点的随机选择,但后续调用将坚持使用该节点,直到节点失败。下一个节点将再次随机选择。
FirstAvailableIdenticalAllProxies 这与FirstAvailable相同,但随机节点选择将由所有动态代理共享。

然后,您可以根据以下示例应用负载均衡策略:

@Clustered(loadBalancePolicy="FirstAvailable")

在 JBoss AS 7 中,您需要使用@Clustered注解您的 SFSB 以复制 SFSB 的状态。在 WildFly 中,情况并非如此,因为 SFSB 默认配置为启用钝化。这意味着只要您使用@Stateful注解您的 bean,并且您使用的是支持高可用性的服务器配置文件,您的 SFSB 的状态将在服务器之间进行复制。请看以下代码:

@Stateful
public class ClusteredBean {
  public void doSomething() {
  // Do something
  }
}

要禁用钝化/复制,您只需将passivationCapable设置为false,如下所示:

@Stateful(passivationCapable=false)
public class ClusteredBean {
   public void doSomething() {
   // Do something
   }
}

默认情况下,SFSBs 使用名为ejb的缓存容器,该容器会在所有节点之间复制会话。如果在会话运行期间应用程序服务器节点失败,EJB 代理将检测到这一点,并选择另一个已复制会话数据的节点。然而,您可以使用@org.jboss.ejb3.annotation.CacheConfig注解引用您的 SFSB 使用的自定义缓存容器。请看以下代码:

@Stateful
@CacheConfig(name="custom-ejb")
public class ClusteredBean {
  ...
}

以下是一个使用分布式缓存的对应缓存容器:

<cache-container name="custom-ejb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan" aliases="sfsb">
    <distributed-cache name="dist" batching="true" mode="ASYNC" owners="3">
        <locking isolation="REPEATABLE_READ"/>
        <file-store/>
    </distributed-cache>
</cache-container>

实体聚类

由于实体位于后端深处,因此不需要考虑负载均衡逻辑或会话复制。然而,缓存您的实体以避免往返数据库是有用的。WildFly 中的 EJB3 持久层实现是 Hibernate 4.3.5。Hibernate 框架包括一个复杂的缓存机制,该机制在 Session 级别和 SessionFactory 级别都得到了实现。

在 Session 级别使用的缓存称为一级缓存,并且只有会话作用域。此缓存在关闭使用它的 Hibernate 会话时立即清除。Hibernate 使用二级缓存来存储从数据库检索的实体或集合。它还可以存储最近查询的结果。正是这个二级缓存需要我们进行集群,因为这个缓存被跨会话使用。

为您的企业应用程序启用二级缓存相对简单。如果您正在使用 JPA,那么您只需将以下内容添加到您的persistence.xml配置文件中即可启用二级缓存:

<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>

    <property name="hibernate.cache.use_second_level_cache" value="true"/>
    <property name="hibernate.cache.use_minimal_puts" value="true"/>
</properties>

第一个元素shared-cache-mode是 JPA 2.x 指定持久单元的实体及其相关状态是否将被缓存的方式。shared-cache-mode元素有五个可能的值,如下表所示:

共享缓存模式 描述
ALL 此值导致所有实体及其相关状态和数据都被缓存。
NONE 此值导致持久单元的缓存被禁用。
ENABLE_SELECTIVE 此值允许在实体类上指定@Cacheable注解时进行缓存。
DISABLE_SELECTIVE 此值启用缓存,并导致除了那些指定了@Cacheable(false)的实体之外的所有实体都被缓存。

命名为 hibernate.cache.use_minimal_puts 的属性通过减少缓存中的写入量来对二级缓存进行一些优化,这以增加额外的读取为代价。当对实体进行集群时,这很有益处,因为 put 操作非常昂贵,因为它激活了缓存复制监听器。

此外,如果你计划在你的应用程序中使用 Hibernate 查询缓存,你需要通过一个单独的属性来激活它,如下所示:

<property name="hibernate.cache.use_query_cache" value="true"/>

为了完整性,我们还将包括使用 Infinispan 作为原生 Hibernate 应用程序缓存提供者的配置。这是你必须添加到你的 hibernate.cfg.xml 中的属性列表:

<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory"/>
<property name="hibernate.cache.infinispan.cachemanager" value="java:CacheManager/entity"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.use_minimal_puts" value="true"/>

如你所见,配置更加详细,因为你必须告诉 Hibernate 使用 Infinispan 作为缓存提供者。这需要使用 hibernate.transaction.factory_class 属性设置正确的 Hibernate 事务工厂。

hibernate.cache.infinispan.cachemanager 属性暴露了 Infinispan 使用的缓存管理器。默认情况下,Infinispan 将负责二级缓存的缓存管理器绑定到 JNDI 名称 java:CacheManager/entity

最后,hibernate.cache.region.factory_class 属性告诉 Hibernate 使用 Infinispan 的二级缓存集成,该集成使用 CacheManager,如之前定义的,作为 Infinispan 缓存实例的来源。

缓存实体

除非你已将 shared-cache-mode 设置为 ALL,否则 Hibernate 不会自动缓存你的实体。你必须选择哪些实体或查询需要被缓存。这绝对是最安全的选项,因为无差别的缓存可能会损害性能。以下示例展示了如何使用注解为 JPA 实体进行此操作:

import javax.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region ="properties") 

public class Property {

@Id
@Column(name="key")
private String key;

@Column(name="value")
private String value;

// Getter & setters omitted for brevity
}

使用 JPA 注解

@javax.persistence.Cacheable 注解指定此实体类是否应该缓存在二级缓存中。这仅在 shared-cache-mode 未设置为 ALL 时适用。

使用 Hibernate 注解

@org.hibernate.annotations.Cache 注解是用于实现与 @Cacheable 相同目的的较旧注解。你仍然可以使用它来定义 Hibernate 应该使用哪种策略来控制缓存内容的并发访问。

CacheConcurrencyStrategy.TRANSACTIONAL 属性为 Infinispan 的完全事务性 JTA 环境提供支持。

如果你的应用程序数据有可能只读不修改,你可以应用 CacheConcurrencyStrategy.READ_ONLY 属性,该属性不会从缓存中驱逐数据(除非程序化执行):

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

最后,最后一个属性是缓存区域,它定义了实体放置的位置。如果你没有为实体类指定缓存区域,则此类的所有实例都将缓存在 _default 区域中。定义缓存区域可能有助于你想要执行细粒度的缓存区域管理。

缓存查询

查询缓存可用于缓存查询的结果集。这意味着如果再次发出相同的查询,它将不会击中数据库,而是返回缓存的值。

注意

查询缓存不会缓存结果集中实际实体的状态;它只缓存标识值和值类型的结果。

在以下示例中,名为listUsers的查询结果集被配置为使用@NamedQuery注解内的@QueryHint注解进行缓存:

@NamedQueries(
{
@NamedQuery(
name = "listUsers",
query = "FROM User c WHERE c.name = :name",
hints = { @QueryHint(name = "org.hibernate.cacheable", value =
"true") }
)
})
public class User {

@Id
@Column(name="key")
private String key;

@Column(name="name")
private String name;

...
}

注意

过度使用查询缓存可能会降低应用程序的性能,因此请明智地使用它。首先,如果您的查询(作为查询缓存映射中的键存储)由数百个字符组成,查询缓存将增加内存需求。

其次,更重要的是,每次查询的表中发生更改时,查询缓存的结果都会被无效化。这可能导致查询缓存命中率非常低。因此,除非您查询的表很少更新,否则建议关闭查询缓存。

集群 Web 应用程序

集群 Web 应用程序需要最少的努力。正如我们之前提到的,要在 Web 应用程序中启用集群,您只需在web.xml中添加以下指令:

<web-app>
 <distributable/>
</web-app>

默认情况下,集群 Web 应用程序将使用 Infinispan 配置中包含的 Web 缓存。您还可以为每个部署单元设置特定的缓存。这可以通过向jboss-web.xml文件添加replication-config指令并指定要使用的缓存名称来实现:

<jboss-web>
  <replication-config>
 <cache-name>web.dist</cache-name>
  </replication-config>
</jboss-web>

之前的配置显然应该引用主配置文件中定义的缓存:

<cache-container name="web" default-cache="repl">
   <alias>standard-session-cache</alias>

   <distributed-cache mode="ASYNC" name="web.dist" batching="true">
        <locking isolation="REPEATABLE_READ"/>
        <file-store/>
   </distributed-cache>
</cache-container>

摘要

在本章中,我们探讨了围绕集群的大量配置选项。有很多信息需要吸收,但总的来说,我们将提到以下关键点。

WildFly 集群可以由独立节点组成,或者作为服务器域的一部分。集群子系统在standalone-ha.xmlstandalone-full-ha.xml配置文件中定义。

集群需要三个主要组件:JGroups、Infinispan 和消息传递。JGroups 提供集群节点之间的通信。默认情况下,JGroups 使用 UDP 多播消息来处理集群生命周期事件。

在企业应用程序中,需要配置多个缓存以实现数据的一致性。WildFly 默认配置了四个缓存容器。这些是单例会话 Bean 集群cache-container、SLSB cache-container、Web cache-container和 Hibernate cache-container

单例集群(服务器)cache-container被配置为在集群节点间复制单例会话 bean 数据。SFSB 的(ejb)cache-container被配置为在集群节点间复制状态化会话 bean 数据。Web cache-container被配置为在集群节点间复制 HTTP 会话数据。Hibernate cache-container通过定义一个local-query策略来处理本地实体,采用了一种更复杂的方法。当数据更新且其他集群节点需要被通知时,会使用invalidation-cache。最后,使用replicated-cache来复制查询时间戳。

最后,我们查看了一下消息子系统,该子系统可以通过定义一个cluster-connection元素轻松地进行集群化。这将导致消息在您的 JMS 服务器之间透明地负载均衡。

在下一章中,我们将探讨负载均衡,这是配置高可用性的故事的一半。

第九章:負載均衡 Web 應用程序

在上一章中,我们介绍了如何对 Web 应用程序进行聚类的基礎概念。然而,這只是故事的一部分。為了進一步提高可用性,我們需要考慮如何進行 WildFly 服務器的負載均衡。

負載均衡是在托管相同應用程序內容的服務器之間分配進來的流量。負載均衡通過確保任何單個服務器不會承擔過多的負擔,並在單個服務器故障時保持應用程序的可用性來提高應用程序的可用性。

历史上,JBoss AS 從 Tomcat 繼承了負載均衡庫,Tomcat 是應用服務器網絡模塊的一部分。網絡模塊使用mod_jk(一個 Apache 模塊)將 Tomcat 連接到 Web 服務器,如 Apache。對於那些不熟悉 Tomcat 和 Apache 的您,Tomcat(也稱為 Apache Tomcat)是一個開源 servlet 容器,而 Apache(也稱為 Apache2 或 Apache HTTPD)是一個 HTTP 網絡服務器。

尽管您仍然可以使用mod_jk將 Undertow 連接到 Web 服務器,但您應該考慮使用mod_cluster API。mod_cluster API 是一個基於 HTTPD 的負載均衡器,它相比mod_jk有許多優勢,例如性能和可靠性的提升。我們將在本章中涵蓋mod_jkmod_cluster的安裝。

在這個簡短的介紹之後,我們將介紹在您的 Web 應用程序前面使用 Web 服務器的優勢。然後我們將繼續介紹以下主題:

  • 使用mod_jkmod_proxy將 WildFly 連接到 Apache

  • 使用mod_cluster API 將 WildFly 連接到 Apache

使用 Apache Web 服務器與 WildFly 的優勢

在大多數實際情況中,您會發現 Apache Web 服務器作為您的應用服務器的進入點。這種做法的一些優勢包括:

  • 速度:Apache 在服務靜態內容方面通常更快。

  • 安全性:將 WildFly 放在 Apache 後面,您僅需要關注單一進入點的連接。WildFly 可以配置為只接受來自單一 IP(托管 Apache 的服務器)的連接,並不會直接從互聯網訪問。從根本上說,Apache 成為了一個智能代理服務器。

  • 負載均衡和集群:使用 Apache 作為前端,您可以將流量分發到多個 WildFly 服務器實例。如果您的某個服務器出現故障,通訊將透明地繼續到集群中的另一個節點。

如前所述,連接 Apache 和 WildFly 可以通過兩種方式進行:使用 Tomcat 的mod_jk庫或 Apache 的mod_proxy庫。由於mod_jkmod_proxy的安裝與早期的 AS 版本無異,我們將只提供一個快速設置指南供您參考。

然而,如果您计划设置一个高性能、动态的 Web 服务器集群,您应该考虑迁移到较新的mod_cluster API,这在本章的后续部分中讨论。

使用 mod_jk 库

mod_jk库是使用 Apache Web 服务器作为 WildFly 前端的一个常见解决方案。所有请求首先到达 Apache Web 服务器。然后,Apache 接受并处理任何静态资源请求,例如 HTML 页面或图形图像的请求。然后,借助mod_jk,Apache 请求动态资源,如 JSP 或 Servlet,到 Undertow。Apache 和 Undertow 之间的通信通过 AJP 协议在网络中发送,如下面的截图所示:

使用 mod_jk 库

安装 Apache

对于实时环境,最常用的操作系统是 Linux。因此,我们将演示如何在 Ubuntu 中安装mod_jk。请注意,根据您使用的 Linux 版本,配置可能略有不同。这些说明适用于 Ubuntu 服务器。

在尝试安装mod_jk之前,您需要安装 Apache。您可以在终端中输入以下命令来安装 Apache:

sudo apt-get update
sudo apt-get install -y apache2

确保在完成安装后您可以看到 Apache 欢迎页面。只需将服务器的 IP 地址作为浏览器中的 URL 输入。请查看以下截图:

安装 Apache

安装 mod_jk

接下来,我们需要安装mod_jk。以下命令将安装模块,启用它,并重新启动 Apache:

sudo apt-get install libapache2-mod-jk

您可以通过输入以下命令来确认模块已被启用。这将列出所有当前启用的模块:

sudo apache2ctl -M

然后,您需要修改默认配置文件default中的虚拟主机,该文件位于/etc/apache2/sites-enabled目录下。配置显示了文件的更新默认配置。以下代码中需要添加的行已突出显示:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost

 JkMount /* loadbalancer
    <Directory />
        Options FollowSymLinks
        AllowOverride None
    </Directory>

     ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

JkMount指令告诉 Apache 它应该将哪些 URL 转发到mod_jk模块,该模块随后将它们转发到 Undertow Web 容器。在上面的示例中,所有 URL 路径为/*的请求都发送到mod_jk连接器。这意味着所有请求都被发送。您也可以将特定的 URL 转发到mod_jk,例如/website*

如果您转发所有 URL,您可能希望卸载一个或两个 URL,以便从 Apache 服务器提供静态数据。这可以通过使用JkUmount指令来实现。例如,如果您希望 Apache 在images目录中提供静态媒体文件,您将有一个如下所示的配置:

<VirtualHost *:80>
        ServerAdmin webmaster@localhost

        JkMount /* loadbalancer
 JkUmount /images/* loadbalancer

        <Directory />
                Options FollowSymLinks
                AllowOverride None
         </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

接下来,你需要配置mod_jk工作器文件,即workers.properties文件,该文件位于/etc/libapache2-mod-jk文件夹中。此文件指定了 Undertow Web 服务器的 IP 地址,在这些服务器之间进行负载均衡。可选地,你可以添加一个配置,指定如何在每个服务器之间进行负载均衡。对于双节点设置,文件将如下所示:

# Define worker list
worker.list=loadbalancer,jkstatus
# Set properties for worker1 (ajp13)
worker.worker1.type=ajp13
worker.worker1.host=192.168.0.1
worker.worker1.port=8009  

# Set properties for worker2 (ajp13)
worker.worker2.type=ajp13
worker.worker1worker12.host=192.168.0.2
worker.worker1worker12.port=8009
worker.worker1.lbfactor=1
worker.loadbalancer.type=lb
worker.loadbalancer.balance_workers=worker1,worker2

workers.properties文件中,每个节点使用worker.[n]命名约定定义,其中n代表你为每个 Web 服务器容器选择的任意名称。对于每个工作器,你必须指定 Web 服务器中运行的 AJP13 连接器的主机名(或 IP 地址)和端口号。

负载均衡器类型lb表示工作器执行带粘性会话的加权轮询负载均衡。

你需要在修改worker.properties文件后重启 Apache。看看以下命令:

sudo /etc/init.d/apache2 restart

在 WildFly 中,ha配置文件中的默认配置已经定义了 AJP 连接器。如果你没有使用ha配置文件之一,例如,你正在使用standalone.xml,你需要添加以下高亮显示的行:

<subsystem >
    <buffer-cache name="default"/>
    <server name="default-server">
 <ajp-listener name="ajp" socket-binding="ajp"/>
        <http-listener name="default" socket-binding="http"/>
        ....
    </server>
</subsystem>

AJP 连接器也在socket-binding-group元素中定义了ha配置文件。同样,对于非ha配置文件,你需要 AJP 配置。在下面的代码片段中,你可以看到 AJP 连接器正在监听端口号 8009。看看以下代码:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="0">
    <socket-binding name="management-http" interface="management" port="9990"/>
    <socket-binding name="management-https" interface="management" port="9993"/>
 <socket-binding name="ajp" port="8009"/>
    ...
</socket-binding-group>

一旦设置好此配置,你应该刷新显示 Apache 欢迎页面的页面。现在它将显示 WildFly 欢迎页面。这证明了 Apache 正在将请求定向到 WildFly。停止一个 WildFly 服务器,再次刷新页面。你将看到 WildFly 欢迎页面,因为现在所有请求都被定向到另一个 WildFly 服务器。

配置 mod_proxy

在 WildFly 中,支持一个名为mod_proxy的可选模块。安装后,它可以配置为 Apache 充当代理服务器。这可以用来将请求转发到特定的 Web 应用服务器,如 WildFly,而无需配置如mod_jk的 Web 连接器。

要安装和启用mod_proxy,你需要运行以下命令:

sudo apt-get install libapache2-mod-proxy-html
sudo a2enmod proxy-http

然后,你需要将这些两个指令包含在你的默认站点文件中。你需要为每个你希望转发到 WildFly 的 Web 应用程序这样做,例如,为了转发一个上下文路径为/app的应用程序。看看以下代码:

ProxyPass         /app  http://localhost:8080/app
ProxyPassReverse  /app  http://localhost:8080/app

这告诉 Apache 将匹配http://localhost/app/*的 URL 转发到监听在端口 8080 的 WildFly HTTP 连接器。

配置 mod_proxy

如前图所示,Apache 的mod_proxy是基于 TCP 的,并使用 HTTP,因此您不需要在 WildFly 配置中添加其他任何内容。除此之外,还有对另一个模块mod_proxy_ajp的支持。这个模块可以像mod_proxy一样使用,只是它使用 AJP 协议将 Apache 请求代理到 WildFly。在您可以使用它之前,您需要按照以下方式启用它:

sudo a2enmod proxy-ajp

然后,将高亮显示的行添加到默认站点文件中的虚拟主机中:

<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html

 ProxyPass / ajp://localhost:8009/
 ProxyPassReverse / ajp://localhost:8009/

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

在这里,我们只是将所有流量(/)重定向到监听在 localhost 端口 8009 的 Web 服务器。请看以下截图:

配置 mod_proxy

再次,如果您使用的是非 ha 配置文件,您需要将 AJP 监听器添加到您的 Undertow 配置中,如下所示:

<subsystem >
    <buffer-cache name="default"/>
    <server name="default-server">
 <ajp-listener name="ajp" socket-binding="ajp"/>
        <http-listener name="default" socket-binding="http"/>
        ....
    </server>
</subsystem>

然后,您需要将 AJP 端口添加为 socket-binding 元素,如下所示:

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="0">
     <socket-binding name="management-http" interface="management" port="9990"/>
     <socket-binding name="management-https" interface="management" port="9993"/>
 <socket-binding name="ajp" port="8009"/>
    ....
</socket-binding-group>

使用 mod_cluster 进行负载均衡

mod_cluster是一个基于 HTTP 的负载均衡器,类似于mod_jk,可以用来将请求转发到一组应用服务器实例。使用mod_cluster相较于mod_jkmod_proxy有以下几个优点:

  • 动态聚类配置

  • 由于能够使用服务器端负载指标,因此具有更好的负载均衡能力

  • 与应用程序生命周期有更好的集成

  • AJP 是可选的

当使用标准负载均衡器,如mod_jk时,您必须提供一个静态节点列表,用于分配负载。这个过程不方便,尤其是如果您想根据应用程序周围的流量动态添加或移除节点。此外,使用平面集群配置可能会很繁琐且容易出错,尤其是如果您集群中的节点数量很多。

当使用mod_cluster时,节点会动态地添加到或从您的集群中移除。为了实现这一点,每个 WildFly 服务器都会将其生命周期状态通知 Apache。

Apache 在多播组上发送 UDP 消息,所谓的广告。集群中的每个 WildFly 服务器都会订阅这个组。WildFly 就是通过这个组来了解 HTTP 代理(在这个例子中是 Apache)。然后,每个 WildFly 实例会通知 HTTP 代理它们的可用性,然后代理将它们添加到节点列表中。如果 WildFly 服务器被移除,组中的其他 WildFly 服务器将会收到通知。

下面的图示有助于说明这一概念:

使用 mod_cluster 进行负载均衡

mod_cluster的另一个关键特性在于负载指标。负载指标在服务器端确定,然后根据情况变化发送到 Apache 端。因此,mod_cluster提供了一个比传统基于 HTTPD 的负载均衡器更为稳健的架构,后者在代理上静态地持有指标。

小贴士

关于服务器端负载指标如何计算的信息,请参阅mod_cluster文档中的docs.jboss.org/mod_cluster/1.2.0/html/java.load.html

使用mod_cluster的另一个优点是能够拦截生命周期事件,例如卸载和重新部署。如本节之前所述,这些事件在 Apache 和集群中的节点之间是同步的。

安装 mod_cluster 库

在安装和配置mod_cluster时,有两个需要考虑的事项。第一个涉及 WildFly 配置,第二个涉及下载和安装mod_cluster库到 Apache。我们将首先查看 WildFly,因为它已经预配置,然后继续到mod_cluster的安装。

在你的 WildFly 安装中捆绑的,你会找到 mod_cluster 1.3.0 模块。此子系统作为集群配置的一部分包含在standalone-ha.xmldomain.xml配置文件中,如下所示:

<subsystem >
    <mod-cluster-config advertise-socket="modcluster" connector="ajp">
        <dynamic-load-provider>
            <load-metric type="cpu"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

上述默认配置通过advertise-socket元素引用其socket-binding

<socket-binding name="modcluster" port="0" multicast-address="224.0.1.105" multicast-port="23364"/>

此外,请注意,默认配置使用 AJP 协议。connector属性引用了mod_cluster反向代理将要连接到的 Undertow 监听器的名称。以下是有ajp-listener高亮的 Undertow 配置:

<subsystem >
    <buffer-cache name="default"/>
    <server name="default-server">
 <ajp-listener name="ajp" socket-binding="ajp"/>
        <http-listener name="default" socket-binding="http"/>
        ....
    </server>
</subsystem>

你还需要确保你的接口配置正确,指向你的 WildFly 服务器(们)运行的 IP 地址。更新你的hosts.xmlstandalone-ha.xml文件,用你的服务器的 IP 地址替换 IP:

<interfaces>
    <interface name="management">
        <inet-address value="178.62.50.168"/>
    </interface>
    <interface name="public">
        <inet-address value="178.62.50.168"/>
    </interface>
</interfaces>

现在让我们继续到第二部分——在 Apache 中安装和配置mod_cluster

如果你还没有安装 Apache,你应该遵循本章前面的说明(见安装 Apache部分)。我们首先需要安装所需的 Apache 模块。这些模块用于与 WildFly 上的mod_cluster交互。

在此示例中,我们使用 Apache 2.2,它需要mod_cluster的 1.2.x 版本。如果你使用 Apache 2.4,则可以使用mod_cluster的较新版本,即 1.3.x 版本。你也可以使用 1.2.x 版本,但需要为 Apache 2.4 编译。要查看你的 Apache 版本,请运行以下命令:

apache2ctl -V

小贴士

在撰写本文时,1.3.x 的二进制文件尚未从下载网站提供,因此你可能需要从源代码编译它们(github.com/modcluster/mod_cluster/tree/master)。

在决定编译源代码之前,请先检查下载网站。如果你希望编译模块,你应该检查www.openlogic.com/blog/bid/247607/JBoss-AS7-Clustering-Using-mod_cluster-and-http-2-4-Part-1

访问下载网站 (mod-cluster.jboss.org/downloads),并选择您平台上的二进制文件。选择 httpd 的 mod_cluster 模块

安装 mod_cluster 库

下载完二进制文件后,您需要将其提取到 Apache 的 module 目录中。当您提取下载的存档时,应该看到以下文件:

  • mod_advertise.so

  • mod_manager.so

  • mod_proxy_cluster.so

  • mod_slotmem.so

您可以使用以下命令来完成此操作。第一个命令使用下载页面中的 URL 从 mod_cluster 网站下载文件。第二个命令提取 TAR 文件,最后一个命令将库复制到 modules 目录。

wget -c http://downloads.jboss.org/mod_cluster/1.2.6.Final/linux-x86_64/mod_cluster-1.2.6.Final-linux2-x64-so.tar.gz
tar -xzvf mod_cluster-1.2.6.Final-linux2-x64-so.tar.gz
cp ./*.so /usr/lib/apache2/modules

mod_cluster 配置

接下来,我们需要在 /etc/apache2/mods-available 目录下创建两个文件。第一个文件名为 mod_cluster.load,其中包含该模块所依赖的库列表。以下是该文件的完整内容:

LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_ajp_module /usr/lib/apache2/modules/mod_proxy_ajp.so
LoadModule slotmem_module /usr/lib/apache2/modules/mod_slotmen.so

LoadModule manager_module /usr/lib/apache2/modules/mod_manager.so
LoadModule proxy_cluster_module /usr/lib/apache2/modules/mod_proxy_cluster.so
LoadModule advertise_module /usr/lib/apache2/modules/mod_advertise.so

备注

在 1.3.x 版本中,slotmen 模块名称已从 mod_slotmen.so 更改为 mod_cluster_slotmen.so

此列表包含我们刚刚复制到 module 文件夹中的四个库和现有的 mod_proxy 库。

这些模块中的每一个都在负载均衡功能中扮演着特定的角色。核心模块是 mod_proxymod_proxy_ajpmod_proxy_http。它们使用 HTTP/HTTPS 协议或 AJP 协议将请求转发到集群节点。

接下来,mod_manager 是一个模块,它从 WildFly 读取信息,并与 mod_slotmem 一起更新共享内存信息。mod_proxy_cluster 是包含 mod_proxy 负载均衡器的模块。最后,mod_advertise 是一个额外的模块,允许 HTTPD 通过多播数据包、IP 地址和 mod_cluster 监听的端口号进行广告。

下一个我们需要创建的文件名为 mod_cluster.conf。该文件位于 /etc/apache2/mods-available 目录中,与 mod_cluster.load 文件并列,如下所示:

CreateBalancers 1

<IfModule manager_module>
    Listen 127.0.1.1:6666
    ManagerBalancerName mycluster

    <VirtualHost 127.0.1.1:6666> 
        KeepAliveTimeout 300
        MaxKeepAliveRequests 0
        AdvertiseFrequency 5
        ServerAdvertise On
        EnableMCPMReceive

        <Location />
            Order deny,allow
            Allow from 127.0.1
        </Location>

    </VirtualHost>
</IfModule>

您必须将 127.0.1.1 IP 地址替换为 WildFly 用于连接 Apache 的 IP 地址。如果您的 Apache 和 WildFly 在不同的服务器上,那么它将是您的 Apache 服务器 IP。您还需要将端口号 6666 更新为您想要与 WildFly 通信的端口号。

根据当前的配置,Apache 虚拟主机允许来自以下请求:

  • 前缀为 127.0.1 的 IP 地址

  • 子网络 127.0.1.0/24

CreateBalancers 指令配置了如何在虚拟主机中创建 HTTP 均衡器。CreateBalancers 的可能值为 012,如下所述:

  • 0:在所有虚拟主机中创建均衡器

  • 1:不创建任何均衡器

  • 2:仅为主服务器创建均衡器

备注

CreateBalancers 设置为 1 意味着你必须在 ProxyPass 指令中配置一个均衡器(在章节中进一步展示)。更多信息,请参阅此链接:docs.jboss.org/mod_cluster/1.2.0/html/native.config.html#d0e485

KeepAliveTimeout 指令允许在 300 秒内重用相同的连接。由于我们将 MaxKeepAliveRequests 设置为 0,因此每个连接的请求数量是无限制的。ManagerBalancerName 指令提供了你集群的均衡器名称(默认为 mycluster)。

对我们来说最重要的是 ServerAdvertise 指令。它使用广告机制告诉 WildFly 应该向谁发送集群信息。

你还可以使用 AdvertiseFrequency 指令来细化多播广告消息之间的时间间隔,默认为 10 秒。

小贴士

广告机制的概述

用于广告的默认多播 IP 地址和端口是 224.0.1.105:23364。这些值与以下名为 modcluster 的 WildFly 绑定定义相匹配:

<socket-binding name="modcluster" port="0" multicast-address="224.0.1.105" multicast-port="23364"/>

如果你更改了 WildFly 中的这些值,你还需要在 HTTPD 端使用 AdvertiseGroup 指令进行匹配:

AdvertiseGroup 224.0.1.105:23364

你需要配置的最后一件事情是在你的站点配置文件中的虚拟主机。在 /etc/apache2/sites-enabled 目录下创建一个名为 wildfly 的文件。添加以下高亮显示的代码行:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost

 ProxyPass / balancer://mycluster stickysession=JSESSIONID|jsessionid nofailover=On
 ProxyPassReverse / balancer://mycluster

    <Location />
        Order deny,allow
        Allow from All
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

最后一点,如果你还没有启用 proxy_ajpproxy_http,你需要这样做以便 mod_cluster 能够工作,如下所示:

a2enmod proxy proxy_ajp proxy_http

你现在可以启用 WildFly 站点:

sudo a2ensite wildfly

现在,启用 mod_cluster 模块并重启 Apache:

sudo a2enmod mod_cluster
sudo /etc/init.d/apache2 restart

测试 mod_cluster

为了验证一切是否正常工作,启动你的 WildFly 域,确保你的服务器组正在使用 ha 配置文件。将我们在 第四章 中使用的应用程序部署到同一个服务器组,即 The Undertow Web Server。如果一切配置正确,当你导航到 http://178.62.50.168/chapter4 的上下文根 chapter4 时,你应该能看到该应用程序。

测试 mod_cluster

通过 CLI 管理 mod_cluster

有一些工具可以用来管理和从你的集群中检索运行时信息。你的第一个选择是命令行管理界面,它允许你调查 mod_cluster 子系统。

你需要学习的第一个命令是 list-proxies,它仅返回已连接代理的主机名(和端口):

[domain@localhost:9990 /] /host=master/server=server-one/subsystem=modcluster:list-proxies
{
 "outcome" => "success",
 "result" => ["apache-wildfly:6666"]
}

虽然这可以用于快速检查你的集群成员,但你可以使用 read-proxies-info 命令获取更详细的信息,该命令实际上会向 HTTPD 服务器发送信息消息:

[domain@localhost:9990 /] /host=master/server=server-one/subsystem=modcluster:read-proxies-info
{
 "outcome" => "success",
 "result" => [
 "apache-wildfly:6666",
 "Node: [1],Name: master:server-two,Balancer: mycluster,LBGroup: ,Host: 178.62.50.168,Port: 8159,Type: ajp,Flushpackets: Off,Flushwait: 10,Ping: 10,Smax: 26,Ttl: 60,Elected: 0,Read: 0,Transfered: 0,Connected: 0,Load: 97
Node: [2],Name: master:server-one,Balancer: mycluster,LBGroup: ,Host: 178.62.50.168,Port: 8009,Type: ajp,Flushpackets: Off,Flushwait: 10,Ping: 10,Smax: 26,Ttl: 60,Elected: 0,Read: 0,Transfered: 0,Connected: 0,Load: 98
Vhost: [1:1:1], Alias: localhost
Vhost: [1:1:2], Alias: default-host
Vhost: [2:1:3], Alias: localhost
Vhost: [2:1:4], Alias: default-host
Context: [1:1:1], Context: /chapter4, Status: ENABLED
Context: [2:1:2], Context: /chapter4, Status: ENABLED
"
 ]
}

mod_cluster 子系统还允许我们使用 read-proxies-configuration 命令,该命令提供了有关您的集群的更多详细信息。为了简洁起见,我们将省略其输出的打印。

您的集群中包含的代理列表也可以使用 CLI 进行修改。例如,您可以使用 add-proxy 命令添加一个未被 mod_cluster 的 httpd 配置捕获的代理。请查看以下代码:

[domain@localhost:9990 /] /host=master/server=server-one/subsystem=modcluster:add-proxy(host=192.168.0.11, port=9999)
{
 "outcome" => "success"
}

您也可以使用相应的 remove-proxy 命令从列表中移除代理:

[domain@localhost:9990 /] /host=master/server=server-one/subsystem=modcluster:remove-proxy(host=192.168.0.11, port=9999)
{
 "outcome" => "success"
}

使用 CLI 管理您的 Web 上下文

您可以使用 CLI 来管理您的 Web 上下文。例如,可以使用 enable-context 命令告诉 Apache 某个特定的 Web 上下文能够接收请求,如下所示:

[standalone@localhost:9990 subsystem=modcluster] :enable-context(context=/app, virtualhost=default-host)
{"outcome" => "success"}

相应的 disable-context 命令可以用来防止 Apache 发送 请求:

[standalone@localhost:9990 subsystem=modcluster] :disable-context(context=/app, virtualhost=default-host)
{"outcome" => "success"}

要停止 Apache 从 Web 上下文中发送请求,您可以使用 stop-context 命令,如下所示:

[standalone@localhost:9990 subsystem=modcluster] :stop-context(context=/app, virtualhost=default-host, waittime=50)
{"outcome" => "success"}

添加原生管理功能

如果您无法(或者只是不想)使用 CLI,那么您也可以配置 Apache 网络服务器通过浏览器提供基本的管理界面。

为了做到这一点,您只需要添加 mod_cluster_manager 应用程序上下文,如下所示:

<Location /mod_cluster_manager>
       SetHandler mod_cluster-manager
       Order deny,allow
       Deny from all
       Allow from 192.168.10
</Location>

您可以通过导航到 http://192.168.10.1/http://192.168.10.1/mod_cluster_manager 来测试您的 mod_cluster 管理员应用程序。

在我们的示例中,mod_cluster 管理员显示了通过多播公告发现的全部 WildFly 节点的信息。请查看以下截图:

添加原生管理功能

mod_cluster 管理员页面上,您有很多有用的信息,例如当前活跃的主机数量(在我们的示例中,两个节点)和可用的 Web 上下文。默认情况下,所有 Web 上下文都会自动挂载(不需要像 mod_jk 那样显式挂载),但您可以通过点击旁边的 禁用/启用 链接来排除或包含它们。

使用配置文件管理 Web 上下文

为了完整起见,我们将添加一个可以用来通过您的应用程序服务器配置文件管理 Web 上下文的选项。默认情况下,所有 Web 上下文都是启用的;然而,您可以使用 excluded-contexts 指令从主配置文件中排除 Web 上下文。请查看以下代码:

<subsystem >
    <mod-cluster-config excluded-contexts="ROOT, webapp1"/>
</subsystem>

mod_cluster 故障排除

在 Apache 上安装和启用 mod_cluster 只需几个步骤即可开始工作。然而,如果您遇到问题,可以允许输出详细信息,这将导致显示您的配置概览。将 AllowDisplay 指令添加到您的 mod_cluster_manager 应用程序上下文中,如下所示:

<Location /mod_cluster_manager> 
    SetHandler mod_cluster-manager 
    Order deny,allow 
    Deny from all 
    Allow from 192.168.10 
</Location> 

AllowDisplay On

当添加此指令时,你将获得有关加载到 HTTPD 中的模块的更多信息。此输出可能有助于缩小任何问题,如下面的截图所示:

故障排除 mod_cluster

错误的另一个可能原因是防火墙阻止了广告消息的广播。请记住,广告消息使用 UDP 端口号 23364 和多播地址 224.0.1.105。为了验证广告是否是问题,你可以在 HTTPD 端尝试将其关闭,如下设置:

ServerAdvertise Off

此指令应在应用服务器端通过proxy-list元素匹配。此元素定义了 WildFly 服务器最初将与之通信的 HTTPD 服务器列表:

<mod-cluster-config proxy-list="192.168.10.1:6666">
  ...
</mod-cluster-config>

如果有多个代理,那么proxy-list将包含一个以逗号分隔的列表。

你还可以通过运行一个名为Advertise的测试类来检查mod_cluster是否正确地发布了广告消息,该类可以在github.com/modcluster/mod_cluster/blob/master/test/java/Advertize.java找到。你需要编译该类,然后按照以下方式运行它:

java Advertise 224.0.1.105 23364

如果该模块,即广告,配置正确,你将看到如下命令行显示:

received from /192.168.0.10:23364
received: HTTP/1.0 200 OK
Date: Sat, 26 Jul 2014 20:03:12 GMT
Sequence: 121
Digest: 4dedd3761d451227f36534b63ca2a8a1
Server: b23584e2-314f-404d-8fde-05069bfe5dc7
X-Manager-Address: 127.0.1.1:6666
X-Manager-Url: /b23584e2-314f-404d-8fde-05069bfe5dc7
X-Manager-Protocol: http
X-Manager-Host: 127.0.1.1

最后,别忘了检查 Apache logs目录中的错误日志,看看是否有任何错误。

此外,请确保你已经启用了mod_proxy_http模块,因为没有它mod_cluster将无法工作。

节点间的负载均衡

我们将运行几个测试,以研究mod_cluster如何在几个不同的客户端之间分配负载。

对于这些测试,我们将使用一个非常基本的 Web 应用程序。该应用程序的源代码可以在本书的源代码中找到;项目名为chapter9-balancer。它包含一个简单的index.jsp页面,该页面在控制台上输出一条消息:

<%
Integer counter = (Integer)session.getAttribute("counter");
if (counter == null) {
  session.setAttribute("counter",new Integer(1));
}
else {
  session.setAttribute("counter",new Integer(counter+1));
}
System.out.println("Counter"+session.getAttribute("counter"));          
%>

部署应用程序后,转到 URL http://192.168.0.10/balancer/index.jsp。在发送几个请求后,你会看到每个后续请求都发送到同一服务器。这表明mod_cluster遵循粘性会话策略。请看下面的截图:

节点间的负载均衡

为了我们的测试,我们需要一个可以用来向我们的集群发送多个请求的软件应用程序。我们将使用 JMeter,这是一个 Java 桌面应用程序,通常用于测试负载、测试功能行为和测量性能。JMeter 可以从jmeter.apache.org/download_jmeter.cgi下载。

简而言之,JMeter 测试计划由一个或多个线程组、逻辑控制器、监听器、计时器、断言和配置元素组成。

为了我们示例的目的,我们只需创建以下元素:

  • 线程组,配置为运行 100 个后续请求

  • 一个包含有关 Web 应用程序端点信息的HTTP 请求元素

要执行此操作,请打开 JMeter 并导航到测试计划 | 添加 | 线程 | 线程组,如图所示:

节点间的负载均衡

将线程数(用户数)设置为 100。现在右键单击新创建的线程组 | 添加 | 采样器 | HTTP 请求。在此处,添加服务器 IP 和路径,如图所示。端口号可以留空,因为它默认为端口号 80。请查看以下截图:

节点间的负载均衡

此外,您还应添加一个监听器元素,将测试计划结果汇总到表格/图中,以便您查看结果。为此,导航到HTTP 请求 | 添加 | 监听器 | 在表格中查看结果。现在,从顶部菜单导航到运行 | 开始,JMeter 测试将被执行。

运行测试显示请求大致在两个服务器之间分配。请查看以下截图:

节点间的负载均衡

使用负载指标

从每个服务器收集各种系统负载指标。这些统计数据允许为每个服务器计算一个标准化的负载值。当集群负载较轻时,传入的请求会均匀地分配到每个服务器节点。随着负载的增加,发送到特定节点的流量量取决于其当前负载,也就是说,更多的流量将被导向负载最轻的节点。

默认的mod_cluster配置使用动态负载提供者,如下面的代码所示:

<subsystem >
    <mod-cluster-config advertise-socket="modcluster" connector="ajp">
        <dynamic-load-provider>
            <load-metric type="cpu"/>
        </dynamic-load-provider>
    </mod-cluster-config>
</subsystem>

您可以通过添加进一步的负载指标元素来自定义负载均衡。例如:

<subsystem >
  <mod-cluster-config advertise-socket="modcluster" connector="ajp">
    <dynamic-load-provider history="10" decay="2">
       <load-metric type="cpu" weight="2" capacity="1"/>
 <load-metric type="sessions" weight="1" capacity="512"/>
    </dynamic-load-provider>
  </mod-cluster-config>
</subsystem>

计算负载均衡时最重要的因素是权重容量属性。权重(默认为1)表示指标相对于其他指标的影响。在上一个示例中,CPU 指标的影响将是具有1负载系数会话的两倍。

另一方面,容量属性可用于对负载指标进行精细控制。通过为每个指标设置不同的容量,您实际上可以偏向一个节点而不是另一个,同时保留指标权重。

支持的负载指标列表总结在下表中:

指标 组成指标使用的系数
cpu CPU 负载
堆内存使用量占最大堆大小的百分比
会话 Web 会话数量
请求 每秒请求数量
发送流量 流量中的出站请求数量
接收流量 流量后的入站请求数量

上述度量也可以使用 CLI 设置,例如,假设您想添加一个基于代理使用的堆内存量的度量。当通知您重新加载配置时(输入reload命令),不要忘记重新加载配置。以下是您需要执行的命令:

[standalone@localhost:9990 /] /subsystem=modcluster/mod-cluster-config=configuration/dynamic-load-provider=configuration/load-metric=heap:add(type=heap)
{
 "outcome" => "success",
 "response-headers" => {
 "operation-requires-reload" => true,
 "process-state" => "reload-required"
 }
}
[standalone@localhost:9990 /] /subsystem=modcluster/mod-cluster-config=configuration/dynamic-load-provider=configuration:read-resource()
{
 "outcome" => "success",
 "result" => {
 "decay" => 2,
 "history" => 9,
 "custom-load-metric" => undefined,
 "load-metric" => {
 "cpu" => undefined,
 "heap" => undefined
 }
 }
}

您也可以使用remove命令来移除度量,如下所示:

[standalone@localhost:9990 /] /subsystem=modcluster/mod-cluster-config=configuration/dynamic-load-provider=configuration/load-metric=heap:remove()
{
 "outcome" => "success",
 "response-headers" => {
 "operation-requires-reload" => true,
 "process-state" => "reload-required"
 }
}

在集群上设置动态度量的示例

在以下示例中,我们有一个由两个节点组成的非常简单的集群。每个节点具有相同的 JVM 操作默认值,并且每个节点都在两台相同的机器上运行。

然而,我们将对第一个节点进行内存密集型操作的模拟,以便每个服务器使用的堆内存量不同,如下面的截图所示:

在集群上设置动态度量的示例

这是在 Web 应用程序中一个常见的场景,不同的情况对每个服务器的内存有不同的影响,例如,在 HTTP 会话中暂时保留数据。

在这种情况下,使用轮询方法分配请求可能会导致集群中某些节点出现“内存不足”的情况。您可以尝试通过简单地修改加载度量的配置来减轻这种情况,如下所示:

<subsystem >
  <mod-cluster-config advertise-socket="mod_cluster">
    <dynamic-load-provider history="10" decay="2">
       <load-metric type="heap" weight="2" /> 
       <load-metric type="mem"  weight="1" />
       <load-metric type="cpu"  weight="1" />
    </dynamic-load-provider>
  </mod-cluster-config>
</subsystem>

当在两个节点上使用此配置时,堆内存的使用对其他列出的度量(操作系统内存和 CPU 速度)的影响是两倍。

结果是,第二个服务器处理了 55%的请求,而第一个服务器处理了 45%。

通过设置适当的容量,您可以进一步提高节点权重的粒度,例如,通过在第一个服务器上设置更高的容量,如下所示:

       <load-metric type="heap" weight="2" capacity="512"/>

您可以对第二个服务器设置较低的容量,如下所示:

       <load-metric type="heap" weight="2" capacity="1"/>

因此,测试的结果将不同,因为第二个服务器现在提供的响应比第一个服务器多,从而抵消了权重度量。

注意

每个度量的容量默认为 512,应该配置为0 <= (load / capacity) >= 1

摘要

在本章中,我们展示了如何在节点集之间分配应用程序负载的各种方法。这被称为负载均衡。

负载均衡需要一个网络服务器,例如 Apache,它将流量导向您的各个应用服务器。

在本章的前半部分,我们说明了如何在 WildFly 中使用mod_jkmod_proxy库。mod_jk库需要在 HTTPD 端和 AS 端进行一些配置。mod_proxy库是一个更直接的方法,当使用 WildFly 时是一个首选解决方案,因为它只需要在 HTTPD 端配置端点。

在本章的后半部分,我们探讨了使用mod_cluster在应用程序之间进行负载均衡的推荐方法。

与传统负载均衡器相比,使用 mod_cluster 的主要优势是它不需要静态的工作节点列表,而是通过基于多播的广告机制动态注册应用程序服务器及其应用程序。

这在云环境中尤其有用,因为在云环境中你不能依赖于一个静态的节点列表。动态地添加或删除节点要更有益得多。

最后,mod_cluster 的另一个主要好处是你可以使用在服务器端计算的一组动态指标来定义服务器节点之间的负载。例如,你可以优先考虑配置更好的服务器,比如具有更高内存或更好处理能力的服务器。

在下一章中,我们将探讨 WildFly 管理中最重要的一部分,那就是安全性。

第十章. 保护 WildFly

安全性是企业应用程序的关键元素。您必须能够控制并限制谁有权访问您的应用程序以及用户可以执行哪些操作。

Java 企业版Java EE)规范为企业 JavaBeansEJBs)和 Web 组件定义了一个简单的基于角色的安全模型。JBoss 安全性的实现由 PicketBox 框架(以前称为 JBoss 安全)提供,该框架为 Java 应用程序提供认证、授权、审计和映射功能。

由于涉及安全性的主题数量需要一个专门的书籍来阐述,因此本章将重点关注大多数管理员和开发者感兴趣的主题。我们将详细讨论以下主题:

  • Java 安全 API 简介

  • WildFly 安全子系统的基本原理

  • 定义登录模块及其与各种企业组件(例如,Web 应用 EJB)的集成

  • 保护管理接口

  • 使用安全套接字层SSL)加密对 Web 应用的网络调用

接近 Java 安全 API

Java EE 安全服务提供了一种强大且易于配置的安全机制,用于验证用户并授权对应用程序功能和相关数据的访问。为了更好地理解与安全性相关的主题,我们首先提供一些基本定义:

认证是确保一个人是他所声称的人的过程。认证通常通过检查用户的登录凭证是否与存储在数据存储中的凭证匹配来执行。登录凭证通常包括用户名和密码,但也可以是 X.509 证书或一次性密码OTP)的形式。以下图示展示了登录过程的流程。最终用户提供用户名和密码,并将其提交给应用程序服务器。登录模块将用户的详细信息与存储在数据存储中的信息进行核对。如果凭证匹配,则用户登录;如果凭证不匹配,则登录过程将失败。请查看以下图表:

接近 Java 安全 API

授权是验证用户是否有权访问特定系统资源的过程。授权应在认证之后进行。请查看以下图表:

接近 Java 安全 API

在 Java EE 中,组件容器负责提供应用程序安全。容器基本上提供两种类型的安全:声明式程序性

  • 声明式安全通过部署描述符和/或注解定义应用程序组件的安全要求。部署描述符是一个外部文件,可以修改而不需要重新编译源代码。

    例如,企业 JavaBeans 组件可以使用一个名为ejb-jar.xml的 EJB 部署描述符,并将其放置在 EJB JAR 文件的META-INF文件夹中。

    Web 组件使用一个名为web.xml的 Web 应用程序部署描述符,该描述符位于WEB-INF目录中。

    注解是在类文件中指定的,这意味着任何更改都需要重新编译代码。

    使用注解比使用部署描述符提供了许多好处。首先,它在源代码中比在各个 XML 文件中分散的信息更清晰。其次,它更容易维护,因为配置文件更少。

    使用注解也意味着开发者需要更少的样板代码。

  • 程序式安全在安全检查嵌入到应用程序代码中时出现。当仅使用声明式安全不足以表达应用程序的安全模型时,可以使用它。例如,Java EE 安全 API 允许开发者使用以下方法测试当前用户是否具有特定的角色:

    • isUserInRole(): 在 servlets 和 JSPs 中使用此方法(在javax.servlet.http.HttpServletRequest中采用)

    • isCallerInRole(): 在 EJBs 中使用此方法(在javax.ejb.SessionContext中采用)

    此外,还有其他 API 调用可以提供对用户身份的访问,如下所示:

    • getUserPrincipal(): 在 servlets 和 JSPs 中使用此方法(在javax.servlet.http.HttpServletRequest中采用)

    • getCallerPrincipal(): 在 EJBs 中使用此方法(在javax.ejb.SessionContext中采用)

    使用这些 API,你可以通过编程方式开发复杂的授权模型。

注意

虽然注解本身是程序性的,但它们使安全成为一种声明式风格。因此,注解被认为包含了声明式和程序式安全概念。

Java EE 安全模型是声明式的,因此将安全代码嵌入到业务组件中不是一个选择。这里的声明式意味着你在一个标准的 XML 描述符中描述安全角色和权限。声明式安全允许将来自这个横切关注点的逻辑从核心业务逻辑中提取出来。这导致代码更清晰、更易于阅读。

声明式安全模型的默认实现基于Java 认证和授权服务JAAS)登录模块和主题。WildFly 安全有一个安全代理层,允许开发者创建自定义安全服务,如果默认实现不足以满足需求。这允许在不污染业务代码的情况下独立于使用它的 bean 对象构建自定义安全。

WildFly 使用基于 JAAS 的 PicketBox 框架,用于保护在应用服务器中运行的 Java EE 技术。

WildFly 安全子系统

WildFly 安全子系统是应用服务器的一个扩展,默认包含在独立服务器和域服务器中。查看以下代码:

<extension module="org.jboss.as.security"/>

以下是在服务器配置文件中包含的默认安全子系统:

<subsystem >
  <security-domains>
    <security-domain name="other" cache-type="default">
      <authentication>
        <login-module code="Remoting" flag="optional">
          <module-option name="password-stacking"    value="useFirstPass"/>
        </login-module>
        <login-module code="RealmDirect" flag="required">
          <module-option name="password-stacking" value="useFirstPass"/>
        </login-module>
      </authentication>
    </security-domain>
    <security-domain name="jboss-web-policy" cache-type="default">
      <authorization>
        <policy-module code="Delegating" flag="required"/>
      </authorization>
    </security-domain>
    <security-domain name="jboss-ejb-policy" cache-type="default">
      <authorization>
        <policy-module code="Delegating" flag="required"/>
      </authorization>
    </security-domain>
  </security-domains>
</subsystem>

如您所见,配置相当简短,因为它在很大程度上依赖于默认值,特别是对于高级结构,如安全管理区域。

注意

安全域不明确要求授权策略。如果安全域没有定义授权模块,则使用默认的jboss-web-policyjboss-ejb-policy授权。在这种情况下,应用委托授权策略,该策略简单地委托授权给另一个作为<module-option>声明的模块。

您可以通过定义自己的安全管理配置来覆盖默认的认证/授权管理器。您不太可能需要覆盖这些接口,因此我们将重点放在security-domain元素上,这是 WildFly 安全子系统的核心方面。

可以将安全域想象成外国人的海关办公室。在请求越过 WildFly 边界之前,安全域执行所有必要的授权和身份验证检查,并通知调用者他们是否可以继续。

安全域通常在服务器启动时或在运行中的服务器中进行配置,然后绑定到java:/jaas/键下的 JNDI 树。在安全域内,您可以配置登录身份验证模块,以便您可以简单地通过更改其登录模块来轻松更改您的认证提供者。

以下表格描述了所有可用的登录模块,包括它们的简要描述:

登录模块 描述
Client 此登录模块旨在在 AS 作为客户端时建立调用者身份和凭证。它不应作为实际服务器身份验证的安全域的一部分使用。
Database 此登录模块从数据库加载用户/角色信息。
Certificate 此登录模块旨在根据 X.509 证书对用户进行身份验证。
CertificateRoles 此登录模块扩展了Certificate登录模块,以添加从属性文件中获取的角色映射功能。
DatabaseCertificate 此登录模块扩展了Certificate登录模块,以添加从数据库表获取的角色映射功能。
DatabaseUsers 这是一个基于 JDBC 的登录模块,支持身份验证和角色映射。
Identity 此登录模块简单地将模块选项中指定的原则与对模块进行身份验证的任何主体关联。
Ldap 此登录模块从 LDAP 服务器加载用户/角色信息。
LdapExtended 此登录模块是一个替代的 LDAP 登录模块实现,它使用搜索来定位用户以及关联的角色以绑定认证。
RoleMapping 此登录模块用于将认证过程的结果角色映射到一个或多个声明性角色。
RunAs 此登录模块可用于允许其他登录模块与提供认证服务的受保护 EJB 交互。
Simple 此登录模块用于快速设置测试目的的安全设置。
ConfigureIdentity 这是一个将模块选项中指定的原则与模块中认证的任何主体关联的登录模块。
PropertiesUsers 此登录模块使用属性文件来存储用于认证的用户名和密码。不映射任何角色。
SimpleUsers 此登录模块将用户名和密码作为选项存储。
LdapUsers 此登录模块使用 LDAP 服务器进行用户认证。
Kerberos 此登录模块使用 Sun 的 Kerberos 登录模块作为认证机制。
SPNEGOUsers 此登录模块与 SPNEGOAuthenticator 协同工作以处理认证。
AdvancedLdap 此登录模块是对 LdapExtLoginModule 的重构,它能够分离登录步骤(查找、认证或映射角色),以便任何操作都可以单独执行。
AdvancedADLdap 此登录模块是 AdvancedLdap 登录模块的扩展,它也能够查询正在认证的用户的主组。
UsersRoles 此登录模块是一个简单的基于属性映射的登录模块,它咨询两个 Java 属性格式化的文本文件,将用户名映射到密码(users.properties)和用户名映射到角色(roles.properties)。

激活登录模块是一个两步过程,具体如下:

  1. 首先,您需要在您的 standalone.xml/domain.xml 配置文件中定义登录模块。

  2. 然后,您需要通知您的应用程序使用登录模块来执行认证和授权。

注意

在应用程序服务器的早期版本中,登录模块是在一个名为 login-config.xml 的单独文件中配置的。将早期登录模块迁移到新应用程序服务器并不复杂,因为登录模块的格式与新的应用程序服务器几乎相同。

我们现在将更详细地展开这些点。首先,让我们看看如何定义一些常用的登录模块,然后我们将将它们应用到 Java EE 组件中,如 servlet、EJB 和 Web 服务。

使用 UsersRoles 登录模块

UsersRoles 登录模块是您可以在应用程序中用于测试目的的最简单的安全域之一。它基于两个文件,如下所示:

  • users.properties:此文件包含用户名和密码列表

  • roles.properties: 此文件包含用户与角色之间的映射

这里是一个示例UsersRoles配置,它将安全文件存储在应用程序服务器的配置目录中:

<security-domain name="basic" cache-type="default">
 <authentication>
   <login-module code="UsersRoles" flag="required">
       <module-option name="usersProperties" value="${jboss.server.config.dir}/users.properties"/>
       <module-option name="rolesProperties" value="${jboss.server.config.dir}/roles.properties"/>
   </login-module>
 </authentication>
</security-domain>

您要开始使用安全域,只需将两个属性文件添加到指定的路径(对于独立系统,默认为JBOSS_HOME/standalone/configuration)并在其中添加您的用户名和密码。此登录模块不支持哈希密码;仅支持明文密码。例如,users.properties文件可以包含以下内容:

myusername=mypassword

roles.properties文件包含给定用户名的角色集合。在用户名后添加后缀,如以下代码的第二行所示,允许您将用户名角色分配给一组角色:

myusername=myrole1,myrole2
myusername.MyRoleGroup1=myrole3,myrole4

这意味着使用管理员/admin 凭据进行认证将把管理员的角色分配给用户。

使用数据库登录模块

数据库安全域遵循与早期示例中相同的逻辑,不同之处在于它将凭据存储在数据库中。为了运行此示例,我们需要参考我们之前创建的MySqlDS数据源,在第三章,配置企业服务。请查看以下代码:

<security-domain name="mysqldomain" cache-type="default">
    <authentication>
         <login-module code="Database" flag="required">
               <module-option name="dsJndiName" value="java:/MySqlDS"/>
               <module-option name="principalsQuery" value="select passwd from USERS where user=?"/>
               <module-option name="rolesQuery" value="select role, 'Roles' from USER_ROLES where user=?"/>
         </login-module>
     </authentication>
</security-domain>

注意

您会在rolesQuery模块选项中注意到有一个第二个选择项(Roles)。这对应于RoleGroup列,并且必须始终提供大写的"R"(R)。

为了开始使用此配置,您首先必须创建所需的表并将一些示例数据插入其中:

CREATE TABLE USERS(user VARCHAR(64) PRIMARY KEY, passwd VARCHAR(64));
CREATE TABLE USER_ROLES(user VARCHAR(64), role VARCHAR(32));

INSERT INTO USERS VALUES('admin', 'admin');
INSERT INTO USER_ROLES VALUES('admin', 'Manager');

如您所见,管理员用户将映射到Manager角色。此配置的一个注意事项是它使用数据库中的明文密码,因此,在将此模块投入生产之前,您应该考虑为您的登录模块提供额外的安全性。让我们在下一节中看看如何做到这一点。

加密密码

将密码以明文字符串的形式存储在数据库中不被认为是良好的做法。事实上,数据库甚至比常规文件系统有更多的潜在安全问题。

幸运的是,保护应用程序密码相对简单,可以通过在登录模块中添加一些额外选项来实现。至少,您需要指定存储的密码使用消息摘要算法进行加密。例如,在mysqlLogin模块中,您可以在末尾添加突出显示的行:

<login-module code="Database" flag="required">
    <module-option name="dsJndiName" value="java:/MySqlDS"/>
    <module-option name="principalsQuery" value="SELECT passwd FROMUSERS WHERE user=?"/>
    <module-option name="rolesQuery" value="SELECT role, 'Roles' FROMUSER_ROLES WHERE user=?"/>
 <module-option name="hashAlgorithm" value="MD5"/>
 <module-option name="hashEncoding" value="BASE64"/>
 <module-option name="hashStorePassword" value="true"/>
</login-module>

在这里,我们指定密码将使用MD5哈希算法进行哈希;您也可以使用 JCA 提供者允许的任何其他算法,例如 SHA。

小贴士

对于生产环境,应避免使用 MD5 散列,因为它非常弱。理想情况下,您应使用类似 SHA-512 的算法,并具有大量的散列迭代次数。您还应为每个用户使用单个随机生成的盐。在撰写本文时,最好的散列算法之一是 bcrypt,它会为您生成盐。在做出最终决定之前,您应该进行一些研究。这些加密不被DatabaseServerLoginModule支持,因此您需要创建自己的自定义登录模块。请参阅以下链接以编写自定义登录模块:docs.jboss.org/jbossas/docs/Server_Configuration_Guide/4/html/Writing_Custom_Login_Modules-A_Custom_LoginModule_Example.html

为了完整性,我们在此包含一个小型应用,该应用使用java.security.MessageDigestorg.jboss.security.Base64Util类来生成要插入数据库的 base-64 散列密码。请查看以下代码:

public class Hash {

    public static void main(String[] args) throws Exception {
        String password = args[0];

        MessageDigest md = MessageDigest.getInstance("MD5");

        byte[] passwordBytes = password.getBytes();
        byte[] hash = md.digest(passwordBytes);
        String passwordHash = Base64.getEncoder().encodeToString(hash);
        System.out.println("password hash: "+passwordHash);
    }
}

使用admin作为参数运行主程序会生成散列值X8oyfUbUbfqE9IWvAW1/3。这个散列值将成为我们数据库管理员用户的更新密码。请查看以下屏幕截图:

加密密码

小贴士

如果您不使用 Java 8,可以使用本节中所示的org.jboss.security.Base64Utils库代替Java 8

使用 LDAP 登录模块

轻量级目录访问协议LDAP)是提供目录服务给应用程序的事实标准。LDAP 服务器可以为以下提供集中目录信息:

  • 用户凭据(登录名和密码)

  • 用户目录信息(如姓名和电子邮件地址)

  • 网络目录

LDAP 的工作原理围绕一个称为条目的数据结构。一个条目有一组称为属性的命名组成部分,这些属性包含该条目的数据。这些属性就像数据库记录中的字段一样。

条目的内容和结构由其对象类定义。对象类(连同服务器和用户设置)指定在该特定条目中必须存在哪些属性,以及哪些属性可能存在。

存储在 LDAP 目录中的所有条目都有一个唯一的区分名称DN。每个 LDAP 条目的 DN 由两部分组成:相对区分名称RDN)和记录在 LDAP 目录中的位置。

实际上,RDN 是 DN 中与目录树结构无关的部分,并且反过来由一个或多个属性名称/值对组成。让我们看看以下图中的组织示例:

使用 LDAP 登录模块

在前面的图中,cn=John Smith(其中cn代表“通用名称”)可能是一个 RDN。属性名称是cn,值是John Smith

另一方面,John Smith的 DN 将是cn=John Smithou=Marketingo=Acmec=US(其中ou代表组织单元,o代表组织,c代表国家)。

将 LDAP 连接到 WildFly

通过几种 LDAP 登录模块可以实现 WildFly 和 LDAP 的连接。我们首先需要做的是运行一个 LDAP 服务器实例。今天,有大量的 LDAP 服务器可供选择(包括商业和开源),也许你已经在公司中配置了一个来运行。如果你没有,或者只是不想向其中添加示例数据,我们建议你查看 Apache Directory 项目(directory.apache.org/)。它提供了一个出色的解决方案,用于开始使用 LDAP 并构建复杂的目录基础设施。

安装完成后,我们建议你使用 Apache Directory Studio(可在同一链接中找到),因为它允许你快速创建目录基础设施。从头开始创建目录的最简单方法是使用LDAP 数据交换格式LDIF)文件。在此文件中,你可以指定所有将由 LDAP 引擎加载的条目。

注意

从 Apache Studio 导入 LDIF 文件的一个快速方法是,在文件菜单中选择文件 | 导入 | LDIFLDAP

这里是一个我们将要使用的 LDIF 文件的基本示例:

dn: dc=example,dc=com
objectclass: top
objectclass: dcObject
objectclass: organization
dc: example
o: MCC

dn: ou=People,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: People

dn: uid=admin,ou=People,dc=example,dc=com
objectclass: top
objectclass: uidObject
objectclass: person
uid: admin
cn: Manager
sn: Manager
userPassword: secret

dn: ou=Roles,dc=example,dc=com
objectclass: top
objectclass: organizationalUnit
ou: Roles

dn: cn=Manager,ou=Roles,dc=example,dc=com
objectClass: top
objectClass: groupOfNames
cn: Manager
description: the JBossAS7 group
member: uid=admin,ou=People,dc=example,dc=com

一旦你将此信息导入 LDAP 服务器,你将得到一个如以下截图所示的小型目录:

将 LDAP 连接到 WildFly

在此目录中,我们只注册了一个用户作为admin,属于Manager角色,就像我们在前面的章节中看到的其他登录模块一样。

现在,我们将配置 WildFly 上的 LDAP 连接。为了我们的目的,我们将使用LdapExtended登录模块实现,如下面的代码所示。此实现使用搜索来定位用户和关联的角色以进行认证绑定。roles查询将递归地跟随区分名(DN)以导航分层角色结构。请查看以下代码:

<login-module code="LdapExtended" flag="required">

    <module-option name="java.naming.factory.initial"  value="com.sun.jndi.ldap.LdapCtxFactory"/>
    <module-option name="java.naming.provider.url" value="ldap://localhost:10389"/>
    <module-option name="java.naming.security.authentication" value="simple"/>
    <module-option name="bindDN" value="uid=admin,ou=system"/>
    <module-option name="bindCredential" value="secret"/>
    <module-option name="baseCtxDN" value="ou=People,dc=example,dc=com"/>
    <module-option name="baseFilter" value="(uid={0})"/>
    <module-option name="rolesCtxDN" value="ou=Roles,dc=example,dc=com"/>
    <module-option name="roleFilter" value="(member={1})"/>
    <module-option name="roleAttributeID" value="cn"/>
    <module-option name="searchScope" value="ONELEVEL_SCOPE"/>
    <module-option name="allowEmptyPasswords" value="true"/>
</login-module>

下面是LdapExtended模块属性的简要描述:

  • bindDN:这是用于对用户和角色查询绑定到 LDAP 服务器的 DN,在我们的案例中是"uid=admin,ou=system"。

  • baseCtxDN:这是从用户搜索开始上下文的固定 DN。在我们的例子中,它是"ou=People,dc=example,dc=com"。

  • baseFilter:这是一个用于定位要认证的用户上下文的搜索过滤器。从登录模块获得的输入usernameuserDN将在过滤器中任何出现{0}表达式的位置被替换。

  • rolesCtxDN:这是搜索用户角色的上下文的固定 DN。请考虑这并不是实际角色位置的 DN;相反,这是包含用户角色的对象的位置的 DN。

  • roleFilter:这是一个用于定位与已认证用户关联的角色的搜索过滤器。一个匹配输入用户名的示例搜索过滤器是(member={0})。另一个匹配已认证用户 DN 的替代方案是(member={1})

  • roleAttributeID:这是与角色名称相对应的上下文角色属性的名称。

  • searchScope:此设置将搜索范围设置为以下字符串之一:

    • ONELEVEL_SCOPE:此作用域在命名角色上下文直接下搜索用户和关联的角色。

    • SUBTREE_SCOPE:如果角色的上下文是DirContext,则此作用域在命名对象及其子树中搜索,包括命名对象本身。如果角色的上下文不是DirContext,则此作用域仅搜索对象。

    • OBJECT_SCOPE:此作用域仅在命名角色上下文中搜索。

  • allowEmptyPasswords:这是一个标志,表示是否应将empty(length==0)密码传递给 LDAP 服务器。

保护 Web 应用程序

好的!所以,我们提到了一些常用的登录模块。这些登录模块可以被任何 Java EE 应用程序使用,所以现在是时候展示一个具体的例子了。在本节中,我们将向您展示如何将登录模块应用于 Web 应用程序,以展示基本 Web 认证的实现。

注意

基本访问认证是在通过浏览器发出请求时提供用户名和密码的最简单方式。

它通过发送包含用户凭据的编码字符串来实现。此 Base64 编码的字符串由接收方传输和解码,结果是一个冒号分隔的用户名和密码字符串。

我们需要做的第一件事是启用 web 认证。这需要你在 web 应用程序配置文件(web.xml)中定义security-constraints。请看以下代码:

<web-app>
...
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>HtmlAuth</web-resource-name>
      <description>application security constraints
      </description>
 <url-pattern>/*</url-pattern>
      <http-method>GET</http-method>
      <http-method>POST</http-method>
      <http-method>PUT</http-method>
      <http-method>DELETE</http-method>
    </web-resource-collection>
 <auth-constraint>
 <role-name>Manager</role-name>
 </auth-constraint>
  </security-constraint>
  <login-config>
     <auth-method>BASIC</auth-method>
     <realm-name>Sample Realm</realm-name>
  </login-config>

 <security-role>
 <role-name>Manager</role-name>
 </security-role>
</web-app>

之前的配置将为所有 URL 添加一个安全约束,这显然包括你所有的 JSP servlets。访问将仅限于具有Manager角色的已认证用户。

注意

考虑到我们正在使用Database登录模块,Manager角色将授予使用管理员凭据认证的用户。

下一个配置调整需要在 JBoss web 部署的描述符WEB-INF/jboss-web.xml中执行。在那里,你需要声明用于认证用户的 security domain。请看以下代码:

<jboss-web>
 <security-domain>java:/jboss/env/mysqldomain</security-domain>
</jboss-web>

注意security-domain元素。此元素的值必须与你在安全域的name属性中输入的值完全相同。

提示

有关 WildFly 中哪些 JNDI 名称是有效的概述,请参阅以下链接:docs.jboss.org/author/display/WFLY8/Developer+Guide#DeveloperGuide-ReviewtheJNDINamespaceRules

以下图概述了应用于 Database 登录模块的整个配置序列。请查看以下图:

保护 Web 应用程序

部署您的应用程序后,此操作的输出应该是一个弹出窗口,请求用户身份验证,如下面的屏幕截图所示:

保护 Web 应用程序

使用 admin/admin 登录将授予 Manager 角色访问应用程序的权限。

保护 EJB

通过 Web 登录表单保护应用程序是企业应用程序中最常见的选项。尽管如此,HTTP 协议并不是访问应用程序的唯一选择。例如,EJB 可以通过 RMI-IIOP 协议由远程客户端访问。在这种情况下,您应该进一步细化您的安全策略,通过限制对 EJB 组件的访问,这些组件通常涉及您应用程序的业务层。

小贴士

EJB 层级的安全是如何实现的?

在调用任何 EJB 方法之前必须执行身份验证,并且应该在每次 EJB 方法调用开始时执行授权。

基本的安全检查可以使用以下五个注解实现:

  • @org.jboss.ejb3.annotation.SecurityDomain:此注解指定安全域,它与一个特定的类相关联。

  • @javax.annotation.security.RolesAllowed:此注解指定了允许访问 EJB 中方法(s)的角色的列表。

  • @javax.annotation.security.RunAs:此注解在调用方法期间动态地将一个角色分配给 EJB。如果需要临时允许访问某个方法,可以使用它。

  • @javax.annotation.security.PermitAll:此注解允许所有角色访问特定的 bean 方法。此注解的目的是在您不确定哪个角色将访问 EJB 的情况下扩大某些方法的安全访问权限。(想象一下,某些模块是由第三方开发的,并且它们使用一些标识不清的角色访问您的 EJB)。

  • @javax.annotation.security.DenyAll:此注解拒绝所有角色的访问。它的目的与 PermitAll 类似。

在以下示例中,我们仅将访问名为 SecureEJB 的 EJB 的权限限制在授权的 Manager 角色中:

import org.jboss.ejb3.annotation.SecurityDomain;
import javax.annotation.security.RolesAllowed;

@Stateless
@SecurityDomain("mysqldomain")
@RolesAllowed( { "Manager" })
public  class SecureEJB {
  ...
}

小贴士

小心!服务器类路径中存在多个 SecurityDomain 注解。如图所示,您必须包含 org.jboss.ejb3.annotation.SecurityDomain。另一方面,@RolesAllowed 注解需要导入 javax.annotation.security.RolesAllowed

注解也可以应用于方法级别。例如,如果我们需要一个名为SuperUser的特殊角色来插入新用户,那么我们可以像以下那样标记方法:

@RolesAllowed({"SuperUser"})
public void createUser(String country,String name) {
    User customer = new User ();  
    customer.setCountry(country);
    customer.setName(name);
    em.persist(customer);
}

保护 Web 服务

Web 服务授权可以通过两种方式执行,具体取决于我们是否处理基于 POJO 的 Web 服务或基于 EJB 的 Web 服务。

对 POJO Web 服务的安全更改与我们为 servlets 或 JSP 引入的更改相同,包括在web.xml中定义security-constraints以及在jboss-web.xml中定义登录模块。

如果您使用 Web 客户端访问您的 Web 服务,那么您只需要进行认证。如果您使用的是独立客户端,您将需要指定给 JAX-WS 工厂的凭据,如下面的代码片段所示:

  JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

  factory.getInInterceptors().add(new LoggingInInterceptor());
  factory.getOutInterceptors().add(new LoggingOutInterceptor());

  factory.setServiceClass(POJOWebService.class);
  factory.setAddress("http://localhost:8080/pojoService");
 factory.setUsername("admin");
 factory.setPassword("admin");
  POJOWebService client = (POJOWebService) factory.create();

  client.doSomething();

那么,基于 EJB 的 Web 服务怎么办?配置略有不同。由于安全域没有在 Web 描述符中指定,我们必须通过注解提供它:

@Stateless
@WebService(targetNamespace = "http://www.packtpub.com/", 
    serviceName = "SecureEJBService")
@WebContext(authMethod = "BASIC",
 secureWSDLAccess = false)
@SecurityDomain(value = "mysqldomain")
public  class SecureEJB {
  ...
}

如您所见,@WebContext注解反映了与基于 POJO 的 Web 服务相同的配置选项,包括 BASIC 身份验证和无限制的 WSDL 访问。

现在您应该对@SecurityDomain注解很熟悉了,因为我们是在向您展示如何保护 EJB 时引入它的。正如您在先前的 Web 服务示例中所见,它与jboss-web.xml文件中的信息相当(它引用了mysqldomain安全域)。

提示

如果您更喜欢使用 XML 部署描述符,先前的安全配置也可以通过META-INF/ejb-jar.xmlMETA-INF/jboss-ejb3.xml文件指定。

保护管理接口

对于系统管理员来说,最重要的任务之一是限制对服务器管理接口的访问。如果没有安全策略,每个用户都可以访问应用服务器并修改其属性。

用于在管理接口上启用安全性的属性是一个需要在security-realms部分中定义的安全领域。请查看以下代码:

<management>
    <security-realms>
 <security-realm name="ManagementRealm">
            <authentication>
                <local default-user="$local" skip-group-loading="true"/>
                <properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
            </authentication>
            <authorization map-groups-to-roles="false">
                <properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
            </authorization>
        </security-realm>
    </security-realms>
    ...
    <management-interfaces>
 <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
            <socket-binding http="management-http"/>
        </http-interface>
    </management-interfaces>
</management>

在默认配置下,用户属性存储在mgmt-users.properties文件中,组属性存储在mgmt-groups.properties文件中。这两个文件都可以在您的服务器configuration目录中找到。

注意

用户和组可以随时添加到这些属性文件中。服务器启动后的任何更新都会自动检测。

默认情况下,此管理领域期望条目以以下格式:

username=HEX( MD5( username ':' realm ':' password))

这意味着每个用户都与一个由用户名、领域名称和密码组成的十六进制编码哈希相关联。

要添加新用户,您可以使用包含在您的 WildFly 安装的bin文件夹中的实用脚本,脚本名为add-user.sh(Linux)或add-user.bat(Windows)。从下面的屏幕截图可以看出,add-user脚本需要以下信息:

  • 领域:这是用于保护管理界面的领域名称。如果您只按Enter键,用户将被添加到默认领域ManagementRealm

  • 用户名:这是我们打算添加的用户名(它必须是字母数字的)。

  • 密码:这是密码字段,它需要与用户名不同。

  • :这是您希望用户所属的组名称。如果您留空,您将不会被添加到任何组。

  • AS 进程:这决定了您是否希望用户用于连接到另一个 WildFly 实例。

保护管理接口

这里,我们已将用户chris添加到默认领域。这导致以下属性被添加到您的独立和域配置的mgmt-users.properties中:

chris=554dadf6fa222d6ea11a470f3dea7a94

您现在可以使用此用户连接到远程 WildFly 管理界面,如下面的截图所示:

保护管理接口

添加用户的一个更简单的方法是使用非交互式 shell。这种方法通过将用户名、密码以及可选的领域名称传递给add-user脚本来实现:

add-user.sh myuser mypassword realm1

基于角色的访问控制

基于角色的访问控制RBAC)是 WildFly 8 中引入的新功能。它允许系统管理员为管理控制台创建用户,但对其系统的一部分有权限限制。在 JBoss AS 7 中,管理控制台用户可以访问一切,这在 WildFly 8 中相当于超级用户角色。

RBAC 默认未启用。要启用它,请运行以下命令:

jboss-cli.sh --connect --command="/core-service=management/access=authorization:write-attribute(name=provider,value=rbac)"

然后,重新加载服务器配置:

jboss-cli.sh --connect --command=":reload"

如果在启用 RBAC 之前您已有现有用户,您需要手动为每个用户配置,通过将用户映射到角色。如果我们有一个名为 Yevai 的用户,并希望分配给她超级用户角色,我们会执行以下操作:

jboss-cli.sh --connect --command="/core-service=management/access=authorization/role-mapping=SuperUser/include=user-yevai:add(name=yevai,type=USER)"

WildFly 8 中有七个预定义的角色。以下表格概述了每个角色。它们按最限制性的角色在上,最不限制性的角色在下排序。

角色 权限
监视器 此用户可以读取配置和当前运行时状态
操作员 此用户具有先前角色的所有权限,可以修改运行时状态,例如重启或重新加载服务器,以及刷新数据库连接池
维护者 此用户具有所有先前角色的所有权限,可以修改持久状态,例如部署应用程序和设置新的数据源
部署者 此用户具有所有先前角色的所有权限,但只有对应用程序的权限。此用户不能更改服务器的配置
管理员 此用户具有所有先前角色的所有权限,可以查看和修改敏感数据,例如访问控制系统
审计员 此用户具有所有先前角色的权限,可以查看和修改资源以管理审计日志系统
超级用户 此用户拥有所有权限

配置组

WildFly 的新特性之一是能够将用户分配到组中。这意味着您可以将一组用户分配到组中,然后将组分配到角色中。要创建新用户并将他们分配到组中,您可以运行以下非交互式命令:

user-add.sh -u tavonga -p mypassword -g MyGroup

用户可以通过具有管理员或超级用户角色的用户通过管理控制台进行管理。为此,请登录到管理控制台,并导航到管理选项卡。在这里,您可以向组添加用户,创建组,并最终查看每个角色的成员。请看以下截图:

配置组

保护传输层

如果你只使用我们至今为止所涵盖的裸概念来创建一个关键任务应用程序,你不能保证能够免受所有安全威胁。例如,如果你需要设计一个支付网关,其中信用卡信息通过 EJB 或 servlet 进行传输,仅仅使用授权和认证堆栈是远远不够的。

为了防止信息泄露,你必须使用提供数据加密的协议。加密是将数据转换为人们或监听你网络的系统无法理解的形式。相反,解密是将加密数据转换回其原始形式的过程,以便它可以被理解。

用于保护通信的协议是 SSL 和 TLS,后者被认为是较旧 SSL 的替代品。

注意

这两种协议之间的差异很小。TLS 使用更强的加密算法,并且能够在不同的端口上工作。在接下来的章节中,我们将对这两种协议都使用 SSL。

加密信息有两种基本技术:对称加密(也称为密钥加密)和非对称加密(也称为公钥加密)。

对称加密是最古老且最知名的技术。它基于一个密钥,该密钥应用于消息的文本以以特定方式更改内容。只要发送者和接收者都知道密钥,他们就可以加密和解密使用此密钥的所有消息。这些加密算法通常运行得很快,非常适合一次性加密大量消息。

对称算法的一个显著问题是组织需要向用户分发密钥。这通常会导致在管理方面产生更多的开销,同时密钥仍然容易受到未经授权的披露和潜在滥用的威胁。

因此,一个关键任务的企业系统通常依赖于非对称加密算法。这些算法通常更容易部署、管理和维护,从而使系统最终更加安全。

非对称加密,也称为公钥加密,基于这样一个概念:用于加密消息的密钥不是用于解密消息的密钥。每个用户都持有两把密钥:公钥,它被分发给其他各方,私钥,它被保密保存。每条消息都使用接收者的公钥进行加密,并且只能用他们的私钥解密(由接收者解密)。看看下面的图示:

保护传输层

使用非对称加密,你可以确保你的消息不会被第三方泄露。然而,你仍然有一个漏洞。

假设你想与一个商业伙伴交换信息,因此你通过电话或电子邮件请求他们的公钥。一个欺诈用户拦截你的电子邮件或简单地监听你的对话,并迅速发送一个带有他们公钥的伪造电子邮件给你。现在,即使你的数据传输是安全的,它也会被错误地发送给错误的人!这种监听被称为中间人攻击。

为了解决这个问题,我们需要一个文件来验证公钥属于个人。这个文件被称为数字证书或公钥证书。数字证书由一个格式化的数据块组成,包含证书持有者的名称(可能是用户名或系统名)、持有者的公钥以及认证机构CA)的数字签名以进行认证。认证机构证明发送者的名称与文件中公钥关联。

这里展示了一个数字证书的原型:

保护传输层

公钥证书通常用于与网站进行安全交互。默认情况下,网络浏览器附带一组预定义的 CA。它们用于验证当你输入一个安全网站时,浏览器收到的公钥证书确实是由网站所有者签发的。简而言之,如果你通过浏览器连接到https://www.abc.com并且你的浏览器没有给出证书警告,你可以确信你可以安全地与网站负责实体进行交互。

注意

简单身份验证和客户端身份验证

在前面的例子中,我们描述了一个简单的服务器身份验证。在这种情况下,唯一需要证明其身份的方是服务器。

然而,SSL 也能够在网络中的 SSL 握手期间执行双向****认证(也称为客户端或双向认证),如果服务器在 SSL 握手期间请求客户端证书的话。

客户端认证需要一个来自 CA 的 X.509 格式的客户端证书。X.509 格式是 SSL 证书的行业标准格式。在下一节中,我们将探讨可用的生成数字证书的工具,以及如何让您的证书由 CA 签名。

启用安全套接字层

WildFly 使用Java 安全套接字扩展JSSE),这是 Java 标准版中捆绑的,以利用 SSL/TLS 通信。

企业应用程序可以安全地使用两种协议:HTTP 和 RMI。HTTP 通信由standalone.xml/domain.xml文件内的 Undertow 子系统处理。对于您的应用程序来说,确保 RMI 传输的安全性并不总是强制性的要求,因为在大多数生产环境中,WildFly 位于防火墙后面。

如以下图所示,您的 EJBs 不是直接暴露在不信任的网络中,通常是通过一个 Web 服务器连接的。

启用安全套接字层

为了配置 WildFly 使用 SSL,我们需要一个工具,该工具以 X.509 证书的形式生成公钥/私钥对,用于 SSL 服务器套接字。这将在下一节中介绍。

证书管理工具

可以用来设置数字证书的一个工具是keytool,这是一个与 Java SE 一起提供的密钥和证书管理实用程序。它使用户能够管理自己的公钥/私钥对及其相关的证书,用于自我认证(用户向其他用户或服务进行身份验证)或使用数字签名进行数据完整性和认证服务。它还允许用户缓存其通信对等方的公钥(以证书的形式)。

keytool证书将密钥和证书存储在一个称为keystore的文件中,这是一个用于识别客户端或服务器的证书存储库。通常,keystore包含单个客户端或服务器的身份,并受密码保护。让我们看看keystore生成的示例:

keytool -genkeypair -keystore wildfly.keystore -storepass mypassword -keypass mypassword -keyalg RSA -validity 180 -alias wildfly -dname "cn=packtpub,o=PackPub,c=GB"

此命令在当前工作目录中创建名为wildfly.keystorekeystore,并为其分配密码mypassword。它为具有“区分名称”中通用名packtpub、组织PacktPub和两个字母国家代码GB的实体生成一个公钥/私钥对。

这将生成一个自签名证书(使用 RSA 签名算法),其中包含公钥和区分名称信息。此证书将有效期为 180 天,并与keystore条目中引用的别名wildflybook关联的私钥相关联。

注意

自签名证书是一种未经 CA 验证的证书,因此会使您容易受到经典的中间人攻击。自签名证书仅适用于内部使用或测试,直到官方证书到达。

使用自签名证书确保 HTTP 通信

现在让我们看看如何使用这个keystore文件来确保你的 WildFly Web 通道的安全。打开服务器配置文件(standalone.xml/domain.xml),导航到 Undertow 子系统。

首先,我们需要在服务器配置中添加一个https-listener元素,如下面的代码片段中加粗所示:

<subsystem >
    <buffer-cache name="default"/>
    <server name="default-server">
        <https-listener name="https" socket-binding="https" security-realm="CertificateRealm"/>
        <http-listener name="default" socket-binding="http"/>
        <host name="default-host" alias="localhost">
            <location name="/" handler="welcome-content"/>
            <filter-ref name="server-header"/>
            <filter-ref name="x-powered-by-header"/>
        </host>
    </server>
</subsystem>

现在,在management元素内创建一个新的安全域。以下代码中加粗的属性是必需的。其中包括密钥库的路径及其密码。keystore元素还接受aliasrelative-tokey-password属性,这些都是可选的:

<management>
    <security-realms>
        <security-realm name="CertificateRealm">
            <server-identities>
                <ssl>
                   <keystore path="wildfly.keystore" relative-to="jboss.server.config.dir" keystore-password="mypassword"/>  
                </ssl>
            <server-identities>
        </security-realm>
    </security-realms>
</management>

最后,你需要将wildfly.keystore文件复制到你的JBOSS_HOME/standalone/configuration文件夹。

重新启动 WildFly 以加载这些更改。在控制台日志的底部,在服务器启动期间,你应该看到以下输出(Undertow HTTPS 监听器 https 正在监听/127.0.0.1:8443)。

使用自签名证书确保 HTTP 通信

例如,如果你通过 SSL 配置的 WildFly 服务器上的 HTTPS 访问 Web 应用程序,比如部署chapter4并通过https://localhost:8443/chapter4访问它,你会看到以下屏幕(显示的屏幕将取决于你的浏览器):

使用自签名证书确保 HTTP 通信

如果你对证书的工作方式不熟悉,一旦浏览器与 Web 服务器建立了安全连接,Web 服务器就会向浏览器发送一个证书。因为我们刚刚安装的证书没有被任何认可的 CA 签名,浏览器安全沙盒会警告用户潜在的安全威胁。

由于这是一个内部测试,我们可以通过选择我了解风险 | 添加异常 | 确认安全异常来安全地继续。这就是激活自签名证书的 SSL 所需做的全部。

使用 CA 签名的证书确保 HTTP 通信

为了获得浏览器能识别的证书,你需要向 CA 发出证书签名请求CSR)。CA 随后将返回一个可以在你的服务器上安装的已签名证书。大多数这些服务都不是免费的。费用取决于你请求的证书数量、加密强度和其他因素。StartSSL 为公共域名服务器提供免费、低保证的证书。

因此,要生成 CSR,你需要使用你之前创建的keystorekeyentry。看看以下代码:

keytool -certreq -keystore wildfly.keystore -alias wildfly -storepass mypassword -keypass mypassword  -keyalg RSA  -file certreq.csr

这将创建一个名为certreq.csr的新证书请求,格式如下所示:

-----BEGIN NEW CERTIFICATE REQUEST-----
  ...
-----END NEW CERTIFICATE REQUEST-----

假设你选择了Verisign (www.verisign.com) 作为 CA,以下证书需要发送给 CA:

由 CA 签发的证书加密 HTTP 通信

在提交你的 CSR 后,CA 将返回一个需要导入到你的密钥链中的已签名证书。假设你将你的 CA 证书保存在一个名为signed_ca.txt的文件中。请看以下命令:

keytool -import -keystore wildfly.keystore -alias testkey1 -storepass mypassword -keypass mypassword -file signed_ca.txt

在这里,-import选项用于将证书或证书链添加到由-keystore参数指定并由-alias参数标识的受信任证书列表中。参数-storepass指定用于保护keystore的密码。如果未提供-keypass选项,并且私钥密码与keystore密码不同,系统将提示输入。

现在,你的网络浏览器将识别你的新证书是由 CA 签发的,并且将不再抱怨无法验证证书。

摘要

我们在本章开始时讨论了安全的基本概念以及认证和授权之间的区别。

认证用于验证用户的身份,而授权用于检查用户是否有权访问特定资源。

WildFly 使用 PicketBox 框架。PicketBox 位于 Java 身份验证和授权服务(JAAS)的顶部,并保护应用程序中运行的所有 Java EE 技术。安全子系统的核心部分包含在security-domain元素中,它执行所有必要的授权和身份验证检查。

我们接着查看了一些用于检查用户凭证与不同数据存储库的登录模块。每个登录模块都可以以编程方式或声明方式由企业应用程序使用。虽然编程安全可以提供细粒度的安全模型,但你应考虑使用声明式安全,它允许业务层和安全策略之间的清晰分离。

在本章的后面部分,你看到了如何通过向它们添加安全域来保护管理接口,即新的命令行界面。

在本章的最后部分,我们探讨了如何使用安全套接字层(Secure Socket Layer)加密通信通道,以及如何使用由keytool Java 实用程序生成的证书。

在下一章中,我们将通过展示如何在 OpenShift 上配置和分发企业应用程序来结束对 WildFly 的讨论,OpenShift 是一个 JBoss 云解决方案。

第十一章。WildFly、OpenShift 和云计算

由于云计算领域使用的术语可能引起混淆,本章的第一节将概述云计算的基本概念。然后我们将讨论 OpenShift 项目及其将为您组织带来的好处。

云计算简介

什么是云计算?我们无处不在都能听到这个术语,但它究竟是什么意思呢?我们都有意或无意地使用过云。如果你使用 Gmail、Hotmail 或任何其他流行的电子邮件服务,你就已经使用过云了。简单来说,云计算是通过互联网提供的一组池化计算资源和服务的集合。

客户端计算在计算机行业中不是一个新概念。那些在 IT 行业工作了一二十年的人会记得,第一种客户端-服务器应用程序是主机和终端应用程序。当时,存储和 CPU 非常昂贵,主机将这两种资源集中起来,为瘦客户端终端提供服务。

随着个人电脑革命的到来,它为普通企业桌面带来了大量存储和廉价的 CPU,文件服务器因此成为实现文档共享和归档的一种流行方式。正如其名,文件服务器为企业客户端提供了存储资源,而进行生产性工作所需的 CPU 周期则全部在 PC 客户端内部产生和消耗。

在 20 世纪 90 年代初,新兴的互联网终于连接了足够的计算机,学术机构开始认真思考如何将这些机器连接起来,以创建比任何单个机构都能负担得起的更大的共享存储和计算能力。这就是“网格”这一想法开始成形的时候。

云计算与网格计算

通常,由于一些相似之处,“网格”和“云”这两个术语似乎正在趋同;然而,它们之间的重要差异往往不被理解,这在市场上造成了混淆和混乱。网格计算需要多台计算机的资源来解决单个问题,同时进行。因此,它可能位于云中,也可能不在云中,这取决于你对其的使用类型。关于网格的一个担忧是,如果一个节点上的软件部分出现故障,其他节点上的软件部分也可能出现故障。如果该组件在另一个节点上有一个故障转移组件,这可以缓解问题,但如果组件依赖于其他软件来完成一个或多个网格计算任务,问题仍然可能发生。请看以下截图:

云计算与网格计算

云计算是从网格计算演变而来的,允许按需提供资源。使用云计算,公司可以瞬间扩展到巨大的容量,而无需投资新的基础设施、培训新人员或购买新的软件。

注意事项

网格和云 - 相似之处与不同之处

网格和云之间的区别在于任务计算的方式。在计算网格中,一个大任务被分成许多小部分,并在多台机器上执行。这种特性是网格的基本特征。

云计算旨在让用户能够使用各种服务,而无需投资底层架构。云服务包括通过互联网提供软件、基础设施和存储,可以是单独的组件,也可以是一个完整的平台。

云计算的优势

我们刚刚介绍了云计算的基础知识,现在我们将概述如果您迁移到使用云服务可能会获得的一些好处:

  • 按需服务提供:使用自助服务提供,客户可以快速轻松地访问云服务,无需麻烦。客户只需向服务提供商请求一定数量的计算、存储、软件、流程或其他资源。

  • 弹性:这意味着客户不再需要预测流量,但可以积极且自发地推广他们的网站。为高峰流量进行工程已成为过去式。

  • 成本降低:通过按需购买适量的 IT 资源,组织可以避免购买不必要的设备。对于中小企业来说,使用云计算也可能减少对内部 IT 管理员的需求。

  • 应用程序编程接口(API):API 使得组织的软件能够与云服务交互。这意味着系统管理员可以与其云模型交互。云计算系统通常使用基于 REST 的 API。

尽管云计算带来了许多优势,但也有一些不利因素或潜在风险需要考虑。最引人注目的是,在企业外部处理敏感数据会带来固有的风险。这是因为外包服务绕过了软件公司对其内部程序施加的物理、逻辑和人员控制。此外,当您使用云时,您可能不知道您的数据托管在哪里。实际上,您甚至可能不知道它将存储在哪个国家,这可能导致与当地司法管辖权的问题。

如 Gartner 集团(www.gartner.com)建议,您应始终要求提供商提供有关聘请和监督特权管理员的特定信息。此外,云提供商应提供证据,证明加密方案是由经验丰富的专家设计和测试的。了解提供商是否会代表其客户做出合同承诺,遵守当地隐私要求,也很重要。

云计算选项

云计算可以根据云托管的位置分为以下三种可能的形式,每种选项都带来不同级别的安全性和管理开销:

  • 公有 :当服务和基础设施在异地提供并且通常在多个组织之间共享时使用此选项。公有云通常由外部服务提供商管理。

  • 私有 :此选项提供针对单一组织的 IT 云资源,并按需提供。私有云基础设施在私有网络上维护。

  • 混合 :这种选项是将私有云和公有云作为一个单一实体来管理,允许您将业务的部分方面保持在最有效率的环境中。

采用不同云计算选项中的任何一个的决定是专家之间讨论的问题,并且通常取决于几个关键因素。例如,就安全性而言,尽管公有云提供了一个安全的环境,但私有云提供了一种固有的安全级别,甚至满足最高标准。此外,您可以添加安全服务,例如入侵检测系统IDS)和专用防火墙。对于拥有大量备用容量的良好运行的数据中心的大型组织来说,私有云可能是一个合适的选择。即使您必须添加新软件将数据中心转变为云,使用公有云的成本也更高。

另一方面,就可扩展性而言,私有云的一个负面因素是它们的性能仅限于您的云集群中的机器数量。如果您达到计算能力的极限,则需要添加另一个物理服务器。此外,公有云通常提供一种按使用付费的模式,您按小时支付使用的计算资源。如果您定期启动和关闭开发服务器,这种类型的公用事业定价是经济的。

因此,大多数公共云部署通常用于 Web 服务器或开发系统,在这些系统中,大型组织及其客户的安全和合规性要求不是问题。中型和大型企业通常更倾向于使用私有云,因为它们满足更严格的安全和合规性要求。私有云的缺点是,实施这些云的组织需要专用的高性能硬件。请查看以下图表:

云计算选项

云服务类型

云计算服务可以广泛地分为以下三种类型。这些类型也被称为云服务模型或 SPI 服务模型。

  • 基础设施即服务 (IaaS): 此服务允许您按需启动计算机。对于每台服务器,您可以选择所需的内存大小、处理器数量、硬盘空间大小以及操作系统。它允许您在几分钟内完成所有这些操作,使硬件的获取更加容易、便宜和快速。此服务的知名提供商包括亚马逊 EC2、谷歌计算引擎、Rackspace 和 DigitalOcean。

    注意

    DigitalOcean 在市场上相对较新。您可以在不到 60 秒内启动一个服务器实例!DigitalOcean 的主要卖点是其界面的简单性,这意味着您将不再需要翻阅一页又一页的文档。除此之外,它的价格也非常合理。如果您正在考虑 IaaS 提供商,DigitalOcean 绝对应该被列入您的清单。

  • 平台即服务 (PaaS): 此服务为开发者提供开发平台。最终用户编写自己的代码,PaaS 提供商将代码上传并在网络上展示。

    通过使用平台即服务 (PaaS),您无需投资资金来为开发人员准备项目环境。PaaS 提供商将在网络上提供平台,在大多数情况下,您可以使用浏览器来使用该平台。无需下载任何软件。这种简单性和成本效益的结合赋予了小型和中型企业,甚至个人开发者,启动他们自己的云 SaaS 的能力。

    注意

    PaaS 提供商的例子包括 Facebook 和 OpenShift。Facebook 是一个社交应用平台,第三方可以编写新的应用程序,这些应用程序可供最终用户使用。OpenShift 允许开发者像执行git push命令一样简单地将他们的 WildFly Web 或企业应用程序部署到云中。

  • 软件即服务(SaaS):这种服务基于从服务提供商租赁软件而不是购买软件的概念。软件通常通过浏览器访问。也称为按需软件,由于其高度灵活性、优质服务、增强的可扩展性和低维护成本,目前是云计算中最受欢迎的类型。SaaS 的例子包括 Zoho、Google Docs 和 SalesForce CRM 应用程序。请看以下截图:云服务类型

您可能会想知道是否有可能将某些提供商同时定义为平台软件。答案是肯定的!例如,Facebook 可以被定义为平台(因为服务和应用程序可以通过 Facebook API 提供)和软件(因为它被数百万最终用户使用)。

Red Hat 最初开发 OpenShift 平台是为了在云上运行的 JBoss/WildFly 服务器上部署和管理 Java EE 应用程序。

OpenShift 提供三种软件版本,如下所示:

  • 在线版:这个版本是一个免费、基于云的平台,用于在几分钟内将新的和现有的 Java EE、Ruby、PHP、Node.js、Perl 和 Python 应用程序部署到云上。

  • 起源版:这个版本是软件的免费开源版本。它只提供社区支持。要运行此版本,您需要自己的基础设施。这个版本超出了本书的范围,因此不会涉及。

  • 企业版:这个版本可以下载并在任何地方运行,包括 Amazon、Rackspace 或您自己的基础设施。它包含 Red Hat Enterprise Linux,稳定,并附带 Red Hat 的全面支持。

开始使用 OpenShift Online

OpenShift 允许您在云中创建、部署和管理应用程序。它提供磁盘空间、CPU 资源、内存和网络连接。您可以从包括 Tomcat、WildFly、Jenkins 在内的各种 Web 卡轮中进行选择。您还可以插入数据库卡轮,如 MySQL。根据您构建的应用程序类型,您还可以访问该类型的模板文件系统布局(例如,PHP、WSGI 和 Rack/Rails)。OpenShift 还为您生成有限的 DNS。

要开始使用 OpenShift Online,您需要做的第一件事是创建一个账户。访问 OpenShift 的主页www.openshift.com/,并选择注册。完成在线注册并验证您的电子邮件地址。

在您能够创建应用程序之前,您需要创建一个域名。OpenShift 使用非严格域名(即没有前导点)。

登录您的 OpenShift 账户,并导航到设置标签页。输入您的域名并点击保存。请看以下截图:

开始使用 OpenShift Online

注意

每个账户只能支持单个域名。如果您希望使用多个域名,您需要创建一个具有不同用户名的单独账户。

安装 OpenShift 客户端工具

安装 OpenShift 客户端工具是一个简单的过程。以下指南显示了如何在 Ubuntu 14.04 上安装这些工具。如果您想在不同的 Linux 发行版或不同的操作系统上安装它们,请参阅 Red Hat 文档access.redhat.com/documentation/en-US/OpenShift_Online/2.0/html/Client_Tools_Installation_Guide/chap-OpenShift_Client_Tools.html

  1. 首先,确保您已执行以下命令以获取最新的软件包列表:

    $ sudo apt-get update
    
    
  2. 然后,您需要通过运行以下命令来安装所需的依赖项,rubyrubygemsgit

    $ sudo apt-get install ruby-full rubygems-integration git-core
    
    
  3. 我们现在可以通过运行以下命令来安装客户端工具:

    $ gem install rhc
    
    

    执行此命令后,您应该会看到以下类似的内容:

    Fetching: net-ssh-2.9.1.gem (100%)
    ...
    Fetching: rhc-1.28.5.gem (100%)
    ==========================================================
    If this is your first time installing the RHC tools, please run 'rhc setup'
    ==========================================================
    Successfully installed net-ssh-2.9.1
    ...
    Successfully installed rhc-1.28.5
    10 gems installed
    Installing ri documentation for net-ssh-2.9.1...
    ...
    Installing RDoc documentation for rhc-1.28.5...
    
    

    注意

    在使用客户端工具之前运行设置向导非常重要。未能这样做可能会在以后造成问题。

  4. 要运行设置,请输入以下命令:

    rhc setup
    
    

设置将需要您按照屏幕上出现的顺序输入以下查询的数据:

  • 对于输入服务器主机名,只需按Enter键使用默认值,该默认值是用于 OpenShift Online 的服务器。

  • 对于输入用户名和密码,请输入您的账户用户名和密码。

  • 如果您的系统上没有 SSH 密钥,将会自动生成一个。您将被询问是否要将密钥上传到服务器。输入yes

  • 如果您之前没有创建域名,现在将会提示您添加一个域名。

从不同的计算机访问您的 OpenShift 账户

您的计算机与 OpenShift 之间的通信是通过 SSH 使用安全密钥进行的。为了从不同的机器使用您的域名,只需将 OpenShift 客户端工具下载并安装到您的另一台计算机上。当您运行工具设置时,您的计算机的密钥将被添加到服务器上的密钥中。

要撤销访问权限,您需要删除该计算机的密钥。您可以通过登录到 OpenShift,导航到设置,然后滚动到公钥处来完成此操作。现在,删除与您想要撤销访问权限的计算机相关的密钥。

创建我们的第一个 OpenShift 应用程序

在我们开发一个要在 OpenShift 上运行的应用程序之前,我们首先应该定义一些 OpenShift 术语:

  • 应用程序:这显然是您将部署到 OpenShift 的应用程序。

  • Gear:这是包含您的服务器以及运行应用程序所需的各种资源(如 RAM、处理器和硬盘空间)的容器。

  • 卡式盒:卡式盒是一个提供特定功能的插件。例如,您可以选择添加到您的齿轮中的 WildFly 卡式盒和数据库卡式盒。

安装您的第一个卡式盒

要查看所有可用的卡式盒,请运行以下命令:

$ rhc cartridge list

创建应用程序的语法如下:

$ rhc app create app_name cartridge_name

在撰写本文时,在卡式盒列表中尚无 WildFly 卡式盒可用。为此示例,我将使用 GitHub 上可用的卡式盒(github.com/openshift-cartridges/openshift-wildfly-cartridge)。导航到您希望代码所在的位置的文件夹。如果您使用的是 Eclipse,您可能想将当前目录切换到您的项目工作区文件夹。使用前面的语法,但将卡式盒名称替换为卡式盒 URL,我们将创建应用程序,如下所示:

$ rhc app create wildfly https://cartreflect-claytondev.rhcloud.com/reflect?github=openshift-cartridges/openshift-wildfly-cartridge#WildFly8

运行此命令后,控制台将打印出大量信息。我们将逐个处理这些输出。第一条信息是与卡式盒和齿轮相关的详细信息。我们可以看到卡式盒是从哪个 URL 克隆的,齿轮大小和域名:

Application Options
-------------------
Domain:     chrisritchie
Cartridges: https://cartreflect-claytondev.rhcloud.com/reflect?github=openshift-cartridges/openshift-wildfly-cartridge#WildFly8
Gear Size:  default
Scaling:    no

下一个部分显示了应用程序正在创建,并且正在齿轮上部署一个工件:

Creating application 'wildfly' ... Artifacts deployed: ./ROOT.war
done

接下来打印出管理控制台的相关详细信息,如下所示:

 WildFly 8 administrator added.  Please make note of these credentials:
 Username: admin6vIBvE6
 Password: B_vh3CA5v4Dc
 run 'rhc port-forward wildfly to access the web admin area on port 9990.
Waiting for your DNS name to be available ... done

下面的部分显示了远程 Git 仓库正在克隆到您的本地硬盘。一旦您允许,齿轮的 SSH 密钥将被添加到您的known_hosts文件中:

Cloning into 'wildfly'...
The authenticity of host 'wildfly-chrisritchie.rhcloud.com (50.16.172.242)' can't be established.
RSA key fingerprint is cf:ee:77:cb:0e:fc:02:d7:72:7e:ae:80:c0:90:88:a7.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'wildfly-chrisritchie.rhcloud.com,50.16.172.242' (RSA) to the list of known hosts.
Your application 'wildfly' is now available.

最后,您的应用程序 URL、远程 Git 仓库和 SSH 位置都会打印出来,如下所示:

 URL:        http://wildfly-chrisritchie.rhcloud.com/
 SSH to:     53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com
 Git remote: ssh://53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com/~/git/wildfly.git/

Run 'rhc show-app wildfly' for more details about your app.

现在,您可以验证服务器是否正在运行,并且可以通过将浏览器指向前面输出中指定的 URL 来访问已部署的应用程序。请查看以下屏幕截图:

安装您的第一个卡式盒

现在,让我们将注意力转向您计算机上的本地仓库。

提示

您可以通过选择文件 | 导入 | 项目GIT | 现有本地仓库 | 添加将仓库导入 Eclipse。然后,浏览到您的git仓库位置,将其作为新的 Maven 项目导入,以便 Eclipse 可以自动为您生成项目配置文件。

如果您查看以下屏幕截图所示的git仓库结构,您将看到src文件夹遵循典型的 Maven 项目结构,适用于 Web 应用程序:

安装您的第一个卡式盒

如果您通过命令行检查根文件夹,您也会注意到隐藏的文件夹。有两个重要的隐藏文件夹。.git 文件夹包含所有版本信息以及 Git 配置。.openshift 文件夹包含 OpenShift 的各种配置。请查看以下截图:

安装您的第一个插件

deployments 文件夹执行与 JBOSS_HOME/standalone/deployments 目录相同的任务。放置在此处的应用程序在将仓库推送到远程 Git 仓库时将自动部署。

理解工作流程

在我们开始编写实际应用程序的代码之前,我们需要了解工作流程以及代码是如何部署到服务器的。以下是基本步骤:

  1. 修改本地 Git 仓库中的源代码。

  2. 将任何部署,如 JDBC 连接器,添加到 deployments 文件夹。此步骤是可选的。

  3. 将所有文件阶段到本地仓库,准备提交。

  4. 将文件提交到本地仓库。

  5. 将更改推送到远程仓库。这将触发您的设备上的部署。您不需要将应用程序 WAR 添加到 deployments 文件夹。

构建应用程序

因此,现在我们需要创建我们自己的应用程序。对于第一个示例,我们将部署一个简单的服务,该服务将文本区域内的文本下载为 PDF 文件。此应用程序由一个 servlet 组成,该 servlet 使用 iText 库(可在 itextpdf.com/download.php 获取)将请求转换为 PDF 响应。以下是 servlet:

package com.packtpub.chapter11;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;

@WebServlet("/convert")
public class TextToPdf extends HttpServlet {

    public void init(ServletConfig config) throws ServletException{
        super.init(config);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        doPost(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String text = request.getParameter("text");
        response.setContentType("application/pdf");
        Document document = new Document();
        try{
            PdfWriter.getInstance(document, response.getOutputStream());
            document.open();
            document.add(new Paragraph(text));
            document.close(); 
        }catch(DocumentException e){
            e.printStackTrace();
        }
    }
}

public void init(ServletConfig config) throws ServletException{
    super.init(config);
}

public void doGet(HttpServletRequest request, 
    HttpServletResponse response) throws ServletException, IOException{
    doPost(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
    String text = request.getParameter("text");
    response.setContentType("application/pdf");  
    Document document = new Document();
    try{
        PdfWriter.getInstance(document, response.getOutputStream());  
    document.open();

    document.add(new Paragraph(text));

    document.close();
    }catch(DocumentException e){
        e.printStackTrace();
    }
}

我们还需要将 itextpdf 库添加到项目的 pom.xml 文件中,以便代码可以编译。请查看以下代码:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.2</version>
</dependency>

此外,我们还需要一个包含文本区域的 HTML/JSP 页面。此代码位于 createpdf.html 文件中:

<form action="TextToPdf" method="post">
    <textarea cols="80" rows="5" name="text">
        This text will be converted to PDF.
    </textarea>
    <input type="submit" value="Convert to PDF">
</form>

我们现在已经完成了应用程序。我们需要使用以下 git add 命令将应用程序添加并提交到我们的本地 Git 仓库:

$ git add *

然后,输入以下 git commit 命令:

$ git commit -m "Initial commit of wildfly app"

最后,您需要将本地更改推送到位于您设备上的远程仓库,如下所示:

$ git push

这将推送您的代码并触发各种 Git 钩,导致您的代码被编译、打包和部署,如以下输出所示。为了简洁,省略了构建输出。

Counting objects: 19, done.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (12/12), 1.58 KiB | 0 bytes/s, done.
Total 12 (delta 3), reused 0 (delta 0)
remote: Stopping wildfly cart
remote: Sending SIGTERM to wildfly:72039 ...
remote: Building git ref 'master', commit 7d63607
...
remote: [INFO] Scanning for projects...
remote: [INFO]                                                                         
remote: [INFO] ------------------------------------------------------------------------
remote: [INFO] Building wildfly 1.0
remote: [INFO] ------------------------------------------------------------------------
...
remote: [INFO] ------------------------------------------------------------------------
remote: [INFO] BUILD SUCCESS
remote: [INFO] ------------------------------------------------------------------------
...
remote: Preparing build for deployment
remote: Deployment id is aed5acfd
remote: Activating deployment
remote: Deploying WildFly
remote: Starting wildfly cart
...
remote: CLIENT_MESSAGE: Artifacts deployed: ./ROOT.war
remote: -------------------------
remote: Git Post-Receive Result: success
remote: Activation status: success
remote: Deployment completed with status: success
To ssh://53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com/~/git/wildfly.git/
   76af36f..7d63607  master -> master

我们现在可以最终通过 wildfly-chrisritchie.rhcloud.com/createpdf.html 访问我们的应用程序。请查看以下截图:

构建应用程序

在输入一些文本并点击 转换为 PDF 按钮后,将下载包含文本的 PDF 文件,如下所示:

构建应用程序

启动我们的应用程序会生成一个 PDF 文件作为结果——你的第一个云应用程序!现在我们已经将一个简单的应用程序部署到你的 OpenShift 组件中,在下一节中,我们将向你展示如何管理你的 OpenShift 应用程序并介绍一些高级功能。

查看 OpenShift 服务器日志文件

在某个时候,你可能需要查看服务器端的情况。也许你的应用程序部署失败,或者你在遇到错误后需要查看日志。你可以通过以下几种方式查看 OpenShift 服务器日志:

  • 使用客户端工具跟踪日志文件

  • 通过 SSH 进入组件

跟踪日志文件

跟踪应用程序服务器日志很简单。你只需要运行 rhc tail 命令。例如,要查看我们称为 wildfly 的示例应用程序的日志,你需要执行以下操作:

$ rhc tail -a wildfly

这将打印出日志文件的最新条目,如下所示:

2014-08-11 22:11:39,270 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-4) JBAS017534: Registered web context: /
2014-08-11 22:11:41,411 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 54) JBAS018559: Deployed "ROOT.war" (runtime-name : "ROOT.war")
2014-08-11 22:12:26,849 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015961: Http management interface listening on http://127.6.97.1:9990/management
2014-08-11 22:12:26,862 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015951: Admin console listening on http://127.6.97.1:9990
2014-08-11 22:12:26,864 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.1.0.Final "Kenny" started in 219903ms - Started 299 of 429 services (177 services are lazy, passive or on-demand)

要退出日志,只需按下 Ctrl + C

通过 SSH 查看日志

使用 rhc tail 命令只在一部分时间内有用。很可能会想查看整个日志或搜索日志。为此,你需要 SSH 进入组件。我们使用 -a 开关指定应用程序名称,如下所示。

$ rhc ssh -a wildfly

通过输入 ls app_name,你可以看到目录结构类似于 WildFly 安装。现在你可以使用 less 命令查看你的文件,这意味着你可以在文件中进行导航和搜索:

$ less wildfly/standalone/logs/server.log
...

==> example/logs/server.log <==
14:41:18,706 INFO  [org.jboss.as.connector.subsystems.datasources] (Controller Boot Thread) Deploying JDBC-compliant driver class org.h2.Driver (version 1.2)
14:41:18,712 INFO  [org.jboss.as.connector.subsystems.datasources] (Controller Boot Thread) Deploying non-JDBC-compliant driver class com.mysql.jdbc.Driver (version 5.1)
14:41:18,732 INFO  [org.jboss.as.clustering.infinispan.subsystem] (Controller Boot Thread) Activating Infinispan subsystem.
14:41:18,860 INFO  [org.jboss.as.naming] (Controller Boot Thread) Activating Naming Subsystem
14:41:18,877 INFO  [org.jboss.as.naming] (MSC service thread 1-1) Starting Naming Service 

小贴士

使用 less 命令比使用 tail 命令提供了更多的控制。按下 Shift + F 开始跟踪文件,按下 Ctrl + C 停止跟踪文件。反斜杠允许你向后搜索,而问号允许你向前搜索文件。这使得查找异常和错误的发生变得容易。

管理 OpenShift 中的应用程序

起初,你可能觉得在远程服务器上管理应用程序可能很困难。一旦你学会了管理应用程序的命令,这种担忧应该会大大减少。

为了控制你的应用程序,你可以使用 rhc app 命令,它接受要执行的操作和 -a 命令,该命令指定应用程序名称。例如:

$rhc app restart -a app_name

以下表格显示了可用于管理应用程序的命令列表。你可以通过命令行使用 --help 标志查看可用选项:

选项 描述
start 启动应用程序
stop 停止当前运行的应用程序
force-stop 杀死应用程序进程
restart 重新启动应用程序
reload 重新加载应用程序
delete 删除应用程序
configure 配置应用程序属性
create 创建应用程序
deploy 部署应用程序
scale-up 扩展应用程序组件
scale-down 缩小应用程序组件
show 显示应用程序信息
tidy 删除应用程序的日志文件和临时文件

如果您想删除我们之前创建的应用程序,您将使用以下命令:

$ rhc app delete -a wildfly

This is a non-reversible action! Your application code and data will be permanently deleted if you continue!

Are you sure you want to delete the application 'atestapp'? (yes|no): yes

Deleting application 'wildfly' ... deleted

配置您的应用程序

当您创建一个应用程序时,您将有一个本地副本的仓库,其中包含您的应用程序代码和 WildFly 服务器的 deployments 文件夹。除此之外,在您的 Git 仓库中还有几个隐藏的文件夹。第一个是 .git 文件夹,其中包含所有与 Git 相关的配置。第二个文件夹是 .openshift。以下是 .openshift 文件夹的内容:

chris-macbook:.openshift chris$ ls -lah
drwxr-xr-x   3 chris  staff   102B Aug 11 21:20 action_hooks
drwxr-xr-x   4 chris  staff   136B Aug 11 21:20 config
drwxr-xr-x   8 chris  staff   272B Aug 11 21:20 cron
drwxr-xr-x   3 chris  staff   102B Aug 11 21:20 markers

action_hooks 文件夹是开发者可以放置将在 OpenShift 构建生命周期中执行的动作钩子脚本的地方。

注意

您可以创建一个构建脚本以执行应用程序初始化,例如创建表或设置变量。有关支持的动作钩子的详细信息,请参阅openshift.github.io/documentation/oo_user_guide.html#build-action-hooks中的文档。

cron 文件夹允许开发者向 gear 添加 cron 作业。添加的脚本将根据它们是否放在 minutelyhourlydailyweeklymonthly 文件夹中来安排。

markers 文件夹可以用来设置各种设置。例如,skip_maven_build 标记文件将指示 Maven 编译器跳过构建过程。最后但同样重要的是,config 文件夹具有以下结构:

chris-macbook:.openshift chris$ ls -lah config/
drwxr-xr-x  3 chris  staff   102B Aug 11 21:20 modules
-rw-r--r--  1 chris  staff    29K Aug 11 21:20 standalone.xml

如您所预期,standalone.xml 文件是 WildFly 应用程序的配置文件。modules 文件夹是您可以添加自己的模块的地方,就像在 WildFly 的本地安装中一样。

我们只是触及了 OpenShift 可以做什么的基本知识。要深入了解,请参阅openshift.github.io/documentation/oo_user_guide.html中的 OpenShift 用户指南。

添加数据库卡式

每个企业应用程序都需要某种类型的存储来存储其数据。OpenShift 允许您在创建应用程序后添加数据库卡式。要查看可能的卡式列表,您可以发出以下命令:

$ rhc cartridge list

在输出的列表中,您将看到各种数据库供应商的卡式选项。在这个例子中,我们将向我们的应用程序添加 MySQL 数据库卡式。为此,运行以下命令:

$ rhc cartridge add mysql-5.5

注意

如果您将应用程序配置为可扩展的,数据库卡式将被安装到一个新的 gear 中。如果您的应用程序未配置为可扩展的,它将被添加到与您的应用程序相同的 gear 中。这是为了确保在您扩展或缩小 gears 时,您的数据库不会受到影响。

输出将打印与我们新数据库相关的信息,例如根密码、连接 URL 等:

chris-macbook:wildfly chris$ rhc cartridge add mysql-5.5

Adding mysql-5.5 to application 'wildfly' ... done

mysql-5.5 (MySQL 5.5)
---------------------
 Gears:          Located with wildfly-wildfly-8
 Connection URL: mysql://$OPENSHIFT_MYSQL_DB_HOST:$OPENSHIFT_MYSQL_DB_PORT/
 Database Name:  wildfly
 Password:       y-rftt5LUl6j
 Username:       adminWfn4mG6

MySQL 5.5 database added.  Please make note of these credentials:

 Root User: adminWfn4mG6
 Root Password: y-rftt5LUl6j
 Database Name: wildfly

Connection URL: mysql://$OPENSHIFT_MYSQL_DB_HOST:$OPENSHIFT_MYSQL_DB_PORT/

You can manage your new MySQL database by also embedding phpmyadmin.
The phpmyadmin username and password will be the same as the MySQL credentials above.

RESULT:

Mysql 5.1 database added.  Please make note of these credentials:
 Root User: admin
 Root Password: SH-v4VuAZ_Se
 Database Name: example

这的好处是您不需要进一步配置您的 standalone.xml 文件,因为当您添加 MySQL 卡时,所有环境变量都已为您设置。您可以使用 java:jboss/datasources/MysqlDS 的 JNDI 命名空间立即访问数据源。查看 standalone.xml 文件中的数据源配置。您将看到所有属性都是外部环境变量,如下所示:

<datasource jndi-name="java:jboss/datasources/MySQLDS" enabled="${mysql.enabled}" use-java-context="true" pool-name="MySQLDS" use-ccm="true">
    <connection-url>jdbc:mysql://${env.OPENSHIFT_MYSQL_DB_HOST}:${env.OPENSHIFT_MYSQL_DB_PORT}/${env.OPENSHIFT_APP_NAME}</connection-url>
    <driver>mysql</driver>
    <security>
        <user-name>${env.OPENSHIFT_MYSQL_DB_USERNAME}</user-name>
        <password>${env.OPENSHIFT_MYSQL_DB_PASSWORD}</password>
    </security>
    <validation>
        <check-valid-connection-sql>SELECT 1</check-valid-connection-sql>
        <background-validation>true</background-validation>
        <background-validation-millis>60000</background-validation-millis>
        <!--<validate-on-match>true</validate-on-match>-->
    </validation>
    <pool>
        <flush-strategy>IdleConnections</flush-strategy>
    </pool>
</datasource>

要删除数据库卡,从而禁用它,您可以简单地运行以下命令:

$ rhc cartridge remove mysql-5.5

使用 OpenShift 工具和 Eclipse

除了客户端工具外,还有一个适用于 Eclipse 的插件,允许您与 OpenShift 集成。如果您更喜欢图形界面而不是命令行,应考虑此插件。

安装 OpenShift 工具需要与我们在 第二章 中安装 WildFly 插件相同的步骤,即 配置 WildFly 核心子系统。按照以下步骤安装 OpenShift 工具:

  1. 在 Eclipse 中,通过点击 帮助 | Eclipse Marketplace 来进入市场。

  2. 搜索与您的 Eclipse 版本匹配的 JBoss Tools 版本。

  3. 点击 安装。您将看到可用的完整功能列表。

  4. 您现在可以选择 JBoss OpenShift Tools 以及您想要的任何其他功能,如以下截图所示:使用 OpenShift 工具和 Eclipse

  5. 点击 确认,接受许可条款,然后点击 完成

当 Eclipse 重新启动时,您将能够创建新应用程序或导入现有应用程序。创建新应用程序很简单。执行以下步骤:

  1. 导航到 文件 | 新建 | OpenShift 应用程序。请查看以下截图:使用 OpenShift 工具和 Eclipse

  2. 您将看到一个弹出窗口,允许您输入您的 OpenShift 账户的用户名和密码。输入您的详细信息,然后点击 确定

  3. 下一屏将允许您从 OpenShift 云中下载现有的应用程序或创建一个新的。在这里我们将创建一个新的,如果您需要下载现有的应用程序,请调查该选项。在可能的快速启动卡中查找 WildFly 8。请查看以下截图:使用 OpenShift 工具和 Eclipse

  4. 点击 下一步,输入您的应用程序名称,并选择您的齿轮配置文件。请查看以下截图:使用 OpenShift 工具和 Eclipse

  5. 点击 下一步,然后再次点击 下一步。您的新应用程序现在已完成,齿轮已配置。

本教程旨在简要介绍 OpenShift Tools 插件。如果您对使用 OpenShift Tools 感兴趣,请参阅docs.jboss.org/tools/4.1.0.Final/en/User_Guide/html_single/index.html#chap-OpenShift_Tools在线文档。

应用程序扩展

迄今为止,我们已介绍了一些 OpenShift 平台最基本的功能。尽管在一个章节内无法涵盖所有可用选项,但还有一个需要介绍的功能,那就是应用程序的扩展。

要使应用程序可扩展,您必须在创建应用程序时使用以下命令传递-s开关:

$ rhc app create app_name type -s

注意

无法将不可扩展的应用程序变为可扩展。为此,您需要拍摄应用程序的快照,启动一个新的可扩展应用程序,然后将您的代码推送到它。

一旦创建了可扩展的应用程序,当某一时间段内并发请求数超过最大并发请求数的 90%时,它将自动向集群添加节点。当连续三个时间段内并发请求数低于最大并发请求数的 49.9%时,它将自动缩小规模。

您也可以通过命令行手动扩展应用程序。要手动扩展应用程序,请运行以下命令:

$ 53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com "haproxy_ctld -u"

要手动缩小应用程序的规模,请运行以下命令:

$ 53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com "haproxy_ctld -d"

最后,您可能想要禁用或启用自动扩展。这也可以通过命令行实现。要停止自动扩展,请运行以下命令:

$ 53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com "haproxy_ctld_daemon stop"

要启动自动扩展,请运行以下命令:

$ 53e905324382ecc7c30001d0@wildfly-chrisritchie.rhcloud.com "haproxy_ctld_daemon start"

摘要

在本章中,我们探讨了在公司的自有基础设施上托管应用程序的传统方法的替代方案。OpenShift 平台提供了免费和付费版本的 PaaS,使开发者能够将应用程序部署到云端,无需担心下载和管理堆栈、编写脚本或安装代理。

OpenShift 平台与其他云解决方案(如 MS Azure)有一些相似之处。就像 Azure 一样,OpenShift 是由供应商管理和运行的云服务。OpenShift 提供了快速从多个卡宾中选择的能力,每个卡宾都连接到运行您的应用程序所需的一种资源。使用单个 Git 命令,您的源代码被推送到齿轮,然后您的应用程序被构建并部署到服务器。

管理您的 OpenShift 齿轮有几种方法。首先,您可以通过命令行来管理它们。这是最佳选择,因为您对您的齿轮有完全的控制权。其次,是 Web 界面,它具有有限的功能,但可以快速创建新应用程序。最后,是 OpenShift Tools,它是 JBoss Tools 插件套件的一部分,用于 Eclipse。

使用 OpenShift 时有三种选项可供选择。OpenShift Online 是一款提供免费和基于订阅的服务的产品。所有齿轮都托管在公有云上。OpenShift Enterprise 允许您下载一个稳定且受支持的 OpenShift 版本,以便在您自己的硬件上运行。最后,如果您想要最新的功能(仅提供社区支持)或想要为 OpenShift 的开发做出贡献,那么有 OpenShift Origin。

附录 A. CLI 参考

为了保持简单,以下是最常用的命令和操作的快速参考,用于通过 CLI 管理应用程序服务器。为了简洁起见,仅提及 Linux 环境中的jboss-cli.sh脚本。Windows 用户只需将此文件替换为等效的jboss-cli.bat文件。

启动选项:

以下命令可用于以非交互方式启动 CLI:

  • 将脚本命令传递给jboss-cli shell:

    ./jboss-cli.sh --connect command=:shutdown
    
    
  • 执行文件中的 CLI shell:

    ./jboss-cli.sh --file=test.cli
    
    

通用命令

以下命令可用于收集系统信息并设置特定的服务器属性:

  • 显示环境信息:

    version
    
    
  • 显示 JNDI 上下文:

    /subsystem=naming:jndi-view
    
    
  • 显示 XML 服务器配置:

    :read-config-as-xml
    
    
  • 显示容器中注册的服务及其状态:

    /core-service=service-container:dump-services
    
    
  • 设置系统属性:

    /system-property=property1:add(value="value")
    
    
  • 显示系统属性:

    /system-property=property1:read-resource
    
    
  • 显示所有系统属性:

    /core-service=platform-mbean/type=runtime:read-attribute(name=system-properties)
    
    
  • 删除系统属性:

    /system-property=property1:remove
    
    
  • 更改套接字绑定端口(例如,http端口):

    /socket-binding-group=standard-sockets/socket-binding=http:write-attribute(name="port", value="8090")
    
    
  • 显示公共接口的 IP 地址:

    /interface=public:read-attribute(name=resolved-address)
    
    

域模式命令

在主机名(以及如果需要,服务器名)前加上前缀以指示你正在向哪个主机(或服务器名)发出命令。示例:

  • 显示主机 master 的 XML 配置:

    /host=master:read-config-as-xml
    
    
  • 显示运行在主机master上的server-one服务器的公共接口的 IP 地址:

    /host=master/server=server-one/interface=public:read-attribute(name=resolved-address)
    
    

与应用程序部署相关的命令:

CLI 也可以用于部署应用程序。CLI 假定MyApp.war文件位于jboss-cli工作目录之外。以下是部署命令的快速参考:

  • 已部署应用程序列表:

    deploy
    
    
  • 在独立服务器上部署应用程序:

    deploy MyApp.war
    
    
  • 在独立服务器上重新部署应用程序:

    deploy -f MyApp.war
    
    
  • 从所有服务器组卸载应用程序:

    undeploy MyApp.war
    
    
  • 在所有服务器组上部署应用程序:

    deploy MyApp.war --all-server-groups
    
    
  • 在一个或多个服务器组上部署应用程序(用逗号分隔):

    deploy application.ear --server-groups=main-server-group
    
    
  • 从所有服务器组卸载应用程序:

    undeploy application.ear --all-relevant-server-groups
    
    
  • 从一个或多个服务器组卸载应用程序:

    undeploy as7project.war --server-groups=main-server-group
    
    
  • 不删除内容地卸载应用程序:

    undeploy application.ear --server-groups=main-server-group --keep-content
    
    

JMS

在这里,你可以找到可以用于创建/删除 JMS 目的地的 JMS 命令:

  • 添加 JMS 队列:

    jms-queue add –-queue-address=queue1 --entries=queues/queue1
    
    
  • 删除 JMS 队列:

    jms-queue remove --queue-address=queue1
    
    
  • 添加 JMS 主题:

    jms-topic add –-topic-address=topic1 --entries=topics/topic1
    
    
  • 删除 JMS 主题:

    jms-topic remove --topic-address=topic1
    
    

数据源

这是一个可以使用数据源别名发出的便捷数据源命令列表:

  • 添加数据源:

    data-source add --jndi-name=java:/MySqlDS --name=MySQLPool --connection-url=jdbc:mysql://localhost:3306/MyDB --driver-name=mysql-connector-java-5.1.16-bin.jar --user-name=myuser --password=password –max-pool-size=30
    
    
  • 删除数据源:

    data-source remove --name=java:/MySqlDS
    
    

数据源(使用资源操作)

您还可以使用数据源子系统上的操作对数据源进行操作:

  • 列出已安装的驱动程序:

    /subsystem=datasources:installed-drivers-list
    
    
  • 添加数据源:

    data-source add --jndi-name=java:/MySqlDS --name=MySQLPool --connection-url=jdbc:mysql://localhost:3306/MyDB --driver-name=mysql-connector-java-5.1.30-bin.jar --user-name=myuser --password=password --max-pool-size=30
    
    
  • 使用操作添加 XA 数据源:

    xa-data-source add --name=MySQLPoolXA --jndi-name=java:/MySqlDSXA --driver-name=mysql-connector-java-5.1.30-bin.jar -xa-datasource-properties=[{ServerName=localhost}{PortNumber=3306}]
    
    
  • 使用操作删除数据源:

    /subsystem=datasources/data-source=testDS:remove
    
    

Mod_cluster

可以使用以下 CLI 操作执行 Mod_cluster 管理:

  • 列出已连接的代理:

    /subsystem=modcluster:list-proxies
    
    
  • 显示代理信息:

    /subsystem=modcluster:read-proxies-info
    
    
  • 向集群添加代理:

    /subsystem=modcluster:add-proxy(host= CP15-022, port=9999)
    
    
  • 删除代理:

    /subsystem=modcluster:remove-proxy(host=CP15-022, port=9999)
    
    
  • 添加 Web 上下文:

    /subsystem=modcluster:enable-context(context=/myapp, virtualhost=default-host)
    
    
  • 禁用 Web 上下文:

    /subsystem=modcluster:disable-context(context=/myapp, virtualhost=default-host)
    
    
  • 停止 Web 上下文:

    /subsystem=modcluster:stop-context(context=/myapp, virtualhost=default-host, waittime=50)
    
    

批处理:

这是使用 CLI 处理批处理的方法:

  • 开始批处理:

    batch
    
    
  • 暂停批处理:

    holdback-batch
    
    
  • 暂停后继续批处理:

    batch
    
    
  • 当前批处理堆栈上的命令列表:

    list-batch
    
    
  • 清除命令的批处理会话:

    clear-batch
    
    
  • 在堆栈上执行批处理命令:

    run-batch
    
    

快照

快照允许存储和检索服务器配置:

  • 捕获配置快照:

    :take-snapshot
    
    
  • 列出可用的快照:

    :list-snapshots
    
    
  • 删除快照:

    :delete-snapshot(name="20140814-234725965standalone-full-ha.xml")
    
    
posted @ 2025-09-12 13:56  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报