JBoss-EAP6-高可用指南-全-
JBoss EAP6 高可用指南(全)
原文:
zh.annas-archive.org/md5/d6bb48b9e141c33ca552b41e5089b7de译者:飞龙
前言
高可用性是一个广泛讨论的话题,它涉及到项目的部署和开发。在这本书中,我想探讨关于集群、负载均衡、故障转移和会话复制的话题。
高可用性也是一个非常有趣的话题,开源社区提供的技术特别有趣。在本书中,我们将学习如何使用 JBoss EAP6 与其他 JBoss 和 Apache 社区工具一起构建一个高可用性系统。
在本书中,我们将使用以下项目:JBoss EAP 6、Apache httpd、mod_jk 和 mod_cluster。当我编写这本书时,JBoss EAP 6.1.0.Final 是基于 JBoss AS 7.2.x 的最新产品版本,可以从 JBoss 社区免费下载。这个版本非常稳定,具有集群功能,因此我们将在本书中使用它。
自从 AS 8.x 以来,JBoss AS 的项目名称已更名为 WildFly。尽管项目名称已更改,但其设计变化不大,你可以将本书中的大部分知识用于 WildFly 的未来版本。
JBoss EAP6 提供了一个域管理功能,可以帮助我们集中管理多个服务器。这个功能在集群环境中非常有用,因为我们不需要分别管理每个服务器。我们将在书中检查这个功能。
在商业应用中,传输安全性通常被认为是至关重要的。在这本书中,我将介绍将 SSL 应用于集群环境的方法。
本书涵盖的内容
第一章, JBoss EAP6 概述,教你如何下载和安装 JBoss EAP6,介绍 JBoss EAP6 的启动模式,并涵盖域管理功能的基本用法。
第二章, 使用 JBoss EAP6,更详细地介绍了使用 EAP6 管理控制台,并解释了 EAP6 管理模型的设计。
第三章, 设置 JBoss EAP6 集群,指导你如何正确设置 EAP6 服务器,以便它们可以形成一个集群。
第四章, 使用 mod_jk 进行负载均衡,展示了如何将 mod_jk 用作 EAP6 集群的负载均衡器。
第五章, 使用 mod_cluster 进行负载均衡,讨论了如何将 mod_cluster 用作 EAP6 集群的负载均衡器。
第六章, 使用 SSL 进行集群,展示了如何在集群环境中启用 SSL,并教你如何将 SSL 与 mod_jk 一起使用。
第七章, 使用 SSL 配置 mod_cluster,展示了如何将 SSL 与 mod_cluster 一起使用。
第八章,开发分布式应用程序,讨论了如何借助 JavaEE 技术开发可分发应用程序并将其部署到 EAP6 集群中。
附录,WildFly 故障排除,展示了如何在运行时调试 WildFly 服务器。这一章作为附加章节提供,可以从www.packtpub.com/sites/default/files/downloads/2432OS_Appendix.pdf下载。
您需要这本书什么
阅读这本书需要一些关于 Linux/Unix 的基本知识。您可能需要遵循书中的 shell 命令来正确配置服务器。
强烈建议您对 IP 多播有一定的了解。因为许多集群功能依赖于 IP 多播,有了这方面的知识,您可以更好地理解 EAP6 集群的设计。
如果您想遵循第八章,开发分布式应用程序中的说明,您可能需要一些关于 EJB 和 Servlet 开发的基本知识。此外,对 Maven 使用的了解也是首选的。
第六章,使用 SSL 进行集群,和第七章,使用 SSL 配置 mod_cluster,主要关注在集群中应用 SSL。如果您想遵循这两章中的说明,需要了解 SSL/TLS 技术的基本知识。
当您遵循这本书中的说明时,请关闭您机器的网络防火墙,以防它阻止集群所需的重要端口。如果您使用 Linux 作为工作环境,请禁用 SELinux,因为它将对集群功能产生很大影响。当您完全理解了 EAP6 集群功能后,您可以重新启用这些保护措施,并正确配置它们以与 EAP6 集群一起工作。
这本书面向谁
JBoss EAP6 管理员和 JavaEE 开发者是这本书的主要读者。任何想了解 JBoss 社区提供的最酷技术的读者都建议阅读这本书。我希望这本书不仅仅是一个逐步教程,所以我包括了一些关于 JBoss EAP6 及其相关项目设计的讨论。
惯例
在这本书中,您会发现许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。
文本中的代码单词、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL 和用户输入如下所示:“默认配置文件是standalone.xml。”
代码块设置如下:
<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>
任何命令行输入或输出都如下所示:
$ ./standalone.sh
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“要取消部署项目,请首先点击启用/禁用,然后点击删除”。
注意
警告或重要注意事项以如下框中的方式显示。
小贴士
小技巧和技巧显示如下。
读者反馈
读者反馈始终受到欢迎。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正从中获得最大收益的标题非常重要。
要发送一般反馈,只需发送电子邮件到 <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,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过发送电子邮件到 <copyright@packtpub.com> 并附上涉嫌侵权材料的链接与我们联系。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果你在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。
第一章:JBoss EAP6 概述
在本章中,我们将学习关于高可用性的基本概念,并概述 JBoss EAP6 在这个领域为我们提供的功能。然后我们将学习如何安装 JBoss EAP6 以及它的基本用法。
理解高可用性
要理解“高可用性”这个术语,以下是来自维基百科的定义:
“高可用性是一种系统设计方法和相关的服务实现,它确保在合同测量期间将满足预定的操作性能水平。用户希望他们的系统,例如医院、生产计算机和电网,能够随时为他们提供服务。 ... 如果用户无法访问系统,则称其为不可用。”
在 IT 领域,当我们提到“高可用性”这个词时,我们通常想到的是服务器的正常运行时间,而诸如聚类和负载均衡等技术可以用来实现这一点。
聚类意味着使用多个服务器形成一个组。从用户的角度来看,他们将集群视为一个单一实体,并像访问单个点一样访问它。以下图显示了集群的结构:

为了实现之前提到的目标,我们通常使用集群的控制器,即负载均衡器,位于集群前面。它的任务是接收和调度用户请求到集群内的节点,节点将执行处理用户请求的真正工作。节点处理完用户请求后,响应将被发送到负载均衡器,负载均衡器将把它发送回用户。以下图显示了工作流程:

除了负载均衡用户请求外,集群系统还可以在其内部进行故障转移。
提示
故障转移意味着当一个节点崩溃时,负载均衡器可以切换到其他正在运行的节点来处理用户请求。
在集群中,一些节点可能在运行时失败。如果发生这种情况,应该将失败的节点的请求重定向到健康的节点。这个过程在以下图中显示:

为了使故障转移成为可能,集群中的节点应该能够从一端复制用户数据到另一端。
提示
在 JBoss EAP6 中,Infinispan 模块,这是 JBoss 社区提供的数据网格解决方案,负责 Web 会话复制。
如果一个节点失败,用户请求可以被重定向到另一个节点;然而,与用户的会话不会丢失。以下图说明了故障转移:

为了实现之前提到的目标,JBoss 社区为我们提供了一套强大的工具。在下一节中,我们将对其进行概述。
JBoss EAP6 高可用性
作为 Java EE 应用服务器,JBoss EAP6 使用了来自不同开源项目的模块:
-
Web 服务器(JBossWeb)
-
EJB(JBoss EJB3)
-
Web 服务(JBossWS/RESTEasy)
-
消息传递(HornetQ)
-
JPA 和事务管理(Hibernate/Narayana)
如我们所见,JBoss EAP6 使用了更多的开源项目,每个部分可能都有其自己的考虑,以实现高可用性的目标。现在让我们简要介绍一下这些部分与高可用性的关系:
JBoss Web、Apache httpd、mod_jk 和 mod_cluster
对于 Web 服务器的集群可能是最热门的话题,并且被大多数人所理解。市场上有很多好的解决方案。
对于 JBoss EAP6,它采用的解决方案是使用 Apache httpd作为负载均衡器。httpd将用户请求分发到 EAP 服务器。Red Hat 领导了两个与httpd一起工作的开源项目,分别称为mod_jk和mod_cluster。在这本书中,我们将学习如何使用这两个项目。
EJB 会话 Bean
JBoss EAP6 提供了@org.jboss.ejb3.annotation.Clustered注解,我们可以在@Stateless和@Stateful会话 Bean 上使用它。
提示
集群注解是 JBoss EAP6/WildFly 的特定实现。
当使用@Clustered与@Stateless一起时,会话 Bean 可以进行负载均衡;当使用@Clustered与@Stateful Bean 一起时,Bean 的状态将在集群中复制。
JBossWS 和 RESTEasy
JBoss EAP6 提供了两种内置的 Web 服务解决方案。一个是 JBossWS,另一个是 RESTEasy。JBossWS 是一个实现了 JAX-WS 规范的 Web 服务框架。RESTEasy 是实现 JAX-RS 规范的实现,可以帮助你构建 RESTful Web 服务。
HornetQ
HornetQ 是 JBoss 社区提供的高性能消息传递系统。该消息系统设计为异步的,并在负载均衡和故障转移方面有自己的考虑。
Hibernate 和 Narayana
在数据库和事务管理领域,高可用性是一个巨大的话题。例如,每个数据库供应商可能都有自己的数据库查询负载均衡解决方案。例如,PostgreSQL 有一些开源解决方案,例如 Slony 和 pgpool,这些解决方案可以让我们从主数据库复制到从数据库,并将用户查询分布到集群中的不同数据库节点。
在 ORM 层,Hibernate 也有如Hibernate Shards这样的项目,可以将数据库以分布式的方式部署。
JGroups 和 JBoss Remoting
JGroups 和 JBoss Remoting 是 JBoss EAP6 集群特性的基石,它使它能够支持高可用性。JGroups 是一个基于 IP 多播的可靠通信系统。
提示
JGroups 不仅限于多播,也可以使用 TCP。
JBoss Remoting 是 JBoss EAP6 中多个部分的底层通信框架。
域管理
除了之前讨论的主题外,JBoss EAP6 还引入了一个名为域管理的新功能。这个功能可以帮助我们集中管理作为集群部署的 EAP6 服务器。在本书的后续章节中,我们将学习如何使用这个功能。
安装 JBoss EAP6
在前面的章节中,我们概述了高可用性以及 JBoss EAP6 在这方面为我们提供了什么。如果你没有完全理解所有内容,没关系。我们将在本书中逐步触及这些部分,帮助你构建整个画面。第一步是安装 JBoss EAP6。请从以下 URL 下载JBoss EAP 6.1.0.Final:
www.jboss.org/jbossas/downloads/
定位到6.1.0.Final版本并下载 ZIP 文件。下载完成后,解压它。内容如下所示截图:

从前面的截图,我们可以看到一个名为jboss-modules.jar的 JAR 文件。这个 JAR 文件将帮助我们加载服务器的组件。EAP6 服务器的组件位于modules目录中。这个目录包含 EAP6 可以通过jboss-modules.jar加载的组件。
bin目录包含我们将要使用的启动脚本和其他工具。
standalone和domain目录与 EAP6 启动模式相关。我们将在下一节中更详细地介绍它。
JBoss EAP6 启动模式
启动模式是 JBoss EAP6 中引入的新概念。目前 EAP6 提供了两种模式:
-
独立模式
-
域模式
在bin目录中有两个启动脚本用于这两种模式:
domain.sh standalone.sh
让我们看看这两种模式的意义。
域模式
域模式是 EAP6 中引入的新概念。域指的是一组可以共享配置/部署信息的服务器,这在集群环境中非常有用。
例如,我们有三个运行的 JBoss EAP6 服务器,它们形成一个集群。假设我们有一个名为cluster-demo的项目,并希望将其部署到集群中。传统的方法是将此项目手动部署到每个 EAP6 实例。
幸运的是,借助 EAP6 中的域管理功能,我们现在可以将许多 EAP6 服务器配置为服务器组,并将项目部署到这个组中。然后,该项目将被部署到属于此组的所有 EAP6 服务器上。域模式为我们提供了集群的集中管理点。服务器组还共享相同的配置,该配置会自动分发到所有节点。我们将在后续章节中看到其用法。
独立模式
独立模式类似于传统的 JBoss AS 运行模式,并且在运行时没有提供任何域管理功能。
以独立模式启动 JBoss EAP6
现在我们尝试以独立模式启动 JBoss EAP6。进入bin目录并运行standalone.sh。服务器输出如下截图所示:

现在让我们看看服务器输出的详细信息,以了解启动过程。
理解启动过程
我们可以从服务器输出中看到几个重要的事情。以下是最重要的一点:
Started 123 of 177 services (53 services are passive or on-demand)
从之前的日志中,我们可以看到在 EAP6 启动过程中并非所有组件都被启动。这种设计大大加快了 EAP6 的启动时间。我们可以看到在启动过程中默认启动了一些服务:

这些组件被称为子系统。这些子系统在通过standalone/configuration导航时配置在standalone.xml文件中。
现在让我们看看standalone.sh中启动 EAP6 服务器的实际命令:
eval \"$JAVA\" -D\"[Standalone]\" $JAVA_OPTS \
\"-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/server.log\" \
\"-Dlogging.configuration=file:$JBOSS_CONFIG_DIR/logging.properties\" \
-jar \"$JBOSS_HOME/jboss-modules.jar\" \
-mp \"${JBOSS_MODULEPATH}\" \
-jaxpmodule "javax.xml.jaxp-provider" \
org.jboss.as.standalone \
-Djboss.home.dir=\"$JBOSS_HOME\" \
-Djboss.server.base.dir=\"$JBOSS_BASE_DIR\" \
"$SERVER_OPTS" "&"
提示
下载示例代码
您可以从您在www.packtpub.com的账户中下载您购买的所有 Packt Publishing 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
从之前的命令中,我们可以看到jboss-modules.jar是整个 EAP6 服务器的引导 JAR 文件,入口点是org.jboss.as.standalone,在以下命令中指定:
-jar \"$JBOSS_HOME/jboss-modules.jar\" org.jboss.as.standalone
我们将在稍后看到关于启动过程的更多详细信息。现在让我们检查独立模式的配置文件。
standalone.xml 文件
standalone.xml的结构如下:

如前一个截图所示,standalone.xml定义了独立服务的多个方面。让我们简要查看:
| extensions | 此部分包含一个扩展模块的列表。列出的组件将由jboss-modules加载。 |
|---|---|
| management | 此部分包含与 EAP6 管理接口及其安全设置相关的配置。 |
| profile | 在此部分,我们可以为每个子系统配置设置。大多数子系统是扩展部分中加载的组件,一些子系统是 EAP6 内部需要的,并在启动时默认加载。 |
| interfaces | 此部分定义了一个命名网络接口的列表。 |
| socket-binding group | 此部分包含一个 socket 绑定组的列表,包括不同模块可能使用的接口集合。 |
替代配置文件
除了默认的standalone.xml文件外,EAP6 还为独立模式提供了一些其他配置文件。
提示
每个独立配置文件只有一个配置文件。相比之下,在域配置文件中可以定义多个配置文件。
我们可以在 standalone/configuration 目录中检查它们:

这些文件定义了不同目的的不同配置文件。以下是对它们差异的总结:
| standalone.xml | 这是独立模式的默认设置。 |
|---|---|
| standalone-full.xml | 与默认设置相比,此配置文件添加了消息子系统(HornetQ 及相关组件)。 |
| standalone-ha.xml | 与默认设置相比,此配置文件添加了与集群相关的组件:mod_cluster 和 JGroups,由 Infinispan 驱动的可复制缓存以及其他相关组件。 |
| standalone-full-ha.xml | 与默认设置相比,此配置文件提供了 -full 和 -ha 中的功能组合。 |
要在启动时使用这些替代配置,我们可以在调用 standalone.sh 时使用 -c 选项。例如,如果我们想使用 standalone-ha.xml,命令如下:
$ ./standalone.sh -c standalone-ha.xml
请注意,-c 选项假定配置位于 $JBOSS_HOME/standalone/。
--help 选项
standalone.sh 和 domain.sh 都为我们提供了帮助文档。我们可以始终使用 --help 选项来检查它:
$ standalone.sh --help
配置文件
在 bin 目录中,有几个配置文件将在启动过程中被包含:

我们可以将自己的配置放入这些文件中,启动脚本将包含它们。
在域模式下启动 JBoss EAP6
在本节中,让我们看看域模式。在 bin 目录中使用以下命令在域模式下启动 EAP6 服务器:
$ ./domain.sh
我们可以看到,与独立模式相比,启动过程有所不同。首先,在域模式下加载了更多的组件:
Started 274 of 401 services (126 services are passive or on-demand)
在 domain.sh 中,我们还可以看到启动命令也有所不同:
eval \"$JAVA\" -D\"[Process Controller]\" $PROCESS_CONTROLLER_JAVA_OPTS \
\"-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/process-controller.log\" \
\"-Dlogging.configuration=file:$JBOSS_CONFIG_DIR/logging.properties\" \
-jar \"$JBOSS_HOME/jboss-modules.jar\" \
-mp \"${JBOSS_MODULEPATH}\" \
org.jboss.as.process-controller \
-jboss-home \"$JBOSS_HOME\" \
-jvm \"$JAVA_FROM_JVM\" \
-mp \"${JBOSS_MODULEPATH}\" \
-- \
\"-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/host-controller.log\" \
\"-Dlogging.configuration=file:$JBOSS_CONFIG_DIR/logging.properties\" \
$HOST_CONTROLLER_JAVA_OPTS \
-- \
-default-jvm \"$JAVA_FROM_JVM\" \
'"$@"'
'jboss-modules.jar' is still used for bootstrap:
-jar \"$JBOSS_HOME/jboss-modules.jar\" \
与独立模式相比,入口点不再是 org.jboss.as.standalone;而是变为 process-contoller:
org.jboss.as.process-controller
还有一个名为 host-controller 的进程:
-Dorg.jboss.boot.log.file=$JBOSS_LOG_DIR/host-controller.log
以下图显示了当 EAP6 在域模式下运行时这些过程之间的关系:

正如在域模式下一样,首先启动了一个轻量级的 进程控制器,然后它启动了一个 主机控制器 进程,该进程将控制多个服务器进程。这是因为域模式允许同时运行多个服务器实例,每个服务器都将有自己的 JVM 进程。
域模式
正如我们之前所看到的,当 EAP6 在域模式下运行时,多个服务器可以同时运行。此外,这些服务器可以属于不同的服务器组。属于同一组的服务器将共享部署和配置信息。
例如,我们有一个名为 main-server-group 的服务器组,在这个组中,我们有两个名为 server-one 和 server-two 的服务器。如果我们在这个 main-server-group 中部署一个名为 cluster-demo.war 的项目,那么它将同时部署到这两个服务器上。如果我们在这个组中更改一些设置,这两个服务器的设置都将同步更新:

在前面的例子中,同一组的两个服务器位于同一台机器和同一 EAP6 实例中。但实际上,它们可以存在于不同的 EAP6 服务器中,并且同一组的服务器可以在网络上同步。
配置文件
与独立模式不同,域模式使用两个配置文件:
-
domain.xml -
host.xml
这些配置文件位于 domain/configuration/ 位置。现在让我们首先看看 domain.xml。
域.xml 文件
domain.xml 文件的结构及其与 standalone.xml 的区别如下截图所示:

如果我们将它的结构与独立模式进行比较,我们可以看到差异。首先,有三个复数形式的章节:
-
配置文件
-
接口
-
socket-binding-groups
这种差异的原因很容易猜测。在域模式中,有多个服务器在不同的服务器组中运行,EAP6 支持每个服务器组拥有自己的设置集。因此,需要不同的配置文件、接口和套接字绑定组。
此外,我们还可以看到一个名为 server-groups 的新部分。这是它在 domain.xml 中的默认设置:
<server-groups>
<server-group name="main-server-group" profile="full">
<socket-binding-group ref="full-sockets"/>
</server-group>
<server-group name="other-server-group" profile="full-ha">
<socket-binding-group ref="full-ha-sockets"/>
</server-group>
</server-groups>
前面的设置如下图所示:

这样,不同的服务器组绑定到不同的设置。
host.xml 文件
现在,让我们检查 host.xml。以下截图显示了其结构:

host.xml 文件是主机控制器的设置文件。它包含一些与 standalone.xml 类似的部分,例如管理和接口。它们的目的也是相同的。现在让我们看看 domain-controller 部分。
域控制器部分
domain-controller 部分定义了哪个主机用作域控制器。域控制器实际上是一个主机控制器,但它充当域的管理者。默认的 domain-controller 设置为 local,这意味着 EAP6 将默认使用其主机控制器作为域控制器。
我们还可以定义一个远程主机控制器作为域控制器。然后多个 EAP6 可以连接到同一个域控制器并接受其管理。现在让我们看看 servers 部分。
服务器部分
servers 部分如下截图所示:

在域模式下,一个主机控制器可以同时管理多个服务器,每个服务器都有自己的名称并属于一个服务器组;这些服务器绑定到不同的套接字以避免冲突。
auto-start 选项检查是否在 EAP6 启动时启动此服务器。我们可能可以通过此选项选择在 EAP6 启动时启动哪个服务器。
port-offset 选项用于将不同的服务器绑定到不同的端口以避免冲突。让我们看看 host.xml 中的默认配置:
<servers>
<server name="server-one" group="main-server-group">
<socket-bindings port-offset="0"/>
</server>
<server name="server-two" group="main-server-group">
<socket-bindings port-offset="150"/>
</server>
<server name="server-three" group="other-server-group">
<socket-bindings port-offset="250"/>
</server>
</servers>
下面的部署图显示了之前讨论的服务器和服务器组之间的关系:

这里是 domain.xml 中的服务器组设置:
<server-groups>
<server-group name="main-server-group" profile="full">
<socket-binding-group ref="full-sockets"/>
</server-group>
<server-group name="other-server-group" profile="full-ha">
<socket-binding-group ref="full-ha-sockets"/>
</server-group>
</server-groups>
我们可以看到 main-server-group 绑定到 full-sockets,而 other-server-group 绑定到 full-ha-sockets。这两个套接字定义如下:
<socket-binding-group name="full-sockets" default-interface="public">
<socket-binding name="http" port="8080"/>
</socket-binding-group>
<socket-binding-group name="full-ha-sockets" default-interface="public">
<socket-binding name="http" port="8080"/>
</socket-binding-group>
full-sockets 绑定到 HTTP 端口 8080,port-offset 为 0。因此,server-one 使用的 Web 端口是 8080;对于 server-two,因为它的 port-offset 是 150,它的 Web 端口是 8080 + 150 = 8230。同样,server-three 使用的 HTTP 端口是 8080 + 250 = 8330。
现在让我们将所有三个服务器的 auto-start 设置为 true,以便它们将在 EAP6 启动时启动:
<servers>
<server name="server-one" group="main-server-group" auto-start="true">...</server>
<server name="server-two" group="main-server-group" auto-start="true">...</server>
<server name="server-three-master" group="other-server-group" auto-start="true">...</server>
</servers>
现在让我们通过调用 domain.sh 在域模式下启动 EAP6。EAP6 启动后,让我们尝试使用 telnet 命令访问 8080、8230 和 8330:
$ telnet localhost 8080
Trying localhost...
Connected to localhost.
$ telnet localhost 8230
Trying localhost...
Connected to localhost.
$ telnet localhost 8330
Trying localhost...
Connected to localhost.
我们可以看到现在所有服务器都在监听连接。
XSD 文档
JBoss EAP6 在 docs/schema 中提供了架构文档。每个架构都定义了 EAP6 配置文件使用的命名空间。例如,我们可以检查 standalone.xml 的开头,看看它使用的 xml 命名空间:
<?xml version='1.0' encoding='UTF-8'?>
<server >
...
我们可以看到使用的命名空间是 urn:jboss:domain:1.4。让我们通过使用 grep 命令在 docs/schema 目录中找到定义的命名空间:
$ grep -rl 'urn:jboss:domain:1.4' *
jboss-as-config_1_4.xsd
我们可以看到 jboss-as-config_1_4.xsd 包含了我们正在寻找的 xml 命名空间的定义。现在我们可以检查该命名空间中每个元素的定义。例如,如果我们想了解 standalone.xml 中 server 部分的含义,我们可以在 xsd 文件中检查其定义:
<xs:element name="server">
<xs:annotation>
<xs:documentation>
Root element for a document specifying the configuration
of a single "standalone" server that does not operate
as part of a domain...
</xs:documentation>
</xs:annotation>
...
</xs:element>
如前一个代码片段所示,xsd 架构是非常有用的文档。它们可以帮助我们理解配置文件中元素的含义。
摘要
在本章中,我们学习了高可用性的基本概念,我们学习了如何安装 JBoss EAP6 并以不同的模式运行它。我们还了解了 EAP6 的两种运行模式。在下一章中,我们将学习如何使用 EAP6 管理控制台,并使用它将项目部署到 EAP6。
第二章。使用 JBoss EAP6
在上一章中,我们学习了如何下载和安装 JBoss EAP6。我们还了解了 EAP6 的独立模式和域模式;在本章中,我们将开始学习其基本用法。我们将学习如何管理和配置 EAP6 服务器。本章将涵盖以下主题:
-
使用 JBoss EAP6 管理控制台部署 Web 应用程序
-
命令行界面管理控制台的基本用法
-
JBoss EAP6 管理模型的设计
首先,我们需要了解 EAP6 管理控制台的一些基本配置。配置正确后,我们可以启动 EAP6 服务器并使用其管理控制台。
配置 JBoss EAP6 管理控制台
JBoss EAP6 提供了两个管理控制台——一个是基于 Web 的,另一个是基于 命令行界面(CLI)。在使用它们之前,我们需要正确配置管理模块。
安全领域
要使用管理控制台,我们必须了解其认证方案。JBoss EAP6 使用的认证模块被称为 安全领域。
小贴士
EAP6 使用安全领域来获取对管理接口的安全访问。
在 standalone.xml 的独立配置中打开。相对设置如下:
<management>
<security-realms>
<security-realm name="ManagementRealm">
<authentication>
<local default-user="$local"/>
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
</security-realm>
</security-realms>
<management-interfaces>
<native-interface security-realm="ManagementRealm">
<socket-binding native="management-native"/>
</native-interface>
<http-interface security-realm="ManagementRealm">
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
</management>
如前图配置所示,管理模块所使用的领域被称为 ManagementRealm。它使用一个名为 mgmt-users.properties 的属性文件来存储用户的密码信息。management-interfaces 定义了绑定到管理控制台的网络套接字。默认情况下,Web 管理控制台绑定到地址 127.0.0.1:9990,而 CLI 绑定到地址 127.0.0.1:9999。以下图表给出了管理控制台配置的概述:

由于我们已经了解了 JBoss EAP6 使用的认证方案,我们现在需要创建一个用户账户来访问管理控制台。
设置管理员账户
JBoss EAP6 为我们提供了一个命令行工具来生成用户账户。您可以在 bin 文件夹中找到名为 add-user.sh 的工具。让我们使用它来生成一个管理员账户。此过程在以下屏幕截图中显示:

在前一个屏幕截图所示的过程中,我们创建了一个名为 jbossadmin 的 管理用户 类型的用户。此用户属于 ManagementRealm,因此我们可以使用它来访问管理控制台。请注意,密码长度必须超过八个字符,并且至少应包含一个字母、一个数字和一个符号。因此,我为 jbossadmin 使用密码 @packt000。
对于最后一个选项 此新用户是否将被用于一个 AS 进程连接到另一个 AS 进程?,我们选择 否。在后面的章节中,我们将为域模式中的远程服务器连接创建用户账户。
使用基于网络的网络管理控制台
现在,让我们尝试在独立模式下使用 EAP6 网络管理控制台。在 EAP6 的 bin 文件夹中运行 standalone.sh 来启动服务器。然后,我们通过默认地址 http://127.0.0.1:9990 访问网络管理控制台。管理控制台将弹出一个登录窗口;输入我们刚刚创建的用户账户。整个过程如下面的截图所示:

登录后,我们可以看到管理控制台的主窗口,如下面的截图所示:

在下一节中,我们将使用基于网络的网络管理控制台来部署网络应用程序。
在独立模式下部署项目
现在,我们可以尝试将一个非常简单的名为 cluster-demo1 的网络项目部署到 EAP6 中。这是一个简单的“你好,世界”项目,包含一个简单的问候页面,显示浏览器上的当前时间。要将此项目部署到 EAP6 中,我们可以在管理控制台中点击 Manage Deployments 选项卡,然后点击 Add。

然后,我们选择 cluster-demo1.war 并点击 Next>>。

EAP6 将要求我们验证部署名称。我们应该接受默认名称并点击 Save。这个过程如下面的截图所示:

由于项目已部署,控制台将重定向到 Available Deployments 页面。要启动已部署的项目,我们需要选择它并点击 En/Disable。这个过程如下面的截图所示:

我们还需要确认以启用此项目。这是通过点击 Confirm 来完成的,如下面的截图所示:

最后,我们可以看到项目已启动,如下面的截图所示:

现在,如果我们检查控制台的服务器输出,我们可以看到 cluster-demo1.war 已按如下方式部署:
18:55:46,557 INFOorg.jboss.as.server.deploymentJBEAP6015876: Starting deployment of "cluster-demo1.war"(runtimename: "cluster-demo1.war")
18:55:46,622 INFOorg.jboss.webJBEAP6018210: Register webcontext: /cluster-demo1
18:55:46,638 INFOorg.jboss.as.serverJBEAP6018559: Deployed "cluster-demo1.war"(runtime-name : "cluster-demo1.war")
现在,让我们看看 standalone.xml。我们可以看到,已添加了如下所示的部署描述:
<deployments>
<deployment name="cluster-demo1.war" runtime-name="cluster-demo1.war">
<content sha1="3afbf9c1d6fe967e0ff7eb190b862700b693e431"/>
</deployment>
</deployments>
</server>
从前面的代码片段中,我们可以看到配置文件的内容已被管理控制台更新。最后,让我们检查 standalone/data/content 如下:
mini:jboss-eap-6.1weinanli$ tree standalone/data/content
standalone/data/content
└── 3a
└── fbf9c1d6fe967e0ff7eb190b862700b693e431
└── content
从前面的代码片段中,我们可以看到已部署项目的内容已被哈希并存储在 standalone/data/content 目录中。
测试
现在,我们可以尝试访问已部署的项目,以查看它是否运行正确。在下面的代码片段中,我使用了 curl 命令来测试连接:
$ curl http://127.0.0.1:8080/cluster-demo1/index.jsp
<html>
<body>
<h2>Hello World!</h2>
Hello! The time is now Wed Nov 20 15:50:22 CST 2013
</body>
</html>
如前所述的过程所示,我们可以从控制台输出中看到 hello 页面。
部署扫描器
独立模式支持在 JBoss AS 先前版本中使用的传统热部署。此方法允许您将复制的项目放入目录中,然后 JBoss EAP6 将定期扫描该目录以部署其中复制的项目。此功能由部署扫描器子系统支持,该子系统在standalone.xml中定义如下:
<extension module="org.jboss.as.deployment-scanner"/>
standalone.xml中子系统的默认设置如下所示:
<subsystem >
<deployment-scanner path="deployments" relativeto="jboss.server.base.dir" scan-interval="5000"/>
</subsystem>
它将每 5 秒扫描一次独立部署目录,并部署新添加的项目。我们可以尝试使用部署扫描器部署cluster-demo1.war。在此之前,我们需要从 Web 管理控制台卸载此项目。

要卸载项目,请点击启用/禁用,然后点击移除。项目卸载后,我们可以使用以下代码将cluster-demo1.war文件放入standalone/deployments目录:
$ mv cluster-demo1/target/cluster-demo1.war jboss-eap-6.1/standalone/deployments
由于部署扫描器被设置为每 5 秒扫描此目录一次,我们将等待一会儿,然后我们会看到项目从服务器输出中部署如下:
00:21:05,963 INFOorg.jboss.as.serverJBAS018559: Deployed "cluster-demo1.war"(runtime-name : "cluster-demo1.war")
现在让我们看一下以下所示的deployments目录:
README.txt cluster-demo1.war.deployed
cluster-demo1.warmod_cluster.sar
我们可以看到一个名为cluster-demo1.war.deployed的新文件被自动创建。这是部署扫描器创建的标记文件,用于标记我们项目的状态。现在,让我们使用以下代码删除此文件:
$ rm cluster-demo1.war.deployed
等待一会儿,你可以看到以下服务器输出:
00:26:21,289 INFOorg.jboss.as.serverJBEAP6018558: Undeployed"cluster-demo1.war"(runtime-name: "cluster-demo1.war")
由于我们已删除cluster-demo1.war.deployed标记文件,扫描器得知我们想要卸载项目。因此,它采取了行动。现在让我们再次使用以下代码检查deployments目录:
README.txt cluster-demo1.war.undeployed
cluster-demo1.warmod_cluster.sar
我们可以看到扫描器创建了一个名为cluster-demo1.war.undeployed的另一个标记文件,该文件标记了cluster-demo1.war文件为已卸载。如果我们使用以下代码删除cluster-demo1.war.undeployed文件:
$ rm cluster-demo1.war.undeployed
然后,扫描器将重新部署此项目如下:
00:29:41,499 INFOorg.jboss.as.serverJBEAP6018559: Deployed "cluster-demo1.war"(runtime-name : "cluster-demo1.war")
现在让我们看一下deployments目录。我们可以看到cluster-demo1.war.deployed标记文件再次出现,如下所示:
README.txt cluster-demo1.war.deployed
cluster-demo1.warmod_cluster.sar
注意
部署扫描器只能在独立模式下使用。
介绍 JBoss DMR
JBoss DMR 是 JBoss 管理模块的基石。所有管理操作都将转换为封装在 DMR 格式中的管理命令。部署扫描器或管理控制台使用的部署操作最终都将转换为 DMR 命令。例如,当部署扫描器想要将项目部署到 EAP6 时,它将向部署模块发送以下类似 JSON 的 DMR 命令。以下是该命令的裁剪文本:
[{
"operation" =>"composite",
"address" => [],
"steps" => [
{
"operation" =>"add",
"address" => [("deployment" =>"cluster-demo1.war")],
"content" => [{
"path" =>"deployments/cluster-demo1.war",
"relative-to" =>"jboss.server.base.dir",
}],
},
{
"operation" =>"deploy",
"address" => [("deployment" =>"cluster-demo1.war")]
}
]
}]
我们可以看到部署扫描器已发送一个包含两个操作:添加和部署的操作。这意味着 EAP6 将首先将此项目添加到其作用域中,然后启动它。在 Web 管理控制台中,我们看到这两个动作是分开的——我们首先将一个项目添加到 EAP6 中,然后点击启用/禁用来启动它。Web 管理控制台和命令行界面都会向部署控制台发送此类 DMR 命令。
在域模式下部署项目
现在,让我们学习如何在域模式下部署项目。当 JBoss EAP6 在域模式下运行时,多个服务器可以形成一个服务器组。当我们向服务器组部署项目时,该组中的所有服务器都将部署该项目。首先,让我们通过domain.sh启动 JBoss EAP6 的域模式。然后,我们将访问管理控制台地址,http://127.0.0.1:9990。域模式下的管理控制台与独立模式不同。如下截图所示:

以下是一些关于前一个截图所示的管理控制台内容的笔记:
-
在侧边栏中有一个服务器部分,我们可以检查不同服务器组中的所有服务器。
-
标记表示每个服务器的运行状态。
-
端口:显示每个服务器的端口偏移量。因为这些服务器运行在同一台机器上,它们的端口必须偏移以避免冲突。
-
配置文件:显示服务器组绑定的配置文件。
-
属于同一组的服务器在管理控制台上有相同的颜色。
-
我们可以在管理控制台中启动或停止服务器。
默认情况下有两个服务器组。让我们在下一节检查主服务器组。
主服务器组
我们可以看到,默认情况下主服务器组包括两个服务器。让我们将这些组部署cluster-demo1.war。请确保这两个服务器已启动。如果没有,我们可以使用管理控制台提供的启动服务器功能。
现在,让我们部署cluster-demo1.war。首先,我们需要点击侧边栏上出现的管理部署选项卡。然后点击内容仓库选项卡,并点击添加。此过程如下截图所示:

现在,我们将选择cluster-demo1.war,如下截图所示:

现在,我们点击下一步>>然后点击保存。然后,我们可以看到项目已部署到内容仓库。结果如下截图所示:

我们可以看到,在域模式下的部署过程与独立模式不同。我们不会直接将项目部署到服务器上。相反,我们首先将其添加到内容库中,然后将其部署到服务器组。EAP6 将帮助我们部署项目到组中的所有服务器。
现在,让我们将cluster-demo1.war部署到 main-server-group。首先点击服务器组标签,然后点击 main-server-group 的查看>,如图所示:

点击查看>后,我们进入main-server-group的网页。然后,我们点击分配名称并选择cluster-demo1.war进行保存。这如图所示:

最后,让我们启用项目。点击启用/禁用,然后点击确认,如图所示:

确认部署后,项目应该部署到server-one和server-two。让我们验证这一点。
测试
现在我们尝试访问两个服务器。请注意,主服务器位于端口 8080,从服务器有一个 150 的端口偏移量,因此 Web 端口是 8080+150=8230。结果如下:

从前面的截图可以看出,cluster-demo1.war已部署在两个服务器上。
CLI 使用方法
CLI 为我们提供了一个纯文本环境来管理 EAP6 服务器,并且它与 Web 管理控制台共享相同的 DMR 模型。在本节中,让我们简要讨论 CLI 的使用。
连接到 CLI
启动 CLI 的命令是jboss-cli.sh,它位于bin文件夹中。确保您已以独立模式启动 EAP6。现在让我们运行 CLI 命令。这个过程如图所示:
$ ./jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /]
现在我们已经进入了 CLI 控制台,下一步是使用connect命令进入管理控制台,如下所示:
[disconnected /] connect localhost
[standalone@localhost:9999 /]
我们已经使用了connect命令来连接到管理控制台。
注意
你可能已经注意到 CLI 没有要求我们使用管理员账户登录。在 CLI 中,当我们连接到本地 EAP 服务器时,身份验证被绕过。
既然我们已经连接到文本管理控制台,现在让我们学习一些基本命令。
ls
我们将要学习的第一个命令是ls。它类似于在 shell 环境中使用的ls命令。此命令列出 JBoss EAP6 中的资源,如图所示:

我们可以看到资源是以树状结构组织的。这类似于文件系统,我们可以使用ls命令来检查资源的内容,就像检查目录一样。例如,我们可以检查子系统中的资源,如图所示:

cd
我们可以使用cd命令查看资源,就像它们是目录一样。用法如下面的截图所示:

如前一个截图所示,我们可以使用cd命令遍历资源。
Basic commands
CLI 为我们提供了一套基本命令。我们可以按两次Tab键来查看这些命令的列表,如下面的截图所示:

要理解这些命令的含义,我们可以在命令名后使用--help选项。例如,如果我们想了解 connect 的用法,我们可以使用--help选项,如下面的截图所示:

在基本命令中,我想特别介绍echo-dmr和read-operation,因为它们是最常用的。
echo-dmr
echo-dmr用于为命令或操作构建 DMR 请求。它就像一个翻译器,将动作翻译成 DMR 请求。例如,如果我们想了解deploy命令如何构建 DMR 请求,我们可以使用echo-dmr来翻译它:
[standalone@127.0.0.1:9999 /] echo-dmr deploy /cluster-demo1/cluster-demo1.war
{
"operation" =>"composite",
"address" => [],
"steps" => [
{
"operation" =>"add",
"address" => {"deployment" =>"cluster-demo1.war"},
"content" => [{"bytes" => bytes {
...
}}]
},
{
"operation" =>"deploy",
"address" => {"deployment" =>"cluster-demo1.war"}
}
]
}
从前面的 DMR 请求中,我们可以清楚地看到deploy命令的底层细节。
read-operation
在 CLI 控制台中,每个资源都有一组我们可以对其执行的操作。我们可以使用read-operation来帮助我们了解可以操作的资源上的操作。例如,如果我们想找出 Web 子系统支持哪些操作,我们可以使用read-operation,如下面的截图所示:

例如,在前面截图所示的列表中,我们看到一个名为read-operation-names的操作。让我们尝试使用它,如下面的截图所示:

我们可以看到read-operation-names操作与read-operation命令非常相似;那么它们之间有什么区别呢?让我们使用echo-dmr来检查它,如下面的截图所示:

从 DMR 层面来看,我们可以看到它们的翻译 DMR 请求完全相同。
GUI
EAP CLI 还支持 GUI 界面,实际上是一个 Swing 应用程序。我们可以通过使用--gui选项来启动它,如下所示:
$ ./jboss-cli.sh --gui
接口如下面的截图所示:

GUI 界面也可以远程使用。例如,如果我们的管理控制台绑定了一个公网 IP 地址,我们可以使用以下命令通过另一个机器上的 GUI 访问它:
power:binweli$ ./jboss-cli.sh --controller=10.0.1.3:9999 --connect --gui
GUI 将从远程机器启动。
部署项目
现在让我们使用 CLI 来部署项目。以下命令用于部署:
[standalone@localhost:9999 /] deploy /cluster-demo1/target/cluster-demo1.war
项目部署后,我们可以在deployment下看到它,如下所示:
[standalone@localhost:9999 /] ls deployment
cluster-demo1.war
现在,让我们使用undeploy命令来删除它,如下所示:
[standalone@localhost:9999 /] undeploy cluster-demo1.war
检查deployment中的内容,我们可以看到它被删除,如下所示:
[standalone@localhost:9999 /] ls deployment
[standalone@localhost:9999 /]
摘要
在本章中,我们学习了如何将管理员账户添加到 JBoss EAP6。我们还学习了在 EAP6 以独立模式或域模式运行时,如何使用管理模型将项目部署到 EAP6。然后,我们更深入地了解了 EAP6 管理模型和 DMR 层的设计。有了这些知识,我希望你对 JBoss EAP6 的结构有一个良好的理解,并掌握了其基本用法。从下一章开始,我们将开始设置 EAP6 集群。
第三章。设置 JBoss EAP6 集群
在上一章中,我们学习了如何使用管理控制台将项目部署到 JBoss EAP6 的独立模式和域模式。我们还看到,当需要管理多个服务器时,域模式对我们帮助很大。它通过将管理任务集中到域控制器中来实现这一目标。
EAP6 提供的域模式为我们设置集群提供了良好的支持,但它本身并不是集群。正如我们在第二章中看到的,使用 JBoss EAP6,尽管服务器在服务器组中管理,但它们并没有形成一个集群。为了使服务器形成一个集群,我们需要执行以下两个任务:
-
正确设置 EAP6 服务器,确保与集群相关的所有组件都处于正确状态
-
设置负载均衡器,以便将用户请求分发到集群中包含的不同 EAP6 服务器
在本章中,我们将重点关注第一个任务。
设计一个集群
在上一章中,我们了解到 EAP6 在domain.xml中提供了两个服务器组。一个是main-server-group,另一个是other-server-group。在本章中,我们将使用other-server-group来设置集群,因为它使用的是full-ha配置文件,而这个配置文件包含了我们将用于设置集群的所有组件。
使用集群,我们可以将请求负载分配到多个服务器。利用 EAP6 域管理功能,我们可以在域控制器的管理控制台中将项目部署到多个 EAP6 服务器。
在本书中,我们将使用三台机器来设置一个集群。其中两台将用于运行 EAP6 服务器,另一台将用于运行负载均衡器。在本章中,我们将使用两台机器来运行 EAP6 服务器。
小贴士
您也可以使用虚拟化技术,让所有服务器在一个物理盒子里运行。只需确保它们有独立的 IP 地址,可以相互通信,并且防火墙已关闭。
部署过程在以下图中展示:

在本章中,我们将使用两台机器并在它们上安装 EAP6,我们将在下一章讨论负载均衡器。以下图中展示了两个 EAP6 服务器的部署图:

这两台机器被称为master和slave。我已经将这些服务器的 IP 地址放入图中供您参考。这些地址用于我的本地环境。在图中,master上的 EAP6 服务器将被配置为域控制器,而slave上运行的 EAP6 服务器将接受master的管理。
在 domain/configuration/host.xml 中,EAP6 为我们提供了一个属于 other-server-group 的默认服务器。我们将把这个服务器在 master 上的名称重命名为 master-server,并在 slave 上的名称重命名为 slave-server。
在 EAP6 域模式中,每个服务器实例都在自己的 JVM 进程中运行。以下是在 master 上将运行的进程:
/usr/bin/java -D[Process Controller]
/usr/bin/java -D[Host Controller]
/usr/bin/java -D[Server:master-server]
以下是在 slave 上将运行的进程:
/usr/bin/java -D[Process Controller]
/usr/bin/java -D[Host Controller]
/usr/bin/java -D[Server:slave-server]
在 master 上运行的宿主控制器将充当域控制器;在 slave 上运行的宿主控制器将接受域控制器的管理。此外,master-server 和 slave-server 都属于 other-server-group。当我们向 other-server-group 部署项目时,项目将被部署到两个服务器上,因为它们属于同一个服务器组,即使它们运行在不同的机器上,使用不同的 JVM。
设置服务器组
在本节中,我们将开始在 EAP6 中进行一些配置。正如我们所知,在 JBoss EAP6 中已经为我们设置了两个服务器组:
-
main-server-group
-
other-server-group
在 第二章,使用 JBoss EAP6 中,我们已经玩过 main-server-group。在本章中,我们将使用 other-server-group。
main-server-group 与 other-server-group 的比较
主要区别在于它们使用两个不同的配置文件。您可以在 domain.xml 中看到这一点:
<server-group name="main-server-group" profile="full">...
<server-group name="other-server-group" profile="full-ha">...
如前述配置所示,在域模式中,我们不会将不同的配置文件定义在不同的配置文件中。相反,它们在 domain .xml 中的不同配置部分中定义。
服务器配置
现在,让我们开始配置我们的服务器组。因为我们需要在两台不同的机器上配置两个 EAP6 实例,让我们逐一进行。让我们从 master 开始。
设置 master
在 master 上的 EAP6 将用作域控制器。让我们开始配置它。
配置 host.xml
我们应该做的第一件事是在 domain/configuration/host.xml 中设置主机名。让我们将主机名设置为 master:
<host name="master" >
然后让我们设置服务器组和服务器。因为我们在这个章节中不需要使用 main-server-group 及其服务器,所以最好关闭它们以节省一些系统资源。打开 host.xml 并将 server-one 和 server-two 的 auto-start 选项设置为 false:
<server name="server-one" group="main-server-group" auto-start="false">...
<server name="server-two" group="main-server-group" auto-start="false">...
下次我们在域模式中启动 EAP6 时,这两个服务器将不会启动。然后我们将 server-three 重命名为 master-server 并将其 auto-start 设置为 true:
<server name="master-server" group="other-server-group" auto-start="true">
<socket-bindings port-offset="250"/>
</server>
请注意,master-server 的端口偏移量是 250,我们将用它来计算 master-server 的服务端口。接下来,我们应该做的是将多个套接字的绑定地址更改为公共 IP 地址。因为我们将使用两台不同机器上的两个 EAP6 服务器来形成一个服务器组,我们需要将套接字绑定到适当的地址,以确保它们可以相互通信。
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:10.0.1.13}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:10.0.1.13}"/>
</interface>
<interface name="unsecure">
<inet-address value="${jboss.bind.address.unsecure:10.0.1.13}"/>
</interface>
</interfaces>
请注意,从服务器将连接到主管理接口以接受其管理,因此它也应该绑定到之前代码片段中显示的公共地址。我们将在后面的章节中介绍 从服务器的配置。
这里的公共地址指的是除了回环地址之外的其他 IP 地址。它应该是一个仅限于局域网内部访问的内部 IP 地址。在实际场景中,管理接口不会是公开的。他们会因为安全原因只使用内部网络。因此,我们通常将所有 EAP6 服务器放在局域网中,并且只将负载均衡器公开,让它代理用户请求到内部网络。我们将在下一章讨论这个问题。
为从服务器添加用户账户
两个服务器(主服务器和从服务器)将由域管理,我们需要使用主机控制器来连接到域。两个主机控制器之间的通信需要经过认证。如果从服务器想要连接到域控制器,我们需要为连接设置一个用户账户,这是由 JBoss EAP6 的安全要求强制执行的。
我们将在 bin 目录中使用 add-user.sh 为从服务器创建用户。过程如下截图所示:

以下是我们创建的用户摘要:
-
用户类型为 Management User
-
用户名为 slave
-
用户密码设置为 @packt000
-
该用户属于 ManagementRealm
-
此新用户是否将被用于一个 AS 进程连接到另一个 AS 进程 设置为 是
最后一点很重要;我们已经将此用户设置为用于 AS 进程连接,并为该用户生成了一个秘密值:
<secret value="QHBhY2t0MDAw" />
这是 slave-server 连接到 master-server 所需要的秘密值。我们需要将此秘密值输入到 slave EAP6 中。另一件重要的事情是,这里的用户名必须与 slave EAP6 的 host.xml 中的 name 属性相同。如果您不遵循此规则,当您尝试在 slave 上启动 EAP6 时,您将收到以下错误消息:
[Host Controller] 22:31:40,341 DEBUG [org.jboss.remoting.remote.client] (Remoting "slave:MANAGEMENT" read-1) Client received authentication rejected for mechanism DIGEST-MD5
[Host Controller] 22:31:40,344 ERROR [org.jboss.remoting.remote.connection] (Remoting "slave:MANAGEMENT" read-1) JBREM000200: Remote connection failed: javax.security.sasl.SaslException: Authentication failed: all available authentication mechanisms failed
因此,此规则由 EAP6 强制执行。
设置 HornetQ
配置 master 服务器还需要进行一个额外的步骤。打开 domain.xml 并找到 HornetQ 的安全设置:
<profile name="full-ha">
<subsystem >
<hornetq-server>
<cluster-password>${jboss.messaging.cluster.password:CHANGE ME!!}
</cluster-password>
...
注意
domain.xml 中的 full-ha 和 full 配置文件都包含 hornetq-server 的设置。请确保您正在 full-ha 配置文件下编辑配置。
将前面的设置更改为以下内容:
<hornetq-server>
<cluster-user>foo</cluster-password>
<cluster-password>bar</cluster-password>
</hornetq-server>
如果您觉得这不太有用,您也可以禁用它:
<hornetq-server>
<security-enabled>false</security-enabled>
</hornetq-server>
本书不会涵盖 HornetQ 的主题。我们只是正确配置它,以确保 EAP6 服务器能够正确启动。
设置从服务器
现在让我们配置slave上的 EAP6。因为它将接受来自master EAP6 的管理,所以这个服务器上的domain.xml变得无用。我们只需要配置host.xml。现在让我们看看它。
配置 host.xml
与master EAP6 上的配置相似,我们首先需要配置主机名:
<host name="slave" >
如在设置 master的配置 host.xml部分所述,这里的名称必须与我们已在master EAP6 中添加的账户的用户名相同。然后我们需要将密钥值分配给ManagementRealm:
<security-realm name="ManagementRealm">
<server-identities>
<secret value="QHBhY2t0MDAw"/>
</server-identities>
...
</security-realm name="ManagementRealm">
然后slave的主机控制器将使用此密钥值进行身份验证。下一步是设置domain-controller:
<domain-controller>
<remote host="10.0.1.13" port="9999" security-realm="ManagementRealm"/>
</domain-controller>
如前所述,slave EAP6 将连接到master EAP6,并使用它作为域控制器。
小贴士
记住,10.0.1.13是master的 IP 地址,而10.0.1.19是slave的地址。
下一步是将slave EAP6 的接口绑定到适当的 IP 地址,以便master EAP6 可以与其通信:
<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:10.0.1.19}"/>
</interface>
<interface name="unsecure">
<inet-address value="${jboss.bind.address.unsecure:10.0.1.19}"/>
</interface>
</interfaces>
请注意,我们没有更改management接口的绑定。因为域控制器将承担管理任务,所以slave上的本地管理将不会使用。所以我们就让它保持不变。
接下来,我们还将关闭server-one和server-two以节省一些资源:
<server name="server-one" group="main-server-group" auto-start="false">
<server name="server-two" group="main-server-group" auto-start="false">
最后,让我们将server-three重命名为slave-server并将auto-start设置为true:
<server name="slave-server" group="other-server-group" auto-start="true">
那就是我们需要为 slave EAP6 配置的所有内容。
在 slave 上配置 domain.xml
slave EAP6 上的domain.xml未使用,因为master EAP6 正在充当域控制器,并将承担管理任务。
测试服务器组
现在我们已经正确设置了master和slave EAP6,是时候对它们进行测试了。
运行 master
首先在master EAP6 上运行以下命令:
$ ./domain.sh
主服务器启动后,让我们检查以下屏幕截图所示的服务器输出:

在前面的屏幕截图中,我们可以看到master-server已启动。然后我们可以看到管理接口和行政控制台也已启动。日志输出如下所示。

现在我们可以看到与集群相关的多个组件已经启动:
-
JGroups 子系统已启动。
-
AJP 连接器已启动。负载均衡器将使用它进行代理请求。我们将在下一章中介绍这个主题。
-
HTTP 连接器已启动。因为
master-server的端口号偏移量为250,HTTP 端口绑定到8080,所以 8080+250 =8330。 -
mod_cluster已启动。我们将在第五章使用 mod_cluster 进行负载均衡中了解mod_cluster。
描述的过程如下所示:

运行 slave
现在,让我们使用domain.sh启动从服务器。其主机控制器将尝试连接到主服务器上的远程域控制器。在从服务器启动后,我们可以检查主EAP6 控制台的输出以确认从服务器已注册:
[Host Controller] 01:22:48,527 INFO [org.jboss.as.domain] (slave-request-threads - 1) JBAS010918: Registered remote slave host "slave", JBoss EAP 6.1.0.GA (AS 7.2.0.Final-redhat-8)
检查服务器状态
现在,让我们访问主EAP6 上的 Web 管理控制台,网址为http://master:9990。
使用账户jbossadmin登录后,我们可以检查master-server和slave-server的状态。以下是它们的状态:

如截图所示,主和从服务器都显示在管理控制台中。这意味着域控制器正在管理所有服务器。现在让我们转到从服务器并尝试访问其管理控制台。结果如下:

我们可以看到从EAP6 现在处于域控制器的管理之下。
项目部署
现在让我们将cluster-demo1部署到other-server-group。
将项目部署到其他服务器组
在上一章中,我们已经学习了如何将项目部署到服务器组,所以在这里我将简要描述这个过程:
-
登录到主EAP6 的管理控制台。
-
在侧边栏中点击管理部署。
-
在内容库选项卡下点击添加并添加cluster-demo1.war。
-
按照说明并保存部署。
-
点击服务器组选项卡。
-
点击其他服务器组的查看。
-
点击分配并选择cluster-demo1.war。
-
保存部署并点击启用/禁用以启动cluster-demo1.war文件。
如果一切顺利,你应该会看到来自主和从服务器的输出。以下是在主服务器上的输出:
[Server:master-server] 23:31:58,223 INFO [org.jboss.as.server] (host-controller-connection-threads - 3) JBAS018559: Deployed "cluster-demo1.war" (runtime-name : "cluster-demo1.war")
以下是在从服务器上的输出:
[Server:slave-server] 23:31:58,246 INFO [org.jboss.as.server] (host-controller-connection-threads - 3) JBAS018559: Deployed "cluster-demo1.war" (runtime-name : "cluster-demo1.war")
在域模式帮助下,项目被部署到属于同一组的两个服务器上。现在我们可以通过使用cURL来验证项目是否已部署到这两个服务器:

使用独立模式进行集群
在前面的章节中,我们看到了域模式为我们提供了一个集中管理服务器的地方。问题是,我们是否需要使用域模式来构建 EAP6 集群?答案是,不一定。
使用独立模式构建集群是完全可以的。我们只需要启用构建集群所需的相对子系统。EAP6 在独立模式下为我们提供了一套配置。*-ha.xml文件包含集群配置文件。我们可以在启动时使用它们。以下是一个命令:
$ ./standalone.sh -c standalone-full-ha.xml
在集群环境中使用独立模式的一些缺点如下:
-
我们必须分别配置每个 EAP6 服务器
-
没有一个集中点来管理这些服务器
这意味着我们必须将项目分别部署到每个 EAP6 服务器上,并确保在重新部署期间它们保持同步。此外,如果我们在同一台机器上运行多个独立的 EAP6 服务器,我们必须仔细设置所有端口的偏移量,以防止它们相互冲突。除了这些缺点之外,独立模式在集群中还有一些优点:
-
您可以独立配置每个服务器。例如,我们可以在服务器 A 上关闭 HornetQ 子系统,并在服务器 B 和 C 上启用它。
-
在独立模式下运行的服务器可以轻松地进行调试。在域模式下,EAP6 将启动多个进程:一个进程控制器进程、一个主机控制器进程以及多个服务器进程(每个服务器实例都在自己的 JVM 空间中运行),这将为调试增加一些困难。
摘要
在本章中,我们探讨了 EAP6 集群的配置,并已设置两个以域模式运行 EAP6 服务器以形成一个服务器组。尽管如此,我们还没有完成构建集群的所有工作:现在我们有两个独立运行的 EAP6 服务器,我们仍然需要一个负载均衡器来将用户请求分发到这两个服务器。在下一章中,我们将专注于设置负载均衡器。
第四章。使用 mod_jk 进行负载均衡
在上一章中,我们设置了两个以域模式运行并使用full-ha配置文件的 EAP6 服务器。在本章中,我们将设置一个负载均衡器,以便将用户请求分发到这两个 EAP6 服务器。
本章中使用的负载均衡器是由 Apache 社区提供的 mod_jk。它易于使用且功能强大。以下图表显示了其工作原理:

以下是对上一张图表的一些说明:
-
mod_jk 是一个可以作为 Apache httpd 模块使用的动态库。在本章中,我们将学习如何使用它。
-
AJP13 是 mod_jk 用于代理用户请求到 JBoss EAP6 服务器的二进制协议。AJP13 代表 Apache JServ Protocol 1.3,并被 Tomcat、Jetty 和其他 Web 服务器广泛使用。
-
当 Apache httpd 收到用户请求时,mod_jk 模块将 HTTP 请求包装成 AJP13 格式,并将其传递给 JBoss EAP6,JBoss EAP6 中的 AJP 连接器将接收来自 httpd 的代理请求。然后,EAP6 将处理请求并将响应发送回 Apache httpd。最后,Apache httpd 将处理来自 JBoss EAP6 的 AJP 响应,并将其转换为真实的 HTTP 响应并发送给用户。
在实际操作中,我们通常将负载均衡器绑定到公网 IP 地址,以便它可以监听来自互联网的用户请求,并将 EAP6 工作节点放在本地网络中。此外,我们还应该为负载均衡器绑定一个本地 IP 地址,以便它可以与 EAP6 服务器通信。用户只需要与负载均衡器通信即可访问服务,他们不需要了解负载均衡器背后的架构。此外,将集群的内部架构暴露给公众是不必要的,可能会引入潜在的安全风险。
准备安装 Apache httpd 的机器
正如我们在上一章中看到的,我们的集群部署结构将如下所示:

我们在上一章中配置了两个 EAP6 服务器。现在,我们将在一台机器上安装 Apache httpd 和 mod_jk 作为负载均衡器,让我们称它为lb。这台机器将有两个 IP 地址:
-
一个用于服务用户请求的公网 IP 地址
-
一个可以与本地网络中的 JBoss EAP6 服务器通信的本地 IP 地址
提示
如果您的计算机只有一个本地 IP 地址,您可以使用它来服务用户请求并与 EAP6 服务器通信。但在实际操作中,我们通常在防火墙后面隐藏集群架构。
编译和安装 Apache httpd
现在,让我们学习如何编译和安装 Apache httpd。您可能会问为什么我们需要自己编译 Apache httpd。有多个原因。通常,httpd 由不同平台提供,这些平台有不同的版本和配置。例如,如果您使用 Ubuntu Linux,而我使用 MacOS,我们的 httpd 版本将不同,我们的 httpd 配置也将不同。
在实践中,编译 httpd、mod_jk 和 mod_cluster 也是常见的。这是因为有时 mod_jk 和 mod_cluster 的新版本在二进制格式发布之前以源代码格式发布。因此,我们需要自己构建它们。
下载 httpd
首先,让我们从 Apache httpd 的网站上下载其源代码。在这本书中,我们将使用 httpd 2.2.x。这个分支是目前与 mod_jk 和 mod_cluster 一起工作的最稳定版本。在撰写本文时,2.2.x 分支的最新版本是 httpd 2.2.25,所以让我们使用这个版本来构建我们的负载均衡器。请注意,httpd、mod_jk 和 mod_cluster 的版本非常重要,所以请坚持使用本书中使用的版本,否则您可能会在一些错误上浪费时间。您可以从 archive.apache.org/dist/httpd/httpd-2.2.25.tar.gz 下载 httpd 2.2.25。
下载后,请将其解压到您有完全访问权限的目录中。我已经将其解压到 /packt/:
$ pwd
/packt
$ tar zxvf httpd-2.2.25.tar.gz
...
$ ls
httpd-2.2.25 httpd-2.2.25.tar.gz
由于我们将在某些情况下使用绝对路径,请不要将源代码放在非常深的路径中,否则您在引用绝对路径时将创建不必要的困难。
编译 httpd
要编译 httpd,首先让我们看一下以下所示的源代码内容:

在内容中,有一个名为 configure 的文件,它将检测您的系统设置并为您生成构建脚本 Makefile。让我们先运行它:
./configure --prefix=/packt/httpd \
--with-mpm=worker \
--enable-mods-shared=most \
--enable-maintainer-mode \
--with-expat=builtin \
--enable-ssl \
--enable-proxy \
--enable-proxy-http \
--enable-proxy-ajp \
--disable-proxy-balancer
如前一个代码片段所示,我们向 configure 脚本提供了几个选项。让我们逐一检查它们:
prefix=/packt/httpd |
prefix 选项定义了二进制安装位置。 |
|---|---|
with-mpm=worker |
MPM 是 httpd 进程引擎。worker 引擎目前与 httpd 2.2.x 稳定工作,所以我们将使用它。 |
enable-mods-shared=most |
此选项将模块编译到共享库中。如果我们不启用它,模块将被编译为静态链接库,我们无法在稍后的 httpd.conf 中的 LoadModule 指令中单独禁用它们。 |
enable-maintainer-mode |
一个用于控制 Automake 工具的选项。 |
with-expat=builtin |
Expat 是用 C 语言编写的 XML 解析库。 |
enable-ssl |
在涉及集群环境中 SSL 支持的章节中需要 SSL 库。 |
enable-proxy |
proxy 库是 mod_cluster 的依赖项。我们将在下一章中学习 mod_cluster。 |
enable-proxy-http |
proxy-http 是由 mod_cluster 需要的。 |
enable-proxy-ajp |
proxy-ajp 是由 mod_jk 和 mod_cluster 需要的。 |
disable-proxy-balancer |
proxy-balancer 与 mod_cluster 冲突,因此我们必须禁用它。 |
在理解了这些选项的含义之后,请使用前面的选项运行 configure 命令。现在让我们运行 make 来编译 httpd:
httpd-2.2.25$ make
Making all in srclib
Making all in pcre
...
等待几分钟,编译应该会完成。
安装 httpd
编译完成后,使用以下命令安装编译后的二进制文件:
httpd-2.2.25$ make install
Making install in srclib
Making install in pcre
...
mkdir /packt/httpd
mkdir /packt/httpd/modules
...
Installing man pages and online manual
mkdir /packt/httpd/man
...
如我们所见,编译的二进制文件安装在我们设置的 --prefix 选项指定的目录中。对我来说,它是 /packt/httpd。
启动 httpd
现在,让我们尝试启动 httpd 来查看它是否正确安装。前往你安装的 httpd 的 bin 目录并运行以下命令:
httpd/bin$ sudo ./httpd -k start -f /packt/httpd/conf/httpd.conf
我们在启动命令中使用了 -k 选项来告诉 httpd 开始,并使用 -f 选项和 httpd.conf 的完整路径来确保 httpd 服务器正在使用我们安装的配置文件。
我们使用 sudo 命令是因为我们需要根权限将 httpd 服务绑定到端口 80。如果服务器启动成功,它将显示一些警告:
httpd: Could not reliably determine the server's fully qualified domain name, using localhost.local for ServerName
警告是由 httpd.conf 中缺少 ServerName 配置引起的。我们稍后会正确配置它。现在,让我们检查日志输出。前往日志目录并检查 error_log:
httpd/logs$ tail -f error_log
...
[Thu Oct 03 15:19:18 2013] [notice] Apache/2.2.25 (Unix) mod_ssl/2.2.25 OpenSSL/1.0.1c DAV/2 configured -- resuming normal operations
error_log 文件可以帮助我们检查在运行 httpd 服务器时是否有任何错误。在这里,我们使用了 tail 命令来检查这个日志文件的尾部内容,并且 -f 选项会持续更新 error_log 的内容到控制台。所以,请保持控制台窗口开启;当我们对 httpd 进行操作时,我们可以随时检查是否有错误。现在,我们可以使用 cURL 命令来测试 httpd 服务:
$ curl http://localhost
<html><body><h1>It works!</h1></body></html>
如我们所见,HTTP 服务正在运行。现在,让我们停止 httpd 服务器并在 httpd.conf 中做一些基本的配置。
停止 httpd
停止 httpd 的命令与启动它的命令类似;只需将 start 替换为 stop:
sudo httpd -k stop -f /packt/httpd/conf/httpd.conf
从 error_log 我们可以看到服务器已经停止:
[Thu Oct 03 16:23:59 2013] [notice] caught SIGTERM, shutting down
配置 httpd
现在,让我们为 httpd 进行一些基本的配置。第一步是备份你的原始 httpd.conf:
httpd/conf$ cp httpd.conf httpd.conf.orig
保留配置的原始副本是一个好习惯;以防我们弄错了,我们可以在以后恢复它。下一步是使用你喜欢的编辑器打开 http.conf 并找到以下代码行:
Listen 80
我们需要更改它,以便 httpd 监听公共地址:
Listen 172.16.123.1:80
我已经在机器 lb 上配置了这个 IP 地址,它将被用来监听用户请求。请注意,将 httpd 绑定到特定的 IP 地址和端口是一个良好的习惯,可以防止潜在的安全风险。除了公共 IP 地址外,机器 lb 还有一个本地 IP 地址,10.0.1.32。前者将用于用户从公共访问;后者与两个 EAP6 服务器位于同一局域网中。如果你的机器没有两个 IP 地址,可以使用单个地址来满足这两个目的。只需记住,在实际操作中,我们通常将集群架构放在防火墙后面。
现在让我们进入下一步。我们需要找到以下这一行:
#ServerName www.example.com:80
让我们把我们的服务器名称放在这一行下面:
ServerName lb
目前我们只需要在httpd.conf中进行这些配置。让我们保存配置并退出编辑。接下来,我们需要确保主机名 lb 映射到我们的公共 IP 地址。对于类 Linux 的环境,我们可以将映射放在/etc/hosts中。打开httpd.conf文件,在底部添加以下行:
172.16.123.1 lb
保存配置文件后,我们可以使用ping命令来测试主机名:
$ ping -c 3 lb
PING lb (172.16.123.1): 56 data bytes
64 bytes from 172.16.123.1: icmp_seq=0 ttl=64 time=0.036 ms
64 bytes from 172.16.123.1: icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from 172.16.123.1: icmp_seq=2 ttl=64 time=0.087 ms
现在,让我们启动 httpd 来检查我们的配置:
sudo httpd -k start -f /packt/httpd/conf/httpd.conf
如果你仍然保留了尾随的-f logs/error_log控制台,你可以在启动过程中立即检查是否有任何错误。如果一切顺利,我们现在可以通过主机名访问 httpd 服务器了:
$ curl http://lb
<html><body><h1>It works!</h1></body></html>
如前一个代码片段所示,主机名 lb 被绑定到了 httpd 服务器上。此外,在 httpd 服务器启动过程中,请注意,警告httpd: 无法可靠地确定服务器的完全限定域名已经消失。这是因为我们已经配置了ServerName在httpd.conf中。
既然我们已经配置了 httpd,下一步就是学习如何使用 mod_jk。
编译和安装 mod_jk
mod_jk 的全称是 Apache Tomcat Connector。它最初是为了代理 httpd 到 Tomcat 的 HTTP 请求而设计的,但由于它是一个标准的 AJP 连接器,因此它可以用于支持 AJP 协议的 Web 容器。因为 JBoss EAP6 支持 AJP13 协议,所以我们可以使用 mod_jk 作为其连接器。
提示
为了节省一些输入,在接下来的文本中,我会用 JK 来指代 mod_jk。
安装 JK
JK 的下载页面位于tomcat.apache.org/download-connectors.cgi。
在撰写本文时,最新版本是 1.2.37,这是我们在这本书中将使用的版本。请从之前提到的网站下载 1.2.37 源代码包,并在下载后解压。完成所有这些后,让我们看看源代码包的内容:
tomcat-connectors-1.2.37-src$ ls
HOWTO-RELEASE.txt conf support
LICENSE docs tools
NOTICE jkstatus xdocs
README.txt native
如我们所见,mod_jk 包含许多组件,但我们只需要在 native 目录下构建代码。让我们进入这个目录并运行里面的configure脚本:
$ ./configure --with-apxs=/packt/httpd/bin/apxs
请注意,我们提供了—with-apxs选项进行配置,因为在构建过程中,它需要 httpd 的二进制文件。配置过程如下所示:
tomcat-connectors-1.2.37-src/native$ ./configure --with-apxs=/packt/httpd/bin/apxs
checking build system type...
checking host system type...
...
config.status: executing depfiles commands
After configure process finished, run make to compile it:
tomcat-connectors-1.2.37-src/native$ make
libtool: install: warning: remember to run `libtool --finish /packt/httpd/modules'
...
Making all in common
Making all in apache-2.0
make[1]: Nothing to be done for `all'.
现在,让我们通过运行make install来安装它:
tomcat-connectors-1.2.37-src/native$ make install
Making install in apache-2.0
Installing files to Apache Modules Directory...
cp .libs/mod_jk.so /packt/httpd/modules/mod_jk.so
chmod 755 /packt/httpd/modules/mod_jk.so
Please be sure to arrange /packt/httpd/conf/httpd.conf...
我已经剪掉了日志输出,只留下了重要的部分。从前面的日志中,我们可以看到编译后的共享二进制文件mod_jk.so已被复制到/packt/httpd/modules/。因为我们使用--with-apxs选项设置了 httpd 路径,在安装过程中,构建脚本知道将编译的二进制文件放在哪里。
在日志的末尾,JK 提醒我们配置httpd.conf以启用它。在下一节中,我们将执行此任务。
配置 JK
在 JK 源中,有一个名为conf的目录。在这个目录中,JK 为我们提供了一些可以参考的示例配置文件:
$ ls /packt/tomcat-connectors-1.2.37-src/conf
httpd-jk.conf workers.properties
uriworkermap.properties workers.properties.minimal
让我们将这些文件复制到 httpd 中。首先,让我们在 httpd 中创建一个名为conf.d的新目录:
/packt/httpd$ mkdir conf.d
然后,让我们将配置文件从 JK 源复制到conf.d:
$ cp /packt/tomcat-connectors-1.2.37-src/conf/httpd-jk.conf /packt/httpd/conf.d/
我们希望 httpd 在启动时加载conf.d/httpd-jk.conf。为了实现这个目标,请打开conf/httpd.conf并找到以LoadModule开头的多行。在这些LoadModule指令的底部,让我们添加一行新代码:
Include conf.d/*.conf
修改如下所示:

Include指令将告诉 httpd 在启动时加载conf.d中后缀为.conf的文件。由于我们已经将httpd-jk.conf放入conf.d,它将在 httpd 启动时被加载。现在让我们转到 JK 配置。
配置 httpd-jk.conf
我们需要正确配置 JK。请打开conf.d中的httpd-jk.conf文件,并检查一些重要的配置:
LoadModule jk_module modules/mod_jk.so
如前一行代码所示,我们可以看到mod_jk.so库已被加载。
JkWorkersFile conf/workers.properties
默认情况下,JK 将在conf目录中查找workers.properties文件。此属性文件用于定义我们的集群结构。让我们将 JK 源目录中的示例config文件workers.properties.minimal复制到conf.d:
$ cp /packt/tomcat-connectors-1.2.37-src/conf/workers.properties.minimal /packt/httpd/conf/workers.properties
之后,我们将详细讨论此文件。现在,让我们回到检查httpd-jk.conf:
JkLogFile logs/mod_jk.log
JkLogFile指令定义了 JK 使用的日志文件。
JkLogLevel info
JkLogLevel指令定义了 JK 的日志级别。您可以将它更改为debug以在 JK 运行时查看更多详细信息。
JkShmFile logs/mod_jk.shm
这是 JK 共享内存文件。只需保持原样即可。现在,让我们看看以下两个 JK 模块:
<Location /jk-status>
JkMount jk-status
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Location>
<Location /jk-manager>
JkMount jk-manager
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Location>
之前提到的两个位置是 JK 管理组件的位置。我们可以在workers.properties中定义它们:
worker.list=jk-status
worker.jk-status.type=status
worker.jk-status.read_only=true
worker.list=jk-manager
worker.jk-manager.type=status
我们可以看到jk-status和jk-manager实际上是同一件事:
worker.jk-status.type=status
其中之一是read_only:
worker.jk-status.read_only=true
另一个可以接受管理命令。在这本书中,我们将仅使用配置文件来配置 JK,而不会涵盖 jk-manager 的使用话题。现在,让我们回到配置文件。jk-status 和 jk-manager 的访问范围在 Location 设置中定义:
Allow from 127.0.0.1
这意味着我们只能从本地主机访问 /jk-status 和 /jk-manager。这是安全的,因为我们不希望这些管理组件被远程访问。为了支持它,我们需要在 conf/httpd.conf 中添加一行。在现有的 Listen 部分下,添加另一个指令:
Listen 127.0.0.1:80
它应该看起来像以下这样:

这将让 httpd 接受来自本地的连接。这就是我们启用管理控制台所需做的全部工作。现在,让我们检查 worker s.properties。
工作节点配置文件
我们将在 conf 目录中复制的 workers.properties 文件中定义我们的集群结构。该文件的内容很简单,如下所示:
worker.list=lb,jk-status
worker.node1.type=ajp13
worker.node1.host=localhost
worker.node1.port=8009
worker.lb.type=lb
worker.lb.balance_workers=node1
worker.jk-status.type=status
配置的第一行定义了两个工作节点:lb 和 jk-status。我们知道 jk-status 用于挂载 JK 的管理组件,其类型是 status。
对于工作 lb,我们看到它的类型是 lb:
worker.lb.type=lb
lb 类型在 JK 中定义了一个负载均衡器,它可以用来将用户请求分发到工作节点。我们看到 JK 提供的示例中有一个名为 node1 的工作节点:
worker.lb.balance_workers=node1
默认情况下,node1 是一个支持 AJP13 协议的节点:
worker.node1.type=ajp13
worker.node1.host=localhost
worker.node1.port=8009
上述配置需要修改。我们有两个工作节点,即运行在主节点和从节点上的两个 EAP6 服务器,它们的 IP 地址如我们所知分别是 10.0.1.13 和 10.0.1.19。
我们知道两个 EAP6 服务器以域模式运行,我们正在使用 other-server-group,主节点作为域控制器运行。因此,让我们检查主节点上的 domain.xml:
<server-group name="other-server-group" profile="full-ha">
<socket-binding-group ref="full-ha-sockets"/>
</server-group>
<socket-binding-group name="full-ha-sockets" default-interface="public">
<socket-binding name="ajp" port="8009"/>
...
</socket-binding-group>
other-server-group 使用了 full-ha-sockets 绑定组。并且 AJP 端口绑定到了 8009。但别忘了在主节点和从节点上的 host.xml 中的 port-offset 设置。在主节点系统上,我们有以下代码片段:
<server name="master-server" group="other-server-group" auto-start="true">
<socket-bindings port-offset="250"/>
</server>
在从节点系统上,我们有以下代码片段:
<server name="slave-server" group="other-server-group" auto-start="true">
<socket-bindings port-offset="250"/>
</server>
因此,它们绑定的 AJP 端口是 8009 + 250 = 8259。根据这两个工作节点的设置,让我们修改 workers.properties 中的配置。以下是其全部内容:
worker.list=lb,jk-status
worker.master.type=ajp13
worker.master.host=10.0.1.13
worker.master.port=8259
worker.slave.type=ajp13
worker.slave.host=10.0.1.19
worker.slave.port=8259
worker.lb.type=lb
worker.lb.balance_workers=master,slave
worker.jk-status.type=status
在之前的配置文件中,我们将我们的两个 EAP6 服务器配置为 lb 的工作节点。以下图表显示了它们之间的关系:

在配置 workers.properties 之后,我们需要回到 conf.d/httpd-jk.conf 以向我们的集群添加一个挂载点。在 JkWorkersFile 指令下,添加以下代码行:
JkMount /* lb
配置如下所示:

它将告诉 httpd 将所有 HTTP 请求重定向到lb,而lb将通过 AJP13 协议代理请求到 EAP6 服务器。这就是配置的全部内容。现在让我们测试我们的集群。
测试集群
请关闭 httpd 服务器然后重新启动它。如果你已经遵循了前几节中的所有说明,服务器应该可以正确启动。
确保你已经以域模式启动了两个 EAP6 服务器,并且项目cluster-demo1已经部署到了other-server-group。我们将使用这两个工作节点进行测试。
由于我们已经将负载均衡器的公网 IP 地址绑定到了主机名lb,让我们通过主机名来访问它。打开一个网页浏览器并输入 URL http://lb。
如果一切顺利,我们现在应该能看到 EAP 主页:

从之前的截图,我们看到请求被转发到了 EAP6 服务器。现在让我们尝试访问cluster-demo1:
$ curl http://lb/cluster-demo1/index.jsp
<html>
<body>
<h2>Hello World!</h2>
Hello! The time is now Fri Oct 04 00:31:54 CST 2013
</body>
</html>
我们可以检查两个 EAP6 服务器的服务器输出,以查看哪个实际上正在处理这个请求。在我的集群中,这个请求由master-server处理:
[Server:master-server] 00:31:54,264 INFO [stdout] (ajp-/10.0.1.13:8259-3) Hello! The time is now Fri Oct 04 00:31:54 CST 2013
让我们关闭正在处理这个请求的 EAP6 服务器。对于我的集群,我在master-server上按下Ctrl + C来关闭:
00:36:32,078 INFO [org.jboss.as.process] (Shutdown thread) JBAS012015: All processes finished; exiting
然后,我使用cURL再次访问负载均衡器。请求被转发到了另一个 EAP 服务器。这次是slave-server在处理用户请求:
[Server:slave-server] 00:36:39,966 INFO [stdout] (ajp-/10.0.1.19:8259-4) Hello! The time is now Fri Oct 04 00:36:39 CST 2013
从用户的角度来看,集群中一个工作节点关闭不会对他们产生影响。
jk-status 模块
最后,让我们简要看看jk-status模块。从你的负载均衡器机器上打开你的网页浏览器。然后,通过其 URL 访问jk-status:http://localhost/jk-status。
我们将看到两个工作节点的状态:

从之前的截图,我们可以检查两个 EAP6 服务器的运行状态以及它们处理了多少请求。
摘要
在本章中,我们学习了如何使用 JK 作为负载均衡器代理用户请求。正如你所见,JK 非常易于使用且功能强大。它依赖于workers.properties来定义集群的结构。当一个集群中的工作节点崩溃时,JK 会将用户请求故障转移到集群中的其他节点。我们还没有触及 JK 为我们提供的所有功能,例如,细粒度路由匹配和jk-status在管理任务中的使用。你可以参考 JK 在线文档了解这些主题。
第五章:使用 mod_cluster 进行负载均衡
在本章中,我们将探讨另一种负载均衡解决方案。它被称为mod_cluster(www.jboss.org/mod_cluster)。
与 JK 相比,mod_cluster 在设计上更强大和复杂。然而,设计中的额外复杂性并不意味着它更难使用;mod_cluster 被设计成可扩展的,并且可以动态地找到工作节点,从而形成一个集群。
这种灵活性通常会给新手带来困惑,并给他们留下 mod_cluster 难以使用的印象。因此,为了欣赏 mod_cluster 的强大功能和使用的简便性,我们首先必须了解其设计。
mod_cluster 的设计
从上一章,我们知道 JK 使用 TCP 端口通过 AJP13 协议与 JBoss EAP6 服务器通信。与 JK 相比,mod_cluster 使用以下三个通道来执行其功能:
-
连接器通道支持负载均衡器代理用户请求到工作节点的多种协议。这部分几乎等同于 JK。这里的区别在于,除了 AJP13 协议外,mod_cluster 还支持 HTTP/HTTPS 协议。
-
广告通道用于发现工作节点。该通道使用 IP 多播来传输 UDP 数据报。负载均衡器将在多播组中广播自己,而工作节点将通过订阅此组自动找到它。
-
管理通道用于在负载均衡器和工作节点之间传输状态和管理消息。管理通道使用的协议是 HTTP/1.1 协议的扩展。该协议的名称是MCMP。
与 JK 相比,mod_cluster 可以收集工作节点的许多运行时因素来判断其“繁忙程度”,并计算出一个表示每个工作节点“繁忙程度”的数字。这个数字被称为负载因子,而因素被称为度量指标。
小贴士
mod_cluster 为我们提供了多个度量指标来使用,例如SystemMemoryUsageLoadMetric和AverageSystemLoadMetric。完整的度量指标列表可以在此处找到:docs.jboss.org/mod_cluster/1.2.0/html/java.load.html。
mod_cluster 的部署分为两部分:第一部分是负载均衡器,另一部分是工作节点。在我们的场景中,负载均衡器是httpd,mod_cluster 为其提供了一个原生组件。在工作节点端,我们使用 JBoss EAP6,mod_cluster 为其提供了一个子系统。总的来说,让我们概述其结构:

根据前面的图示,mod_cluster 分为两部分:负载均衡器端和工作节点端。此外,它还具有以下三个通道,形成了其功能:
-
advertise通道允许负载均衡器进行自我宣传,并且工作节点可以在运行时动态加入或退出集群。
-
使用mod_manager通道,负载均衡器可以从工作节点获取负载因子信息。在此通道中,使用 MCPM 协议,负载因子以及工作节点的其余信息以固定时间间隔发送。
-
mod_proxy_cluster通道将从后面将用户请求转发到工作节点。它支持类似于 JK 的 AJP13 协议,并且额外支持 HTTP/HTTPS。
在对 mod_cluster 的设计概述之后,我们将在下一节学习如何安装 mod_cluster。
安装 mod_cluster
在本节中,我们将学习如何从源代码编译 mod_cluster 并将其安装在我们的负载均衡器机器上。我用来安装 mod_cluster 和 httpd 的机器被称为lb,与上一章中使用的机器相同。
在上一章中,我们学习了如何编译和安装 httpd,并且在我们的 httpd 安装中放置了许多与 JK 相关的配置。为了使本章的说明更清晰,让我们将上一章的以下 httpd 安装存档:
$ mv httpd httpd-for-jk
我们将在下一章中使用它,所以请妥善备份。我们的下一步是重复上一章中进行的 httpd 编译和安装过程。实际上,我们只需要在 httpd 源目录中重新运行make install,因为我们已经正确配置和编译了它,通过运行此命令我们将获得一个新的 httpd 安装。现在让我们继续 mod_cluster 的安装。
下载 mod_cluster
现在我们需要下载 mod_cluster。mod_cluster 的源代码托管在 GitHub 上。本书中将使用 1.2.6.Final 版本:github.com/modcluster/mod_cluster/archive/1.2.6.Final.zip。
下载它并解压 zip 文件,您将得到源目录mod_cluster-1.2.6.Final。以下是其内容:

在源目录中,我们可以看到 mod_cluster 包含几个组件,但我们只需要关注native目录中的组件。其他 Java 模块是为工作节点准备的。由于 EAP6 已经内置了 mod_cluster 子系统,我们不需要编译它们。现在让我们看看native目录:

您可能已经通过组件的名称猜到了它们的一些用途;我们仍然要逐个检查它们:
| advertise | 支持自动发现工作节点的广告模块 |
|---|---|
| mod_proxy_cluster | 支持 AJP/HTTP/HTTPS 代理请求的代理模块。 |
| mod_manager | 控制工作节点并从工作节点获取负载因子的 mod_cluster 管理模块。 |
| mod_slotmem | mod_cluster 内部使用的共享内存模块。 |
| selinux | SELinux 策略文件。本书不会涉及这个主题。 |
| include | 公共头文件。 |
| scripts | 一些我们不会使用的安装脚本。 |
我们已经理解了这些组件的含义,现在是时候构建它们了。
编译和安装 mod_cluster
我们需要构建的模块是 advertise、mod_proxy_cluster、mod_manager 和 mod_slotmem。你构建哪个模块都无关紧要;让我们从advertise开始。我们需要在以下目录中找到一个名为buildconf的脚本:
mod_cluster-1.2.6.Final/native/advertise$ ls buildconf
buildconf
现在让我们运行这个脚本:
$ ./buildconf
它将创建一个名为configure的脚本。然后我们需要使用以下命令运行此脚本:
$ ./configure --with-apxs=/packt/httpd/bin/apxs
我们使用了--with-apxs选项来告诉 mod_cluster httpd 的位置。配置过程完成后,请运行make命令,我们将得到以下名为mod_advertise.so的共享库:

在前面的库构建完成后,让我们将其移动到 httpd 的modules文件夹:
$ mv mod_advertise.so /packt/httpd/modules/
这就是我们编译和安装advertise需要做的所有事情。转到其他三个模块的目录,并使用相同的程序逐个构建它们。我们将得到mod_proxy_cluster.so、mod_manager.so和mod_slotmem.so。请将它们全部移动到 httpd 的modules目录。
这些是我们需要安装的所有 mod_cluster 组件。在下一节中,我们将配置 httpd 以使用这些模块。
配置 mod_cluster
在 httpd 中安装必要的 mod_cluster 组件后,我们将在本节中正确配置它们。
配置 httpd.conf
在我们开始配置 mod_cluster 之前,我们需要在httpd.conf中做一些准备工作。首先要做的是将Listen指令从Listen 80更改为以下内容:
Listen 10.0.1.33:80
Listen 10.0.1.33:6666
Listen 172.16.123.1:80
如我们所知,lb有两个 IP 地址:一个是公开地址 172.16.123.1,另一个是 10.0.1.33,这是负载均衡器用于与两个 EAP6 服务器通信的内部 IP 地址。现在让我们了解配置的目的:
-
10.0.1.33:80将用于 mod_cluster 管理控制台。我们不希望这个管理控制台公开访问,所以我们只将其绑定到本地 IP 地址。 -
10.0.1.33:6666将由 mod_manager 用于与 EAP6 服务器通信,封装在 MCPM 协议中的消息将通过此通道传输。 -
172.16.123.1:80是服务用户请求的公开地址。如果你没有单独的公开 IP 地址,你可以直接使用你的本地 IP 地址来服务所有请求。
在配置了监听地址后,下一步是配置LogLevel。我们需要将日志级别更改为debug;以下是如何进行配置的:
LogLevel debug
我们需要稍后查看调试日志输出。我们现在将转到 ServerName 部分,并添加我们的负载均衡器的主机名。我们使用 lb 作为主机名,所以配置如下:
ServerName lb
并且请记住将此服务器名绑定到 /etc/hosts 中的公共 IP 地址。接下来,我们需要在 httpd.conf 的底部添加一个 Include 指令:
Include conf.d/*.conf
这就是在 httpd.conf 中我们需要做的所有事情。在下一节中,我们将在 conf.d 目录中为 mod_cluster 创建一个单独的配置文件。
配置 mod_cluster
现在,让我们在 httpd 中创建一个名为 conf.d 的目录:
/packt/httpd$ mkdir conf.d
然后,我们需要在同一个目录下创建一个名为 mod-cluster.conf 的文件:
/packt/httpd/conf.d$ touch mod-cluster.conf
由于 httpd.conf 中的 Include conf.d/*.conf 指令,创建的配置文件将在 httpd 启动时被加载。
现在,让我们将内容添加到这个文件中。首先,我们需要加载以下 mod_cluster 模块:
LoadModule slotmem_module modules/mod_slotmem.so
LoadModule manager_module modules/mod_manager.so
LoadModule proxy_cluster_module modules/mod_proxy_cluster.so
LoadModule advertise_module modules/mod_advertise.so
注意到 mod_cluster 依赖于 httpd.conf 中已经配置的一些模块:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule ssl_module modules/mod_ssl.so
注意
前面的模块已经在 httpd.conf 中被加载。请注意,由于与 mod_cluster 冲突,httpd 中的 proxy-balancer 模块已被禁用。
现在我们需要定义两个虚拟主机:一个用于网页管理控制台,另一个用于管理模块发送/接收 MCPM 消息。让我们逐个来看。以下是第一个的配置:
<VirtualHost 10.0.1.32:80>
<Directory />
Order deny,allow
Deny from all
Allow from 10.0.1
</Directory>
<Location /mc>
SetHandler mod_cluster-manager
Order deny,allow
Deny from all
Allow from 10.0.1
</Location>
</VirtualHost>
在之前的虚拟主机定义中,我们已经定义了一个名为 /mc 的位置,并将其绑定到 mod_cluster-manager。这个处理器将为我们提供一个基于网页的管理控制台,我们将在后面的章节中使用。现在让我们检查第二个虚拟主机定义:
<VirtualHost 10.0.1.32:6666>
<Directory />
Order deny,allow
Deny from all
Allow from 10.0.1
</Directory>
ServerAdvertise on http://10.0.1.32:6666
EnableMCPMReceive
</VirtualHost>
在前面的设置中,有两个重要的指令。一个是 ServerAdvertise 指令。在此指令中设置的地址将由 mod_cluster 在多播组中广播。例如,我们的设置如下:
ServerAdvertise on http://10.0.1.32:6666
因此,mod_cluster 将通过说类似这样的话来在多播组中广播:“我的 MCPM 管理通道位于 http://10.0.1.32:6666,来加入我吧!”订阅了多播组的 worker 节点将接收到这个信息,然后可以加入集群。
请注意,我们不需要配置广告的组播地址。这是因为广告的默认地址是 224.0.1.105:23364,这与 EAP6 的默认设置相匹配。我们将在下一节中看到这一点。如果您想更改此设置,您可以使用 AdvertiseGroup 指令,将其放置在 ServerAdvertise 指令下:
AdvertiseGroup <some_other_multicast_addr:some_other_port>
小贴士
您可以随时查看 mod_cluster 的在线文档,了解这些详细配置:(docs.jboss.org/mod_cluster/1.2.0/html/native.config.html)。
现在让我们看看下面的指令:
EnableMCPMReceive
使用前面的指令,虚拟主机 10.0.1.32:6666 被用作管理通道,MCPM 被用作此通道的通信协议。这就是我们在 mod-cluster.conf 中需要做的所有事情。
配置 EAP6
到目前为止,我们还没有查看 EAP6 中的 modcluster 子系统配置。由于 EAP6 域模式提供的默认配置已经很好用,我们不需要做任何更改。但让我们看一下配置。
从 domain.xml 中的配置,我们可以看到 modcluster 子系统的以下默认设置:
<subsystem >
<mod-cluster-config advertise-socket="modcluster" connector="ajp">
<dynamic-load-provider>
<load-metric type="busyness"/>
</dynamic-load-provider>
</mod-cluster-config>
</subsystem>
我们可以看到,modcluster 子系统通过 advertising-socket 指令绑定到名为 modcluster 的广告套接字。然后我们看到,modcluster 子系统默认使用 busyness 指标。这是一个从工作线程判断服务器“忙碌程度”的指标。现在让我们看看 modcluster 套接字绑定的设置:
<socket-binding-group name="full-ha-sockets" default-interface="public">
<socket-binding name="modcluster" port="0" multicast-address="224.0.1.105" multicast-port="23364"/>
</socket-binding-group>
从前面的配置中,我们可以看到 224.0.1.1.105:23364 是广告的默认多播组地址。这与 httpd 端的设置相匹配。
这些设置是 EAP6 中的 modcluster 子系统的设置。因为我们已经从两端了解了 mod_cluster 的设置,所以在下一节中,我们将测试集群。
测试集群
在本节中,我们将测试我们的集群,因此我们需要启动我们的负载均衡器和 EAP6 服务器。在我们开始在 lb 上启动 httpd 之前,我们需要启动两个 EAP6 服务器。在两个 EAP6 服务器启动后,再启动 httpd。在以下章节中,我们将检查启动 httpd 的过程。
启动 httpd
现在我们需要启动 httpd 服务器。如果一切顺利,httpd 中的 mod_cluster 将开始在一个多播组中广播自己,两个 EAP6 服务器中的 modcluster 子系统将通过在广播通道中获取其地址来找到 httpd。我们将在以下章节中调查这个过程;让我们首先启动 httpd。在 lb 上启动 httpd 的命令如下:
/packt/httpd/bin$ sudo ./httpd -f /packt/httpd/conf/httpd.conf -k start
在 httpd 启动后,mod_cluster 将向多播组 224.0.1.105:23364 广播自己,两个 EAP6 服务器上的 modcluster 子系统将从该组中获取管理通道的地址,即 10.0.1.32:6666。然后负载均衡器和两个 EAP6 工作节点将通过使用 MCPM 协议在管理通道中进行通信来形成一个集群。这个过程在以下图中展示:

为了理解这些步骤,我们需要分析通过网络发送的数据包。
协议分析
我们可以使用 Wireshark 捕获来自一个工作节点的一个 IP 数据包。在我的例子中,我将在 master 上运行 Wireshark。我们可以验证这台机器上负载均衡器发送的广告消息。
小贴士
也有一个小型的 Java 程序,允许我们加入多播组并接收 httpd 广告。请参阅此程序github.com/mod_cluster/mod_cluster/blob/master/test/java/Advertize.java。
广告通道
我已经在master上启动了 Wireshark 来捕获 IP 数据包。我将它设置为捕获 224.0.1.105:23364 上的所有数据包,以下是我的发现:

从前面的截图可以看出,master定期接收广告消息。从时间列中,我们可以看到广告消息以 10 秒的间隔发送。
小贴士
您可以通过将AdvertiseFrequency指令放入conf.d/mod-cluster.conf来更改此设置。
我们可以看到广告信息是以 HTTP 格式。以下是该消息的详细信息:
HTTP/1.0 200 OK
Date: Sat, 05 Oct 2013 16:50:49 GMT
Sequence: 2387
Server: 093651cf-0c7f-4fad-aae2-ea1c23c9c339
X-Manager-Address: 10.0.1.32:6666
X-Manager-Url: /093651cf-0c7f-4fad-aae2-ea1c23c9c339
X-Manager-Protocol: http
X-Manager-Host: cute
如前述代码所示,广告信息将负载均衡器信息放置在 HTTP 头部中。在这些头部中,我们应该注意X-Manager-Address的值。它告诉工作节点在哪里找到负载均衡器。其他头部为工作节点提供额外的信息;这些信息描述了负载均衡器。
管理通道
在工作节点得知负载均衡器的管理地址后,它将与负载均衡器通信并在其上注册自己。要查看此过程,我们需要查看 httpd 日志。由于我们在httpd.conf中设置了LogLevel为debug,我们可以获取许多有用的详细信息。mod_cluster 在调试级别输出了很多有用的日志信息,因此我们可以检查logs/error_log以清楚地查看序列。以下是日志:
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(1910): manager_trans INFO (/)
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2618): manager_handler INFO (/) processing: ""
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2667): manager_handler INFO OK
INFO是我们迄今为止看到的第一个 MCMP 命令。这是工作节点用来请求更多负载均衡器详细信息的命令。由于 mod_cluster 动态形成集群,它事先不知道集群的详细信息。负载均衡器和工作节点只在多播通道中相互发现,因此工作节点需要更多关于负载均衡器的详细信息。这就是为什么工作节点会向负载均衡器发送INFO请求,负载均衡器会以INFO-RSP响应。
现在,让我们看看下一步:
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(1910): manager_trans CONFIG (/)
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2618): manager_handler CONFIG (/) processing: "JVMRoute=14a0af8b-59dd-33f9-8233-1f2584fefa67&Host=10.0.1.19&Maxattempts=1&Port=8259&StickySessionForce=No&Type=ajp&ping=10"
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2667): manager_handler CONFIG OK
工作节点获取到负载均衡器的详细信息后,会向负载均衡器发送一个CONFIG消息,告知负载均衡器之前的工作节点详细信息。JVMRoute是工作节点的名称;这是由 EAP6 中的 mod_cluster 子系统自动生成的。现在我们知道服务器14a0af8b-59dd-33f9-8233-1f2584fefa67对应于服务器10.0.1.19,这是我们从属的 EAP6 服务器。
让我们检查下一步:
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(1910): manager_trans ENABLE-APP (/)
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2618): manager_handler ENABLE-APP (/) processing: "JVMRoute=14a0af8b-59dd-33f9-8233-1f2584fefa67&Alias=default-host%2Clocalhost%2Cexample.com&Context=%2Fcluster-demo1"
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2667): manager_handler ENABLE-APP OK
工作节点向负载均衡器发送ENABLE-APP。这是工作节点用来请求负载均衡器将对应于上下文和Alias值的请求路由到由JVMRoute定义的节点。此外,我们看到已启用的应用程序是cluster-demo1。因此,如果我们通过路由/cluster-demo1访问负载均衡器 URL,请求将被转发到 EAP6 服务器。现在让我们看看下一步:
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(1910): manager_trans STATUS (/)
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2618): manager_handler STATUS (/) processing: "JVMRoute=14a0af8b-59dd-33f9-8233-1f2584fefa67&Load=100"
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(1625): Processing STATUS
工作节点发送包含其当前负载因子的STATUS消息。从前一个日志的第二行,我们可以看到从属 EAP6 服务器的负载因子为100(因子数字越小,服务器越忙)。此消息定期发送,负载因子刷新以反映工作节点的实时状态。因此,负载均衡器可以知道哪个服务器更忙,并将此请求发送到负载较轻的工作节点。
通过前面的过程,工作节点和负载均衡器已经收集了足够的信息,并建立了通信。然后,mod_proxy_cluster.so集群将开始执行实际的代理工作。在下一节中,我们将检查这部分。
连接器通道分析
在前一节中,我们看到了负载均衡器如何自我宣传,以及工作节点如何发现它并将自己注册到集群中。现在让我们继续检查error_log,看看发生了什么:
[Sat Oct 05 18:09:15 2013] [debug] mod_proxy_cluster.c(655): add_balancer_node: Create balancer balancer://mycluster
[Sat Oct 05 18:09:15 2013] [debug] mod_proxy_cluster.c(426): Created: worker for ajp://10.0.1.19:8259
[Sat Oct 05 18:09:15 2013] [debug] proxy_util.c(2018): proxy: ajp: has acquired connection for (10.0.1.19)
[Sat Oct 05 18:09:15 2013] [debug] proxy_util.c(2074): proxy: connecting ajp://10.0.1.19:8259/ to 10.0.1.19:8259
[Sat Oct 05 18:09:15 2013] [debug] proxy_util.c(2200): proxy: connected / to 10.0.1.19:8259
根据前面的日志,mod_cluster 已与ajp://10.0.1.19:8259建立连接。这是从属主机上 EAP6 服务器的 AJP 连接器。我们可以看到,mod_cluster 已为我们集群设置了一个名称,即mycluster。我们可以从管理控制台检查此集群的状态。让我们通过其 URL http://10.0.1.32/mc 访问管理控制台。
这在下面的屏幕截图中有显示:

从前面的屏幕截图,我们可以看到两个 EAP6 服务器形成一个集群,它们都属于mycluster。如果您想更改均衡器名称,您可以使用mod-cluster.conf中的ManagerBalancerName指令,如下所示:
<VirtualHost 10.0.1.32:6666>
...
ServerAdvertise on http://10.0.1.32:6666
EnableMCPMReceive
ManagerBalancerName packtlb
</VirtualHost>
根据前面的配置,均衡器名称设置为packtlb。现在,如果我们保存更改并重新启动 httpd,我们可以看到均衡器名称相应地更改:
Node 14a0af8b-59dd-33f9-8233-1f2584fefa67 (ajp://10.0.1.19:8259):
Balancer: packtlb, ...
Node da1db862-4021-36f5-b3ad-b71b61b79c3b (ajp://10.0.1.13:8259):
Balancer: packtlb, ...
当同时运行多个负载均衡器时,这很有用。通过均衡器名称,我们可以轻松地看到哪个工作节点属于哪个负载均衡器。
现在,让我们回到调试日志;这是我们最后需要查看的部分:
[Sat Oct 05 18:09:15 2013] [debug] mod_proxy_cluster.c(1366): proxy_cluster_try_pingpong: connected to backend
[Sat Oct 05 18:09:15 2013] [debug] mod_proxy_cluster.c(1089): ajp_cping_cpong: Done
[Sat Oct 05 18:09:15 2013] [debug] proxy_util.c(2036): proxy: ajp: has released connection for (10.0.1.19)
[Sat Oct 05 18:09:15 2013] [debug] mod_manager.c(2667): manager_handler STATUS OK
在 AJP 通道建立后,来自 httpd 侧的 mod_cluster 将定期发送ajp_cping_cpong消息到 EAP6 工作节点,以检查节点是否仍然存活。
由于我们已经完成了协议分析,在下一节中,我们将访问集群以查看其是否正常工作。
访问集群
我们可以通过其 URL 访问负载均衡器:http://lb/cluster-demo1/index.jsp。
通过检查两个 EAP6 服务器的输出,我们可以看到请求被分配到master:
[Server:master-server] 02:34:49,395 INFO [stdout] (ajp-/10.0.1.13:8259-4) Hello! The time is now Mon Oct 07 02:34:49 CST 2013
现在我们检查 mod_cluster 管理控制台,我们可以看到主服务器已被选举一次:

如前一张截图所示,主服务器的选举次数变为1。
故障转移
现在让我们通过按Ctrl + C键来终止主服务器:
02:41:36,977 INFO [org.jboss.as.process] (Shutdown thread) JBAS012015: All processes finished; exiting
从从服务器,我们可以看到它开始抛出以下异常,因为它无法连接到主服务器:
[Host Controller] 02:41:47,971 DEBUG [org.jboss.as.host.controller] (domain-connection-threads - 8) failed to reconnect to the remote host-controller: java.net.ConnectException: JBAS012144: Could not connect to remote://10.0.1.13:9999\. The connection timed out
这是预期的,因为我们已经终止了主服务器。但它仍然可以作为服务器运行。现在让我们再次访问负载均衡器;我们可以看到以下请求被分配到从服务器:
[Server:slave-server] 02:44:45,285 INFO [stdout] (ajp-/10.0.1.19:8259-2) Hello! The time is now Mon Oct 07 02:44:45 CST 2013
现在让我们访问 mod_cluster 管理控制台,我们可以看到主服务器已被自动移除。此外,从服务器的选举次数变为1:

现在让我们在主服务器上重启 EAP6 服务器,它将重新加入集群。从 httpd 调试日志输出中,我们可以确认如下:
[Mon Oct 07 04:01:31 2013] [debug] proxy_util.c(2200): proxy: connected / to 10.0.1.13:8259
从从服务器输出中,我们可以看到它也恢复了与域控制器的连接:
[Host Controller] 04:01:05,379 INFO [org.jboss.as.host.controller] (domain-connection-threads - 1) JBAS010916: Reconnected to master
压力测试
现在让我们尝试使用 Apache HTTP 服务器基准测试工具(称为ab)来对我们的集群进行负载测试。以下是命令:
$ ab -c 15 -n 1500 http://lb/cluster-demo1/index.jsp
我们使用了15个线程向我们的集群发送1500次请求。以下是结果:
Benchmarking cute (be patient)
Completed 150 requests
...
Finished 1500 requests
您可以看到 ab 在 mod_cluster 上创建了一些负载。以下是测试期间两个工作节点状态的详细信息:

上一张截图显示了主服务器状态,下一张截图显示了从服务器状态:

我们可以看到两个工作节点都被选举了(选举次数超过 1,500 次,因为我已经多次运行了前面的测试)。我们还可以看到从服务器的负载因子变为99。这意味着从服务器比主服务器更忙,因此负载均衡器将在稍后分配更多请求到主服务器。
摘要
在本章中,我们探讨了 mod_cluster 的设计及其用法。本章只是对 mod_cluster 的入门介绍。如果您想查看更高级的用法,请参阅其不断改进的在线文档:docs.jboss.org/mod_cluster/1.2.0/html_single/。
如果您对使用 mod_cluster 有任何疑问,您可以在 JBoss 论坛上提问:community.jboss.org/en/mod_cluster/content。
在下一章中,我们将看到如何在集群环境中应用安全套接字层(SSL)。
第六章. 使用 SSL 进行集群
在前两个章节中,我们学习了如何使用JK和mod_cluster作为负载均衡器代理用户请求到 EAP6 后端服务器,并且负载均衡器和 EAP6 服务器之间的所有通信都是明文传输的。在实践中,有时我们需要通过启用SSL来确保传输层的安全。在本章中,我们将学习如何在集群环境中启用 SSL。我们首先将学习如何在独立使用 JBoss EAP6 时启用 SSL,然后我们将学习如何在 httpd 和 EAP6 服务器一起运行的集群环境中启用 SSL。对于集群环境,在本章中我们将使用JK作为负载均衡器。因为使用mod_cluster已经提供了与 SSL 更精细的集成,我们将在下一章讨论这个话题。
注意
在阅读本章之前,您需要具备一些关于公钥加密和 SSL 的基本知识。
在 JBoss EAP6 中使用 SSL
首先,让我们看看如何在 EAP6 中直接启用 SSL。当我们使用 EAP6 作为独立服务器且前面没有任何负载均衡器时,这很有用。JBoss EAP6 自带 SSL 支持,在本节中,我们将看看如何启用它。
在 EAP6 中启用 SSL
要在 EAP6 中启用 SSL,我们需要为 EAP6 服务器创建一个 x.509 证书。首先准备一个干净的JBoss EAP 6.1.0.Final副本,以确保配置是默认的。当 EAP6 服务器的干净副本准备好使用时,请以独立模式启动它,然后将cluster-demo1部署到运行的服务器上,然后停止服务器。这就是我们需要的准备工作。我们将用它来测试 HTTPS 连接。
现在让我们在 EAP6 基本路径下创建一个名为certs的目录。我们将用它来存储服务器证书。
然后,我们需要导航到certs目录,并使用Java 运行时环境(JRE)提供的keytool命令为 EAP6 服务器生成一个证书。以下是命令:
$ keytool -genkey -keystore myserver.ks
运行过程如下截图所示:

密钥库密码和密钥密码是packt000。请注意,在生产环境中,我们必须将CN设置为我们的网站主机名。对于这个例子,我的主机名叫做mini,所以我将其用作证书的CN。现在让我们检查生成的keystore文件及其包含的密钥:

如前一个屏幕截图所示,我们生成的密钥及其证书存储在 keystore 中。密钥的默认 别名 是 mykey,其证书与之关联。如果我们仔细观察,我们可以看到这个证书的 发行者 和 所有者 是相同的。这意味着这是一个自签名证书。在生产环境中,我们需要找到一个权威机构(如 VeriSign)来签署这个证书。在权威机构签署后,发行者 将会变为该权威机构。
现在我们需要配置 EAP6 Web 子系统以使用密钥及其证书。让我们打开 standalone.xml 并找到子系统 urn:jboss:domain:web:1.4。然后我们需要找到一个 HTTP 连接器并将其方案更改为 HTTPS。接下来,我们需要添加一个 SSL 元素来告诉 EAP6 我们 keystore 的位置和我们的密钥的别名。修改应如下所示:

那就是我们需要在 standalone.xml 中配置的所有内容。此外,请注意配置文件中 HTTPS 使用的端口是 8443:
<socket-binding name="https" port="8443"/>
因此,我们需要使用这个端口来访问 EAP6 Web 子系统。现在我们可以启动 EAP6 服务器并测试 HTTPS 连接:
$ curl -k https://localhost:8443/cluster-demo1/index.jsp
<html>
<body>
<h2>Hello World!</h2>
Hello! The time is now Tue Nov 19 20:40:52 CST 2013
</body>
</html>
cURL 的 -k 选项是绕过证书验证。由于我们的证书未由权威机构签署,默认情况下它不被 cURL 或任何其他网络浏览器信任。
在本节中,我们学习了如何在 EAP6 独立模式下启用 SSL。在域模式下启用 SSL 类似;我们还需要将 Web 子系统设置为使用 HTTPS 方案,并在 SSL 元素中添加证书信息。我想把这个任务留给你。
在 JBoss EAP6 集群中使用 SSL
在集群环境中,应用 SSL 并不像在单服务器环境中那样直接。我们有一个集群中的负载均衡器和工作节点,因此我们需要决定在哪个位置启用 SSL。以下是两个可能的位置:
-
用户与负载均衡器之间的通信
-
负载均衡器与工作节点之间的通信
实际上,我们通常在用户和负载均衡器之间启用 SSL 以确保他们的通信安全,并在负载均衡器和工作节点之间使用 明文 通信。以下是部署图:

这是合理的,因为工作节点通常由防火墙保护,使用 SSL 的目的不仅是为了加密通信通道,而且由权威机构签署的证书还可以帮助客户的网络浏览器验证 Web 服务器的身份。
在负载均衡器和工作节点之间启用 SSL 通信也会在通信层创建许多开销,加密/解密网络数据会消耗 CPU 功率。
JK 不支持负载均衡器和工作节点之间的 SSL 通信,正如我之前解释的那样,在大多数情况下这不是问题,因为 mod_cluster 支持负载均衡器和工作节点之间的安全连接,我们将在下一章中看到如何配置它。
配置 JK 使用 SSL
现在让我们开始学习如何使用 JK 启用 SSL。首先,让我们看看部署图:

实际上我们只需要在 Apache httpd 中启用 SSL。正如我们所知,mod_jk 是一个轻量级负载均衡器,它只支持到工作节点的 AJP 连接。这意味着 JK 和 EAP6 服务器之间的通信将是 明文 AJP13 协议。
要在 httpd 中启用 SSL,我们需要做一些准备工作。请使用 JK 安装恢复 httpd,我们将基于它配置 SSL。如果你忘记了如何正确配置 httpd 和 JK,请再次阅读第四章,使用 mod_jk 进行负载均衡。
为 httpd 生成证书
现在让我们为 httpd 准备服务器证书。首先,在 httpd 中创建一个 certs 目录:
/packt/httpd$ mkdir certs
然后,我们需要导航到 certs 目录并为 httpd 生成一个证书。对于 httpd,我们需要使用 OpenSSL 来生成证书,因为 httpd 不支持 Java 应用程序使用的 keystore 格式。实际上,证书格式都是相同的,但 keytool 和 OpenSSL 生成的存储结构是不同的。OpenSSL 将密钥和证书分离成独立的文件,而 keytool 则将它们存储在一个单独的 keystore 文件中。
现在,让我们生成一个密钥,以下是命令及其过程:
$ openssl genrsa -des3 -out lb.key 1024
Generating RSA private key, 1024 bit long modulus
Enter pass phrase for lb.key: packt000
Verifying - Enter pass phrase for lb.key: packt000
接下来,我们需要为这个密钥文件生成一个相关的证书文件。以下是命令及其过程:
$ openssl req -new -key lb.key -out lb.csr
Enter pass phrase for lb.key: packt000
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Personal
Organizational Unit Name (eg, section) []:Personal
Common Name (e.g. server FQDN or YOUR name) []: lb
Email Address []:
A challenge password []:
An optional company name []:
现在我们得到了证书请求文件 lb.csr 和相关的密钥文件 lb.key。接下来是签署证书请求文件。因为这个证书是用于测试的,我们不需要找到一个权威机构来签署它。我们可以使用密钥文件来签署其自己的证书请求文件。所以这是一个自签名证书。以下是命令和运行过程:
$ openssl x509 -req -days 365 -in lb.csr -signkey lb.key -out lb.crt
Signature ok
subject=/C=CN/ST=Beijing/L=Beijing/O=Personal/OU=Personal/CN=kb
Getting Private key
Enter pass phrase for lb.key: packt000
签名的证书文件已生成,文件名为 lb.crt。这个文件是标准格式,因此我们也可以使用 keytool 来检查其内容,如下面的代码所示:
$ keytool -v -printcert -file lb.crt
Owner: CN=kb, OU=Personal, O=Personal, L=Beijing, ST=Beijing, C=CN
Issuer: CN=kb, OU=Personal, O=Personal, L=Beijing, ST=Beijing, C=CN
从前面的代码片段中,我们可以看到这个证书的 Owner 和 Issuer 是相同的。这就是使用 OpenSSL 生成自签名证书的过程。实际上,我们可以将前面的过程合并成一个命令:
$ openssl req -new -newkey rsa -days 365 -x509 -subj "/C=CN/ST=Beijing/L=Beijing/O=Personal/OU=Personal/CN=httpd" -keyout lb.key -out lb.crt
使用前面的命令,我们可以一步生成密钥文件及其自签名证书。
配置 httpd 使用证书
我们需要在httpd.conf中添加几个指令。第一步是让 httpd 监听端口443,这是 HTTPS 的标准端口。我正在重用前两章中的集群配置,所以我仍然在我的机器lb上运行 httpd,并将它配置为监听公共 SSL 端口:
Listen 172.16.123.1:443
并且不要忘记注释掉对端口 80 的访问,因为我们不希望用户在不使用 HTTPS 的情况下连接:
#Listen 172.16.123.1:80
现在我们需要配置JK,其配置文件位于conf.d/httpd-jk.conf。请删除其中的所有内容,并替换为以下内容:
LoadModule jk_module modules/mod_jk.so
<IfModule jk_module>
JkWorkersFile conf/workers.properties
JkShmFile logs/mod_jk.shm
JkWatchdogInterval 60
<VirtualHost 172.16.123.1:443>
SSLEngine on
SSLCertificateFile /packt/httpd/certs/lb.crt
SSLCertificateKeyFile /packt/httpd/certs/lb.key
JkMount /* lb
JkLogFile logs/mod_jk.log
JkLogLevel debug
</VirtualHost>
</IfModule>
如前所述的配置所示,我们添加了一个绑定到端口443的VirtualHost。此外,我们已启用 SSL 引擎,并提供了服务器证书及其密钥文件以使用。此外,我们已将 JkMount 点放在此虚拟主机内,因此用户对 HTTPS 的请求将通过JK代理并发送到后面的 EAP6 服务器。
这就是在 httpd 中需要配置的所有内容。因为 httpd 和 EAP6 服务器之间的通信仍在使用明文的 AJP13 协议,所以我们不需要在 EAP6 中更改任何配置。现在我们可以启动 EAP6 服务器和 httpd 服务器。在 httpd 服务器启动过程中,它需要我们输入我们的密钥的密码短语。过程如下面的代码片段所示:
$ sudo httpd -k start -f /packt/httpd/conf/httpd.conf
Apache/2.2.25 mod_ssl/2.2.25 (Pass Phrase Dialog)
由于安全原因,一些你的私钥文件已被加密。
为了阅读它们,你必须提供密码短语。
Server localhost:443 (RSA)
Enter pass phrase: packt000
OK: Pass Phrase Dialog successful.
如果你已经在httpd.conf中将LogLevel设置为debug,你可以在logs/error_log中看到许多与 SSL 相关的日志输出。如果出现问题,这是一个很好的分析来源。现在我们可以通过 HTTPS 访问负载均衡器,并看到请求被转发到 EAP6 服务器:
$ curl -k https://172.16.123.1/cluster-demo1/index.jsp
<html>
<body>
<h2>Hello World!</h2>
Hello! The time is now Tue Nov 19 22:40:52 CST 2013
</body>
</html>
如果我们使用Wireshark来监控 httpd 和 EAP6 之间的代理通道,我们可以看到它们仍在使用明文的 AJP13 协议进行通信:

摘要
在本章中,我们对将 SSL 应用于 EAP6 和集群环境进行了概述,并看到了如何将 SSL 和JK一起配置到 httpd 中。在下一章中,我们将学习如何使用mod_cluster应用 SSL。
第七章. 使用 SSL 配置 mod_cluster
在上一章中,我们学习了如何使用 SSL 与 JK(mod_jk)一起使用。在本章中,我们将首先检查 mod_cluster 的设计,并讨论如何与 SSL 一起使用。然后我们将学习如何配置 httpd 和 EAP6 服务器,以便它们可以使用 SSL。
首先,让我们检查 mod_cluster 的设计。
mod_cluster 的设计
正如我们在上一章中看到的,JK 在负载均衡器和工作节点之间使用 AJP13 协议。与 JK 相比,mod_cluster 允许我们确保其通信通道的安全性。
在设计中,mod_cluster 使用三个通道进行通信:广告通道、管理通道和代理通道。mod_cluster 允许我们在管理通道和代理通道中确保通信的安全性。
下面的部署图:

如前图所示,SSL 通信可以在以下三个地方启用:
-
用户与 httpd 之间的通信
-
httpd 和 EAP6 之间的 mod_cluster 管理通道通信
-
httpd 和 EAP6 之间的 mod_cluster 代理通道通信
在上一章中,我们学习了如何使用 SSL 在用户和 httpd 之间启用通信。我们需要生成一个自签名证书,并在 httpd 中配置 mod_ssl 以使用该证书。以下是我们上一章使用的配置:
SSLEngine on
SSLCertificateFile /packt/httpd/certs/lb.crt
SSLCertificateKeyFile /packt/httpd/certs/lb.key
前面的配置实际上是针对 mod_ssl 的;因此,当我们从 JK 切换到 mod_cluster 的负载均衡器组件时,它保持不变。
注意
请检查本章代码中的mod-cluster-ssl.conf文件。
除了 httpd 对纯 mod_ssl 的支持以启用用户和 httpd 之间的 SSL 通信外,mod_cluster 还提供了额外的功能,以确保 httpd 和 EAP6 服务器之间数据传输的安全性。在接下来的章节中,我们将探讨如何为提到的其他两个地方启用安全通信。首先,我们将学习如何在 mod_cluster 管理通道中启用 SSL。
启用 mod_cluster 管理通道的 SSL
在本节中,我们将学习如何保护 MCMP 通道。这意味着 MCMP 消息将通过 SSL 传输。
小贴士
在 mod_cluster 文档中,我们称该协议为 MCMP,代表Mod-Cluster Management Protocol。
在设计中,mod_cluster 使用 SSL 双向认证来保护 MCMP 通道。这意味着我们必须为服务器创建证书。EAP6 需要信任 httpd 提供的证书;同时,httpd 必须信任来自 EAP6 服务器的证书。因此,我们需要两个证书而不是一个。
SSL 双向认证简介
在上一章中,我们为 httpd 创建了一个自签名证书,以启用与用户的 HTTPS 通信。当用户使用网络浏览器访问我们的网站时,httpd 将向网络浏览器提供其证书。如果用户选择信任该证书,网络浏览器将与我们的服务器建立安全连接。这被称为单向认证。这意味着用户将验证网站的标识,但网站不会验证用户的身份。
由于我们的证书是自签名的,网络浏览器将弹出警告,告知用户它无法验证此证书的身份,用户需要决定是否信任它。但如果我们的证书由权威机构签发,网络浏览器将信任我们的证书而不会弹出任何警告信息。这是因为每个网络浏览器都包含一个默认的权威机构列表。这些权威机构签发的证书将被默认信任。
例如,我可以在我的 Firefox 浏览器中看到以下默认 CA 列表:

Firefox 只会信任这些权威机构签发的证书。JDK 也包含这样一个列表,通常是$JAVA_HOME/jre/lib/security/cacerts文件。
现在,让我们来谈谈加密 httpd 和 EAP6 之间的通信。首先,我们将了解单向认证。这种情况在下面的图中展示:

由于 EAP6 运行在 JVM 中,它信任一组默认的 CA。我们的自签名证书显然不是由这些机构签发的;因此,默认情况下,EAP6 不会信任它。
为了解决这个问题,我们可以自己创建一个权威机构,并使用这个权威机构来签发证书。然后,我们可以将这个权威机构放置在 EAP6 中,以覆盖默认的信任列表,这样 EAP6 就会信任它所签发的证书。这种情况在下面的图中展示:

使用之前的方法,我们可以从 httpd 到 EAP6 建立单向认证。如果我们想建立双向 SSL 认证,我们需要为 EAP6 额外创建一个证书,并用 myca 签发它。然后,我们需要配置 httpd 将 myca 放入其信任列表,以便它信任 EAP6 的证书。下面的图展示了这种情况:

从这个图中我们可以看到,httpd将向EAP6展示其证书,EAP6也将向httpd展示其证书。只有在他们的证书由 myca 签发,并且 myca 在他们的信任列表中时,他们才会相互信任。mod_cluster 强制执行这种双向 SSL 认证。在接下来的章节中,我们将学习如何正确配置 httpd 和 EAP6。
配置 SSL 双向认证
由于我们已经学习了 SSL 互信认证的概念,现在让我们配置我们的环境以启用它。我们需要创建以下三个证书:
-
一个作为权威机构的自签名证书。让我们称它为
myca.crt。 -
为 httpd 创建一个由 myca 签署的证书。让我们称它为
httpd.crt。 -
为 EAP6 创建一个由 myca 签署的证书。让我们称它为
eap6.crt。
让我们逐个创建它们。
创建 CA 证书
首先,我们将创建一个 CA 证书。术语 CA 代表 证书授权机构;它实际上是一个自签名证书,将用于签署其他证书。如果一个应用程序将其 CA 放入其信任列表中,由它签署的证书将被信任。
我们将创建一个名为 myca 的自签名证书,并将其用作我们的 CA。在我们创建它之前,请准备一个名为 certs 的目录,并将其放置在适当的位置。由于我们将创建三个证书及其密钥文件,最好将它们放在一起。对我来说,我会将其放在 /packt/certs。
请使用以下命令创建一个自签名证书及其密钥文件:
$ opensslreq -new -newkeyrsa -days 365 -x509 -subj "/C=CN/ST=Beijing/L=Beijing/O=Personal/CN=myca" -keyoutmyca.key -out myca.crt
Generating a 2048 bit RSA private key
..++++++
........................++++++
writing new private key to 'myca.key'
Enter PEM pass phrase: packt000
Verifying - Enter PEM pass phrase: packt000
-----
使用前面的命令,我已经生成了一对密钥及其自签名证书,该证书的 CN 是 myca。我们可以通过以下方式检查证书内容来查看这一点:
$ keytool -printcert -file myca.crt | head -n 2
Owner: CN=myca, O=Personal, L=Beijing, ST=Beijing, C=CN
Issuer: CN=myca, O=Personal, L=Beijing, ST=Beijing, C=CN
如我们所见,Owner 和 Issuer 是相同的。现在,让我们为 httpd 创建一个证书,并用 myca 签署它。
为 httpd 创建证书
由于我们现在有了 myca 权威机构,让我们为 httpd 创建一个证书,然后用 myca 签署它。我们首先需要使用以下命令为 httpd 创建一个密钥对:
$ openssl genrsa -des3 -out httpd.key 1024
Generating RSA private key, 1024 bit long modulus
.........++++++
..........................................................++++++
e is 65537 (0x10001)
Enter pass phrase for httpd.key: packt000
Verifying - Enter pass phrase for httpd.key: packt000
如前所述命令所示,密钥文件名为 httpd.key。接下来,我们将创建与密钥对相关的证书;以下命令执行此操作:
$ openssl req -new -key httpd.key -out httpd.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Personal/CN=httpd"
Enter pass phrase for httpd.key: packt000
请注意,我们证书的 CN 值是 httpd。运行前面的命令后,我们得到等待签署的 http.csr 文件。现在,我们需要使用 OpenSSL 提供的工具使用 myca 签署此证书,但在那之前,我们需要正确设置 OpenSSL。
设置 OpenSSL
OpenSSL 有自己的证书签名工作流程。请运行以下命令以设置 OpenSSL 的工作环境:
$ mkdir -p demoCA/newcerts
$ touch ./demoCA/index.txt
$ touch ./demoCA/serial
$ echo "01" > ./demoCA/serial
我们签署的证书将被放置在 newcerts 目录中,并命名为 01.pem。请确保您已从包含 myca 和 httpd 证书的 certs 目录运行了前面的命令。
下一步是放宽 OpenSSL 的签名策略。首先,我们需要定位 OpenSSL 使用的配置文件。请运行以下命令:
$ openssl ca
Using configuration from /etc/pki/tls/openssl.cnf
…
我们可以从之前的命令中看到 OpenSSL 使用的配置文件的位置。让我们打开 openssl.cnf 并找到 policy_match 部分。除了 commonName 之外,我们需要将此部分的所有项更改为 optional。以下截图列出了详细信息:

此外,我们还需要更改 OpenSSL 默认使用的目录来签署证书;以下是操作步骤:
#dir = /etc/pki/CA
dir = demoCA
注意
我们更改 OpenSSL 默认设置只是为了测试目的。实际上,这将降低证书的安全性。
我们在生产环境中签署证书时应该始终使用默认位置,并仔细管理它们的权限。
此外,真正的 CA 机构通常会要求你提供包含它所需有效信息的证书。例如,可能的要求是,你的证书的 CN 必须匹配你的 DNS 主机名,而你所在国家的名称必须匹配你的主机位置。
签署 httpd.csr 文件
由于我们已经正确设置了 OpenSSL,我们现在可以开始签署我们的 httpd 证书。请确保您位于certs目录中,并且它包含一个包含必要文件内容的demoCA目录。请运行以下命令来签署 httpd 证书:
$ openssl ca -in httpd.csr -keyfile myca.key -cert myca.crt
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for myca.key: packt000
Check that the request matches the signature
Signature ok
Certificate Details:
...
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n] y
Write out database with 1 new entries
...
Data Base Updated
现在,我们已经签署了httpd.csr;这个已签名的证书位于demoCA/newcerts中,名称为01.pem。让我们检查此证书的内容:
$ cat demoCA/newcerts/01.pem
Certificate:
Issuer: C=CN, ST=Beijing, L=Beijing, O=Personal, CN=myca
Subject: CN=httpd, C=CN, ST=Beijing, O=Personal
我们可以看到Subject值为httpd,Issuer值为myca。因此,我们使用了我们自己的 CA 来签署我们的证书。让我们将此证书复制到certs目录,并将其重命名为httpd.crt:
$ cpdemoCA/newcerts/01.pem httpd.crt
关于 httpd 证书的内容就到这里。现在,我们来处理 EAP6 服务器,为它创建一个证书,并且使用 myca 进行签名。
为 EAP6 创建证书
EAP6 服务器中的过程有所不同;首先,我们需要使用keytool命令创建一个带有自签名证书的密钥库。然后,我们将从密钥库中导出证书,并使用 myca 进行签名。之后,我们将myca.crt导入密钥库,作为信任权威。因此,所有由 myca 签名的证书都将被 JVM 信任和接受。之后,我们将已签名的 EAP6 证书重新导入密钥库。
首先,让我们创建一个密钥库。密钥库文件eap6.ks将包含默认密钥对和一个与密钥对相关的自签名证书。以下是创建密钥库的命令:
$ keytool -genkey -keystoreeap6.ks -storepass packt000 -keypass packt000 -keyalg RSA -validity 365 -alias eap6cert -dname "cn=eap6,o=Personal,c=CN,ST=Beijing,L=Beijing"
从前面的命令中,我们可以看到密钥库的名称为eap6.ks,证书的别名为eap6cert。证书的cn字段值为eap6,并且该证书默认为自签名。我们可以使用keytool命令来检查这一点:
$ keytool -list -v -keystore eap6.ks -storepass packt000 -alias eap6cert
Alias name: eap6cert
...
Owner: CN=eap6, O=Personal, C=CN, ST=Beijing, L=Beijing
Issuer: CN=eap6, O=Personal, C=CN, ST=Beijing, L=Beijing
...
现在,让我们使用以下命令导出用于签名的eap6cert:
$ keytool -certreq -keyalg RSA -alias eap6cert -file eap6cert.csr -keystoreeap6.ks -storepass packt000
使用前面的命令,我们得到了 证书签名请求(CSR)文件,eap6cert.csr。我们需要用 myca 签署此证书。过程与签署 httpd.csr 文件时完全相同。我们需要将 eap6cert.csr 放入 certs 目录,并且需要重新使用为签署 httpd 证书而创建的 demoCA 目录。请注意,我们不需要在 ./demoCA/serial 中重置序列号,也不要从 newcerts 目录中删除 httpd 证书 01.pem。OpenSSL 将自动增加序列号,签署的 EAP6 证书将被命名为 02.pem。以下代码片段表示签署过程:
$ opensslca -in eap6cert.csr -keyfilemyca.key -cert myca.crt
Using configuration from /etc/pki/tls/openssl.cnf
Enter pass phrase for myca.key: packt000
Check that the request matches the signature
Signature ok
Certificate Details:
...
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Certificate:
...
Data Base Updated
通过这个过程,EAP6 证书被签署并存储在 demoCA/serial/02.pem 中。现在,让我们将 02.pem 复制到 eap6raw.crt:
$ cp demoCA/newcerts/02.pem eap6raw.crt
我们可以按照以下方式检查此证书的内容:
Issuer: C=CN, ST=Beijing, L=Beijing, O=Personal, CN=myca
Subject: CN=eap6, C=CN, ST=Beijing, O=Personal
从 Issuer 和 Subject 字段,我们可以看到证书已被签署。此外,我们可以在文件底部看到编码的证书数据。让我们使用以下命令从 eap6raw.crt 中提取编码的证书文本:
$ grep -A 50 "BEGIN CERTIFICATE" eap6raw.crt
-----BEGIN CERTIFICATE-----
MIIChTCCAe6gAwIBAgIBAjANBgkqhk...
...
bzCk0wKoQRWOZ5lCXUfN9OEOnVbYcBXTAQ==
-----END CERTIFICATE-----:
Java 安全库只能读取之前编码格式的证书,并且不允许证书文件中有任何额外的文本。因此,我们需要使用以下命令将编码的证书数据提取到另一个文件中:
$ grep -A 50 "BEGIN CERTIFICATE" eap6raw.crt >eap6cert.crt
使用前面的命令,我们已经将编码的证书数据提取到 eap6cert.crt;只有在那时,keytool 命令才能正确读取它。以下是要使用的命令:
$ keytool -printcert -file eap6cert.crt | head -n 2
Owner: O=Personal, ST=Beijing, C=CN, CN=eap6
Issuer: CN=myca, O=Personal, L=Beijing, ST=Beijing, C=CN
现在,我们需要将此已签署的证书重新导入到我们的 keystore 中;keytool 命令将帮助我们使用此已签署的证书更新 keystore 中的自签名证书。在这样做之前,我们需要将 myca 导入到 keystore 中。由于我们的 EAP6 证书是由 myca 签署的,并且我们的 keystore 当前不包含 myca,它将拒绝任何由它签署的证书。以下命令将 myca 导入到 keystore:
$ keytool -import -v -trustcacerts -alias myca -file myca.crt -keystoreeap6.ks -storepass packt000
...
Trust this certificate? [no]: yes
Certificate was added to keystore
[Storing eap6.ks]
请注意前面命令中的 -trustcacerts 选项。我们使用此选项将 myca 标记为受信任的签名机构。现在我们可以导入 eap6cert.crt 文件,使其被 keystore 接受;我们使用以下命令来完成此操作:
$ keytool -import -v -alias eap6cert -file eap6cert.crt -keystoreeap6.ks -storepass packt000
Certificate reply was installed in keystore
[Storing EAP6.ks]
让我们看看 keystore 中的证书:
$ keytool -list -keystore eap6.ks -storepass packt000
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
eap6cert, Dec 2, 2013, PrivateKeyEntry,
Certificate fingerprint (MD5): …
myca, Dec 2, 2013, trustedCertEntry,
Certificate fingerprint (MD5): …
从前面的命令输出中,我们可以看到 keystore 中有两个条目。这两个条目有不同的类型:eap6cert 是 PrivateKeyEntry,这意味着它是一张可以用于身份验证的证书,而 myca 是 trustedCertEntry,这意味着它是一个权威机构,并且它签署的所有其他证书都将被信任。
我们已经正确地准备好了所有证书。接下来,我们将配置 httpd 和 EAP6,以便我们可以正确地使用这些证书。
注意
请检查本章提供的代码。在 certs 目录中,你可以看到我们在此部分生成的所有示例证书。
配置 httpd
为了在管理通道中启用安全通信,我们需要向与周围<VirtualHost>相关的虚拟主机添加几个 SSL 指令,如下面的配置所示:
<VirtualHost 10.0.1.32:6666>
SSLEngine on
SSLCertificateFile /packt/httpd/certs/httpd.crt
SSLCertificateKeyFile /packt/httpd/certs/httpd.key
SSLCertificateChainFile /packt/httpd/certs/myca.crt
<Directory />
Order deny,allow
Deny from all
Allow from 10.0.1
</Directory>
ServerAdvertise on https://10.0.1.32:6666
EnableMCPMReceive
ManagerBalancerNamepacktlb
</VirtualHost>
httpd.crt文件由 httpd 用于标识自身,并将发送到 EAP6 进行认证。myca.crt文件将用于认证 EAP6 发送给 httpd 的证书。正如我们所知,EAP6 的证书由myca签名;因此,httpd 将信任它。
这就是我们为 httpd 配置需要做的所有事情。
注意
请检查本章代码中的mod-cluster-ssl-mcmp.conf配置。
现在,让我们配置 EAP6 部分。
配置 EAP6
在 EAP6 中,我们需要将我们的证书和 truststore 放置在domain.xml的mod_cluster子系统的配置文件中。以下是其内容:
<subsystem >
<mod-cluster-config ...>
...
<ssl key-alias="eap6cert" password="packt000" certificate-key-file="/packt/certs/eap6.ks" ca-certificate-file="/packt/certs/eap6.ks"/>
</mod-cluster-config>
</subsystem>
由于服务器证书eap6cert和 CA 证书myca都在密钥库eap6.ks中,我们将分别使用它们作为certificate-key-file和ca-certificate-file。EAP6 将向 httpd 展示eap6cert证书,并且由于它由 myca 签名,httpd 将信任它。
注意
请检查本章代码中的domain-ssl-mcmp.xml文件。
最后,我们还有一个非常重要的步骤:我们需要在主服务器和从服务器上复制eap6.ks密钥库,并将其放置在/packt/certs/eap6.ks的位置,就像我们在前面的代码片段中所做的那样。由于所有工作节点都使用来自domain.xml的配置,所有 EAP6 服务器都需要这个密钥库文件。
最后,所有配置都已完成。现在让我们测试集群。
测试配置
为了测试我们的配置,让我们启动 httpd 和两个 EAP6 服务器。然后我们可以使用cURL访问我们的集群:
$ curl–k https://172.16.123.1/cluster-demo1/index.jsp
<html>
<body>
<h2>Hello World!</h2>
Hello! The time is now Mon Oct 14 01:52:56 CST 2013
</body>
</html>
上述代码片段显示负载均衡器正在工作。如果我们使用 Wireshark 监控管理通道,我们可以看到 SSL 协议正在运行:

这验证了管理通道的安全性。如果我们检查代理通道中的数据传输,我们可以看到它仍在使用明文 AJP13 协议:

在以下部分,我们将看到如何启用代理通道中的 SSL。
在代理通道中启用 SSL
在本节中,我们将配置 mod_cluster 以使用 HTTPS 而不是 AJP13 作为代理通道。
由于我们在前面的章节中已经准备好了必要的证书,因此为代理通道启用 HTTPS 不会是一项困难的任务。首先,让我们检查 httpd 服务器中的配置。我们需要向虚拟主机添加一个SSLProxyEngine On配置以启用公共访问:
<VirtualHost 172.16.123.1:443>
...
SSLProxyEngine On
...
</VirtualHost>
如我们从前面的配置中可以看到,它告诉 httpd 我们需要为代理通道使用 SSL 连接,这就是我们需要配置 httpd 的所有内容。
注意
样本配置文件是mod-cluster-ssl-full.conf。
现在我们需要配置 EAP6 服务器。在 domain.xml 中,我们需要将 mod_cluster 连接器从 ajp 更改为 https,如下代码片段所示:
<subsystem >
<!--<mod-cluster-config advertise-socket="mod_cluster" connector="ajp">-->
<mod-cluster-config advertise-socket="mod_cluster" advertise="true" sticky-session="true" sticky-session-remove="false" sticky-session-force="false" connector="https">
...
</mod-cluster-config>
</subsystem>
然后,我们需要关闭 ajp 连接器,并添加 https 连接器,如下所示:
<!--<connector name="ajp" protocol="AJP/1.3" scheme="http" socket-binding="ajp" enabled="true"/>-->
<connector name="https" protocol="HTTP/1.1" scheme="https" socket-binding="https" secure="true">
<ssl name="https" key-alias="eap6cert"
password="packt000"
certificate-key-file="/packt/certs/eap6.ks"
protocol="TLS"
verify-client="false"
certificate-file="/packt/certs/eap6.ks"
ca-certificate-file="/packt/certs/eap6.ks"/>
</connector>
由于我们正在强制执行 SSL 连接,我们需要关闭 ajp 连接器以防止其他人使用这个明文通信端口。
注意
样本配置文件是 domain-ssl-full.xml。
在重启 EAP6 服务器后,我们可以看到代理通道开始使用 HTTPS 进行通信。Wireshark 分析的截图如下:

摘要
在本章中,我们学习了使用 mod_cluster 与 SSL 的不同方法。我们通常只需要为公共访问加密传输层,并在 httpd 和 EAP6 之间使用明文通信,因为我们通过将工作节点放置在本地网络中来保护它们。请选择一个合适的解决方案以满足您的需求。在下一章中,我们将学习如何开发和部署分布式项目到集群中。
第八章. 开发分布式应用程序
在前面的章节中,我们一直专注于构建无状态集群。这意味着我们不需要为每个用户请求维护会话,负载均衡器可以自由选择一个工作节点来处理用户请求。
无状态集群更加灵活,并且可以很好地扩展,因此在我们构建集群时,它总是首选。本质上,HTTP 是一个无状态协议,因此它缺乏维护用户请求会话的能力。为了解决这个问题,Web 服务器通常会向用户的 Web 浏览器传递一个会话 ID 以维持长时间的对话。
例如,如果我们正在构建一个在线购物系统,我们必须为每个用户维护一个购物车。当用户检查他们的购物车时,购物车中商品的总价将被计算。所有这些数据都需要存储在服务器端或用户 Web 浏览器的 cookies 中,并且数据需要在多个页面之间保持,所以会话 ID 是引用这些用户数据的键。对于 JBoss EAP,会话 ID 被称为 JSESSIONID。
在集群环境中,情况变得更加复杂,因为这里有多台服务器而不是一台,所以需要复制它们的状态。例如,如果一个工作节点 A 正在处理一个用户的请求,那么购物车的数据可能就保存在工作节点 A 上。如果负载均衡器现在将用户请求重定向到工作节点 B,那么用户会发现他的/她的购物车变空了。即使 JSESSIONID 被传递给工作节点 B,与 JSESSIONID 相关的数据仍然存储在工作节点 A 上。所以用户的数据仍然会丢失。
有两种常用的方法来解决这个问题。第一种选择被称为粘性会话。这是一种直接解决问题的方法。这意味着负载均衡器将用户会话粘附到特定的一个工作节点上。例如,如果一个用户访问我们的网站,负载均衡器选择工作节点 A 来处理请求,那么这个工作节点将永远用于处理该用户后续的请求,直到他/她退出 Web 浏览器或会话结束。
这种解决方案易于应用,并且适合实践中的许多情况。然而,它只部分解决了问题,因为工作节点可能会失败,负载均衡器会希望将用户请求故障转移到另一个工作节点。在这种情况下,崩溃的工作节点上的所有会话仍然会丢失。
因此,这是避免上述问题的第二种解决方案,即工作节点之间相互复制会话数据会更好。这样,当一个工作节点崩溃时,它的会话可以从其他工作节点恢复。
在本章中,我们将学习如何配置 EAP6 服务器之间的会话复制,然后我们将看到如何配置 httpd 中的粘性会话。
Web 会话复制
当 EAP6 以域模式(或启用*-ha配置文件的单机模式)运行时,它将默认提供 Web 会话复制。会话复制由 Infinispan 子系统支持,会话容器在domain.xml(和standalone-*-ha.xml)中定义:
<cache-container name="web" aliases="standard-session-cache" default-cache="repl" module="org.jboss.as.clustering.web.infinispan">
...
</cache-container>
在本节中,我们将使用一个示例项目来演示 Web 会话复制的使用方法。该项目命名为clusterbench。它是由我的同事Radoslav Husar和Michal Babacek在 Red Hat 开发的。
注意
该项目位于github.com/clusterbench/clusterbench。
此项目为我们提供了一些优秀的演示代码。因此,我们将直接将其部署到我们的 EAP6 服务器上进行测试。
在演示项目中,有一个名为clusterbench-ee6-web的子模块。在这个模块中,我们可以看到如何在web.xml中启用会话。它使用一行配置来启用 Web 会话复制,如下面的截图所示:

在web.xml中启用distributable后,Web 会话将在 EAP6 服务器之间进行复制。这是一个 JavaEE 标准要求。由于 JBoss EAP6 符合 JavaEE 标准,因此它支持此功能。此 Web 项目还为我们提供了一个用于测试的 servlet:
@WebServlet(name = "HttpSessionServlet", urlPatterns = {"/session"})
public class HttpSessionServlet extends CommonHttpSessionServlet {
}
前面的类HttpSessionServlet扩展了CommonHttpSessionServlet。CommonHttpSessionServlet在clusterbench-common中定义。以下是CommonHttpSessionServlet的摘要:
public class CommonHttpSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequestreq,
HttpServletResponseresp)
throws ServletException, IOException {
HttpSession session = req.getSession(true);
if (session.isNew()) {
log.log(Level.INFO, "New session created: {0}",
session.getId());
session.setAttribute(KEY, new SerialBean());
}
SerialBean bean = (SerialBean) session.getAttribute(KEY);
int serial = bean.getSerial();
bean.setSerial(serial + 1);
// Now store bean in the session
session.setAttribute(KEY, bean);
System.out.println("***serial: " + serial);
resp.getWriter().print(serial);
}
}
此 servlet 的主要目的是将计数器放入 Web 会话中,每次用户发送请求时,计数器将增加 1。请注意,我在前面的类中添加了一行代码:
System.out.println("***serial: " + serial);
因此,我们可以在稍后查看服务器控制台输出。现在我们可以将此项目部署到我们的集群中,然后我们可以访问 servlet 来使用计数器。我们可以查看服务器输出以确定哪个节点正在实际处理此请求。然后关闭工作节点并再次访问集群。我们应该期望另一个 EAP6 服务器来处理请求。如果会话复制成功,我们应该看到计数器没有重置,而是继续增加。总之,这是一个演示 EAP6 服务器之间会话复制的示例。
为了进行测试,我们可以使用上一章中设置的集群,使用JK或mod_cluster作为负载均衡器,然后将项目clusterbench-ee6.ear部署到 EAP6 域中。
在完成前面的准备工作并且负载均衡器和 EAP6 服务器都运行后,让我们第一次通过cURL访问集群:
$ curl -cmysession.txt http://172.16.123.1/clusterbench/session
0
我们可以看到计数器值被设置为0。-cmysession.txt选项告诉 cURL 将会话 cookie 存储在名为mysession.txt的文件中。我们稍后会检查这个文件。现在我们可以检查服务器端。从 EAP6 服务器控制台输出中,我们可以看到主服务器正在处理用户请求:
[Server:master-server] 20:10:57,810 INFO [org.jboss.test.clusterbench.common.session.CommonHttpSessionServlet] (ajp-/10.0.1.13:8259-1) New session created: 5LQpRPxdSCupM5eHYd93S2wR
[Server:master-server] 20:10:57,813 INFO [stdout] (ajp-/10.0.1.13:8259-1) ***serial: 0
在主服务器的先前控制台输出中,我们可以看到为计数器创建了一个新的会话,会话 ID 为5LQpRPxdSCupM5eHYd93S2wR。此外,我们看到计数器被初始化为0,这与客户端的结果相匹配。
现在让我们回到客户端并检查mysession.txt文件。以下是文件内容:
$ cat mysession.txt
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This file was generated by libcurl! Edit at your own risk.
172.16.123.1 FALSE /clusterbench FALSE 0 JSESSIONID 5LQpRPxdSCupM5eHYd93S2wR
我们可以看到 JSESSIONID 存储在 cookie 中。现在让我们使用这个 cookie 文件再次访问集群:
$ curl -bmysession.txt http://172.16.123.1/clusterbench/session
1
-b选项将让 cURL 读取一个现有的 cookie 文件并将 cookie 发送到服务器,这意味着之前的会话将继续。因为计数器增加 1,这意味着我们的会话由 JSESSIONID 保持。我们可以再次检查 EAP6 服务器的输出:
[Server:master-server] 20:26:12,496 INFO [stdout] (ajp-/10.0.1.13:8259-1) ***serial: 1
因此,主服务器上的计数器没有重置,它在一个会话中持续增加。现在让我们关闭主服务器:
20:31:04,851 INFO [org.jboss.as.process] (Shutdown thread) JBAS012015: All processes finished; exiting
然后我们再次访问集群:
$ curl -bmysession.txt http://172.16.123.1/clusterbench/session
2
因为主服务器已关闭,这次是从服务器在处理请求:
[Server:slave-server] 21:07:46,266 INFO [stdout] (ajp-/10.0.1.40:8259-2) ***serial: 2
尽管请求被重定向到从服务器,但会话保持不变,计数器从 1 增加到 2。这验证了两个服务器之间会话复制工作正常。
CDI 会话作用域 bean 复制
CDI 会话作用域 bean 的使用与 web 会话 bean 类似。在演示项目中,它提供了一个CdiServlet用于测试:
@WebServlet(name = "CdiServlet", urlPatterns = {"/cdi"})
public class CdiServlet extends HttpServlet {
@Inject
privateSessionScopedCdiSerialBean bean;
@Override
protected void doGet(HttpServletRequestreq,
HttpServletResponseresp)
throws ServletException, IOException {
resp.setContentType("text/plain");
int serial = bean.getSerial();
bean.setSerial(serial + 1);
System.out.println("***bean: " + serial);
resp.getWriter().print(serial);
}
}
这个 servlet 也是一个计数器,它使用一个名为SessionScopedCdiSerialBean的会话作用域 CDI bean。以下是这个 bean 的定义:
@SessionScoped
public class SessionScopedCdiSerialBean extends SerialBean implements Serializable
该 bean 被声明为SessionScoped,因此它将在集群中复制。SerialBean是一个包含计数器的 POJO。现在我们可以在我们的集群中测试它。首先我们需要访问 servlet:
$ curl -cmysession.txt http://172.16.123.1/clusterbench/cdi
0
然后我们需要检查哪个 EAP 服务器正在处理用户请求。在我的环境中,主服务器正在处理请求:
[Server:master-server] 21:28:58,440 INFO [stdout] (ajp-/10.0.1.13:8259-4) ***bean: 0
在mysession.txt中,我们可以看到 JESSIONID 被存储:
172.16.123.1 FALSE /clusterbench FALSE 0 JSESSIONID +3EWwwlqUniCic9mtm6c18w2
现在我通过关闭主服务器来断开连接,并再次使用会话 cookie 访问集群:
$ curl -bmysession.txt http://172.16.123.1/clusterbench/cdi
1
现在我们可以看到从服务器正在处理请求:
[Server:slave-server] 21:29:58,032 INFO [stdout] (ajp-/10.0.1.13:8259-1) ***bean: 1
如前述代码片段所示,我们可以看到会话已从主服务器复制到从服务器。
使用 JK 配置粘性会话
在前面的章节中,我们探讨了如何在 EAP6 中配置和使用会话复制。在本节中,让我们转向负载均衡器一侧,看看我们如何配置一个粘性会话。启用粘性会话后,负载均衡器将使用一个工作节点来处理来自一个用户的全部请求。让我们从 JK 配置开始。JK 会自动启用粘性会话。我们可以在其管理控制台中查看这一点,如下面的截图所示:

从前面的图中,我们可以看到 粘性会话 选项默认是启用的。现在我们需要考虑负载均衡器如何实现会话粘性:如果有成千上万的用户请求进入一个集群,并且启用了粘性会话,那么每个用户的请求都会粘附到一个特定的工作节点。因此,负载均衡器需要某种方式来记录这种关系。
在负载均衡器中存储这种关系不是一个好主意。关系数据将随着用户数量的线性增长而增加。如果有多个负载均衡器,情况会变得更糟,粘性关系必须在负载均衡器之间复制。负载均衡器无法承担维护如此大量数据的工作,并且其性能将因查询粘性关系而受限。
为了解决这个问题,JK 和 mod_clusteruse 都提供了一个更简单的解决方案:它将在 JSESSIONID 中放置一个名为 jvmRoute 的服务器 ID。jvmRoute 的值是 UUID,因此它可以用来识别每个工作节点。随着 jvmRoute 成为会话 ID 的一部分,负载均衡器将直接从 JSESSIONID 中提取它,并知道这个会话绑定到哪个服务器。
要启用粘性会话,我们需要编辑 EAP6 的配置来设置此服务器 ID。我们应该做的是打开 domain.xml 并在 web 子系统中添加一个 instance-id 元素:
<subsystem ... instance-id="${jboss.server.name}">
...
</subsystem>
instance-id 元素是 jvmRoute 的值。我们使用了 ${jboss.server.name} 作为其值。这是一个由 EAP6 提供的变量,其值是 host.xml 中设置的服务器名称。因此,我们知道我们两个 EAP6 服务器的 instance-id 值分别是 master-server 和 slave-server。
要在 EAP6 中反映配置,我们需要将这些名称放入 httpd 侧的 worker.properties 中,这样 JK 就会知道其工作者的名称。以下是 worker.properties 的完整内容:
worker.list=lb,jk-status
worker.master-server.type=ajp13
worker.master-server.host=10.0.1.13
worker.master-server.port=8259
worker.slave-server.type=ajp13
worker.slave-server.host=10.0.1.19
worker.slave-server.port=8259
worker.lb.type=lb
worker.lb.balance_workers=master-server,slave-server
worker.lb.sticky_session=1
worker.jk-status.type=status
注意
我们必须确保工作器名称与域控制器中的 instance-id 设置相匹配,这样 JK 才能找到会话粘附的正确服务器。
现在,我们可以测试之前章节中部署的 clusterbench 项目。我们仍然可以使用 cURL 命令访问集群:
curl -cmysession.txt http://172.16.123.1/clusterbench/session
从 mysession.txt 中,JSESSIONID 是:
JSESSIONID 8az1BX6Q+TQI+P4wids6BPMV.master-server
我们现在可以看到会话被分成两部分,由点分隔。第一部分仍然是会话 ID,第二部分是会话中携带的 jvmRoute,其值为 master-server。在服务器输出中,你还可以注意到会话已经创建,会话 ID 显示在 stdout 上:
[Server:master-server] 22:34:42,700 INFO [org.jboss.test.clusterbench.common.session.CommonHttpSessionServlet] (ajp-/10.0.1.13:8259-1) New session created: 8az1BX6Q+TQI+P4wids6BPMV.master-server
[Server:master-server] 22:34:42,701 INFO [stdout] (ajp-/10.0.1.13:8259-1) ***serial: 0
根据 jvmRoute 中的信息,负载均衡器将粘附以下用户请求到 mast er-server。
使用 mod_cluster 配置粘性会话
要在mod_cluster中启用粘性会话,我们需要在 EAP6 的mod_cluster子系统中添加一些配置。对于独立模式,我们可以配置包含mod_cluster子系统的*-ha.xml配置文件;对于域模式,我们可以编辑域控制器的domain.xml。
默认情况下,mod_cluster子系统启用了粘性会话。同时,mod_cluster使用与 JK 相同的方案来处理会话粘性,因此我们还需要在 Web 子系统中添加instance-id配置:
<subsystem ... instance-id="${jboss.server.name}">
...
</subsystem>
这就是我们需要的所有配置。我们不需要在 httpd 端进行任何配置,因为mod_cluster将动态发现工作节点。现在我们可以启动我们的集群并检查mod_cluster的管理控制台:

从前面的截图,我们可以看到两个 EAP6 服务器名称变为master-server和slave-server,这意味着instance-id的设置已被启用。现在我们访问我们的集群:
curl -cmysession.txt http://172.16.123.1/clusterbench/session
然后我们检查mysession.txt的内容:
JSESSIONID AcJIPZwHmlauwxi82s45VWWw.master-server
现在,我们可以看到 JSESSIONID 携带了jvmRoute信息。因此,httpd 将从用户发送以下请求到主服务器。
摘要
在本章中,我们讨论了两种处理集群中状态化应用的解决方案。一个是粘性会话,另一个是会话复制。这两个解决方案通常一起使用,以在状态化集群中提供高可用性。
当我们构建集群时,我们首先应该考虑构建无状态集群,因为无状态集群非常容易扩展,并且在会话复制上没有性能瓶颈。


浙公网安备 33010602011771号