将 Apache Web 服务器连接到 Tomcat 的多个实例

作者:Daniel McCarthy

最近,我被要求重新组织我们的一些 Web 应用程序,以提高它们的稳定性。主要推动力是让我们的每个应用程序都在其自身的 Tomcat 实例中运行。这些应用程序都处于不同的开发阶段,如果单个实例分配了所有可用的内存,则必须重启整个 Tomcat 服务器。反过来,这会使所有其他应用程序都停止运行。

运行多个实例的一个障碍是每个实例都必须在唯一的端口上运行。这时就轮到 Apache 的 Web 服务器出场了。通过使用 mod_jk,我们能够将对特定主机和上下文的请求转发到相应的 Tomcat 运行实例。

将所有这些整合在一起并不是我做过的最难的事情;然而,由于缺乏文档,它确实证明具有挑战性。然而,这句话绝不是对任何开发团队的抨击。事实上,当我发现自己力不从心时,开发人员本身在很多场合都帮助了我。当文档让我感到困惑,或者我找不到我正在寻找的确切内容时,我会前往 irc.freenode.org 上的 #tomcat 或 #apache,并在那里提出我的问题。通常,我会在几分钟内收到回复。

以下软件用于本文的目的。有关下载位置,请参阅本文末尾的资源部分

  • J2sdk1.4.2_09

  • Tomcat 5.0.28

  • Apache 2.0.54

  • mod_jk 1.2.14

我确信你们中的一些人想知道为什么我使用 Java 软件开发工具包 (SDK) 而不是 Java 运行时环境 (JRE)。答案很简单:Tomcat 需要 tools.jar 来编译 JSP 页面,而 tools.jar 在 SDK 中提供。如果您不想使用 SDK,则需要将 tools.jar 放置在 $CATALINA_HOME\common\lib 中。

假设

为了本文的目的,我假设您已经安装了 Apache、Java 和 Tomcat。在安装了上面列出的所有软件(mod_jk 除外)的情况下,让我们设置我们的环境。

为了使 Tomcat 启动,您需要设置两个环境变量 JAVA_HOME 和 CATALINA_HOME。JAVA_HOME 应该指向 J2sdk 安装目录,而 CATALINA_HOME 应该指向 Tomcat 的安装目录。为了让生活更轻松,我将以下行放在 /etc/bashrc 中

export JAVA_HOME=/usr/java/j2sdk1.4.2_09
export CATALINA_HOME=/opt/tomcat 

请注意,我已经将 CATALINA_HOME 设置为 /opt/tomcat。虽然是这种情况,但 /opt/tomcat 实际上是指向 /opt/jakarta-tomcat-5.0.28 的符号链接。虽然您可以通过运行以下命令来判断您安装的 Tomcat 版本$CATALINA_HOME/bin/catalina.sh version,但我更喜欢通过查看安装目录来立即注意到版本。

在我们深入编译 mod_jk 之前,我想消除我在从事这个项目时遇到的一些困惑。具体来说,困惑的中心是“越大越好”的神话。在 Tomcat 连接器的世界中,存在 mod_jk 和 mod_jk2。在我看来,mod_jk2 是合乎逻辑的选择,因为它具有更高的版本号。然而,经过一番探究和询问,我发现 mod_jk2 已被弃用——它不再被开发。对我来说不幸的是,当我发现这个事实时,我已经投入了几个小时让 mod_jk2 工作。希望通过阅读本文,您将节省一些时间。

编译、安装和配置 mod_jk

打开终端并将目录更改为您保存 mod_jk 源的位置。有关下载链接,请参阅本文末尾的资源部分。在终端窗口中,解压源存档并使用以下四行命令安装

tar -xzvf jakarta-tomcat-connectors-1.2.14.1-src.tar.gz
cd jakarta-tomcat-connectors-1.2.14.1/jk/native
./configure --with-axps=/usr/sbin/axps 
make && make install

--with-axps=/usr/sbin/apxs 配置选项允许我们构建 Apache 模块,而无需 Apache 源代码。如果一切顺利,您应该在接近结尾处看到一条消息,告知您库的位置。在我的发行版上,它是 /usr/lib/httpd/modules。

安装 mod_jk 后,我们现在必须配置 Apache 以加载该模块,方法是编辑 httpd.conf。在我的系统上,httpd.conf 位于 /etc/httpd/conf 中。配置 Apache 加载 mod_jk 是一个简单的两行步骤

#Load the mod_jk connector LoadModule jk_module
/usr/lib/httpd/modules/mod_jk.so

我最初犯的一个错误是将模块命名为 mod_jk,就像在LoadModule mod_jk /usr/lib/httpd/modules/mod_jk.so中一样。上面的命令肯定会让 Apache 抱怨并拒绝启动。话虽如此,现在启动或重启 Apache 以验证它是否可以加载新编译的模块是一个好主意。如果 Apache 启动没有问题,则可以安全地继续。

设置多个 Tomcat 实例

可以使用 CATALINA_BASE 环境变量创建多个 Tomcat 实例。每个实例都使用通用的二进制发行版,但使用其自己的 conf、webapps、temp、logs 和 work 目录。每个实例也有其自己的 JVM,因此也有其自己的内存池。如果您已通过 JAVA_OPTS 将最大内存定义为 512MB,则每个实例将尝试分配最大 512MB 的内存。

现在让我们继续设置这些目录。正如我之前提到的,Tomcat 安装在 /opt/tomcat 中。为了保持一定的组织性,我在 /opt 中创建了以下文件夹:/opt/tomcat_instance1、/opt/tomcat_instance2 和 /opt/tomcat_instance3。然而,根据它们的用途或应用程序来命名这些文件夹可能更合适。请记住,这三个文件夹中的每一个都将包含 conf、webapps、temp 和 work 目录。

配置第一个实例

Tomcat 使用 server.xml 配置文件来确定端口、连接器引擎和各种其他“服务器”配置选项。我们将安装的 server.xml 从 $CATALINA_HOME/conf/server.xml 复制到 /opt/tomcat_instance1/conf/server.xml。当我们在这样做时,我们不妨也将 $CATALINA_HOME/con/server.xml 复制到 /opt/tomcat_instance2/conf/server.xml 和 /opt/tomcat_instance3conf/server.xml。

Tomcat 还使用全局 web.xml 文件。所谓全局,我的意思是它用于每个实例。web.xml 文件为在给定实例下运行的每个 Web 应用程序提供默认配置。如果某个选项未在单个 Web 应用程序中定义,则使用默认的 web.xml 选项。我们可以将 $CATALINA_HOME/conf/web.xml 复制到 /opt/tomcat_instance1/conf、/opt/tomcat_instance2/conf 和 /opt/tomcat_instance3/conf。

现在我们必须对 server.xml 进行一些编辑。首先,我们需要禁用 Coyote 连接器。为此,我们注释掉 Coyote 连接器信息。这是一个 XML 文件,因此它使用与 HTML 相同的注释语法。在我们完成注释掉连接器后,它应该看起来像这样

 <!-- 
  <Connector port="8080"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               debug="0" connectionTimeout="20000"
               disableUploadTimeout="true" />
 -->

因为这是第一个运行实例,所以我们不需要修改此文件的更多内容。对于后续实例,我们需要更改关闭端口和 AJP 连接器端口。AJP 连接器端口是 Apache 用于转发请求的端口。

接下来,将 Tomcat 安装提供的 servlets-examples 文件从 $CATALINA_HOME/webapps/servlets-examples 复制到 /opt/tomcat_instance1/webapps/servlets-examples。同样,将示例应用程序复制到 /opt/tomcat_instance2/webapps 和 /opt/tomcat_instance3/webapps。至此,tomcat_instance1 的设置完成。我们现在需要设置第二个和第三个实例,然后将它们整合在一起。

配置第二个实例

我们已经从安装目录复制了 server.xml。我们现在需要对 /opt/tomcat_instance2/conf/server.xml 进行与第一个实例相同的编辑。也就是说,完全按照我们上面所做的那样注释掉 Coyote 连接器。

其他必需的编辑是将 SHUTDOWN 端口从 8005 更改为 8105。我们必须将端口从 8005 更改,因为第一个实例已经在使用它。您可以将第二个实例的端口更改为 1024 以上的任何未使用端口,但为了简单和组织起见,让我们使用 8105。以下是文件中应有的行

<Server port="8105" shutdown="SHUTDOWN" debug="0">

现在我们必须将 AJP 连接器从 8009 更改为 8109。同样,这是必需的,因为第一个实例已经在使用 8009。以下是要更改的行以及所需的编辑

    <Connector port="8109"
	enableLookups="false" redirectPort="8443" debug="0"
      protocol="AJP/1.3" />

如果您正在使用 SSL,您还应该将 redirectPort 更改为 Apache 正在侦听的适当 SSL 端口,通常为 443。

配置第三个实例

我们需要对 /opt/tomcat_instance3/conf/server.xml 进行与第二个实例相同的编辑,除了我们将 8205 替换为 8005,将 8209 替换为 8009。如果这没有意义,以下是 server.xml 的相应部分

<Server port="8205" shutdown="SHUTDOWN" debug="0">

    <Connector port="8209"
	enableLookups="false" redirectPort="8443" debug="0"
      protocol="AJP/1.3" />

配置 mod_jk

mod_jk 使用名为 workers.properties 的文件。我建议将此文件与您的其他 Apache 配置文件放在一起。workers.properties 用于定义 Apache 在哪里查找 Tomcat 实例。在这里,我仅介绍我们将用于我们已设置的三个实例的项目。以下是我们将要使用的 workers.properties 文件,以及选项的说明

worker.list=worker1,worker2,worker3
# Set properties for worker1
worker.worker1.type=ajp13
worker.worker1.host=localhost
worker.worker1.port=8009
# Set properties for worker2
worker.worker2.type=ajp13
worker.worker2.host=localhost
worker.worker2.port=8109
# Set properties for worker3
worker.worker3.type=ajp13
worker.worker3.host=localhost
worker.worker3.port=8209

worker.list 是以逗号分隔的工作进程名称列表。您可能在文件中稍后定义了将不会使用的 Tomcat 工作进程。除非工作进程在 worker.list 值中列出,否则任何定义的工作进程都不会被使用。工作进程以以下格式定义worker.工作进程名称.type,其值为连接器的类型。我们所有的工作进程都是 ajp13 类型。在上面的示例中,我们定义了三个工作进程:worker1、worker2 和 worker3。

您可能已经注意到配置的主机部分。这可以用于配置 Apache 以转发到单独机器上的 Tomcat 实例。事实上,这是一个可以选择采用的选项,以使站点更安全。因为 Tomcat 和 Apache 都驻留在同一台机器上,所以我们使用 localhost。

每个工作进程还需要定义连接器配置为工作的端口。如果您还记得,之前我们将 instance1 配置为侦听端口 8009,instance2 配置为侦听端口 8109,instance3 配置为侦听端口 8209。

使用 mod_jk 配置 Apache

为了启动所有这些,我们需要告诉 Apache 在哪里找到 workers.properties 文件,以及在哪里记录 mod_jk 请求。我们还需要指定日志文件的格式以及特定于 mod_jk 的选项。我通过将以下行添加到 httpd.conf 来完成此操作;我将所有 mod_jk 配置指令都放在虚拟主机声明之前

JkWorkersFile "/etc/httpd/conf/workers.properties"
JkLogFile     "/var/logs/www/mod_jk.log"
JkLogLevel  info
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkOptions     +ForwardKeySize +ForwardURICompat -ForwardDirectories
JkRequestLogFormat     "%w %V %T"

上面的选项告诉 Apache 使用 /etc/httpd/conf/workers.properties 进行工作进程定义,并使用 /var/logs/www/mod_jk.log 日志文件。如果您在使用 mod_jk 时遇到问题,请将 JkLogLevel 调整为“debug”,以便获得更详细的消息。JKLogStampFormat 和 JkRequestLogFormat 定义日志记录格式。选项 ForwardKeySize 指示 mod_jk 随请求一起转发 SSL 密钥大小。ForwardURICompat 指示 mod_jk 正常转发 URL 到 Tomcat。-FowardDirectories 指示 mod_jk 不要从 Tomcat 返回目录列表。

配置 Apache 以进行转发

我们使用 domain1、domain2 和 domain3 作为我们的虚拟主机。为了实现这一点,我们还必须编辑 /etc/hosts 以确保 domain1、domain2 和 domain3 得到正确解析。为此,我们创建三个 VirtualDirectory 声明,每个声明对应于 workers.properties 中定义的工作进程。以下是 VirtualHosts 部分,以及选项的说明。

<VirtualHost *:80>
    ServerName domain1
    JkMount  /servlets-examples/* worker1
</VirtualHost>
<VirtualHost *:80>
    ServerName domain2
    JkMount  /servlets-examples/* worker2
</VirtualHost>
<VirtualHost *:80>
    ServerName domain3
    JkMount  /servlets-examples/* worker3
</VirtualHost>

配置实例在启动时启动

既然我们已经完成了大部分配置,现在是时候设置实例在启动时启动了。这部分是通过创建一个 bash 脚本并将其放置在 /etc/init.d 中来完成的。我使用了此处找到的启动脚本。

#!/bin/bash
#
# tomcat        
#
# chkconfig: 
# description:  Start up the Tomcat servlet engine.

# Source function library.
. /etc/init.d/functions


RETVAL=$?
export CATALINA_BASE="/opt/tomcat_instance1"
export CATALINA_HOME="/opt/tomcat"

case "$1" in
 start)
        if [ -f $CATALINA_HOME/bin/startup.sh ];
          then
            echo $"Starting Tomcat"
            /bin/su tomcat $CATALINA_HOME/bin/startup.sh
        fi
        ;;
 stop)
        if [ -f $CATALINA_HOME/bin/shutdown.sh ];
          then
            echo $"Stopping Tomcat"
            /bin/su tomcat $CATALINA_HOME/bin/shutdown.sh
        fi
        ;;
 *)
        echo $"Usage: $0 {start|stop}"
        exit 1
        ;;
esac
exit $RETVAL

复制上面的内容,并将它们放在 /etc/init.d/tomcat_instance1、/etc/init.dtomcat_instance2 和 /etc/init.d/tomcat_instance3 中。请务必将 CATALINA_BASE 更改为每个脚本的相应目录。现在,我们需要在 /etc/rc5.d 中链接到这些脚本。要创建符号链接,请以 root 身份发出以下命令

cd /etc/rc5.d
ln -s /etc/init.d/tomcat_instance1  S71tomcat_service1
ln -s /etc/init.d/tomcat_instance2 S71tomcat_service2
ln -s /etc/init.d/tomcat_instance3 S71tomcat_service3

测试设置

完成所有配置后,现在是时候测试我们的设置了。我们首先启动 Apache,如果它已经在运行,则重启它。接下来,启动第一个实例

/etc/init.d/httpd stop
/etc/init.d/httpd start
/etc/init.d/tomcat_service1 start

现在,打开浏览器窗口并转到 http://domain1/servlets-examples/。如果一切顺利,您应该看到类似于图 1 的内容。

Connecting Apache's Web Server to Multiple Instances of Tomcat

图 1. 检查您的设置

如果您没有看到类似于上面的页面,请查看日志文件。具体来说,检查 /var/logs/httpd/mod_jk.log 和 /opt/tomcat_instance1/logs/catalina.out 中是否有任何可能发生的错误。

如果一切看起来都正确,请继续启动其余两个上下文

/etc/init.d/tomcat_instance2 start
/etc/init.d/tomcat_instance3 start

将您的浏览器指向 http://domain2/servlets-examples 和 http://domain3/servlets-examples。您应该看到所有三个实例的页面完全相同。

为了使事情更清楚一点,并验证我们是否正在访问正确的实例,我们可以修改 /opt/tomcat_instance2/webapps/servlets-examples/index.html 和 /opt/tomcat_instance3/webapps/servlets-examples/index.html 中的一行。这两个文件中要修改的行是

<b><font face="Arial, Helvetica, sans-serif"><font size=+2>Servlet
Examples with Code</font></font></b>

对于第二个实例,我们希望该行读取如下

<b><font face="Arial, Helvetica, sans-serif"><font size=+2>Domain 2 Servlet
Examples with Code</font></font></b>

对第三个实例进行类似的修改

<b><font face="Arial, Helvetica, sans-serif"><font size=+2>Domain 3 Servlet
Examples with Code</font></font></b>

如果您现在将浏览器指向 http://domain2/servlets-examples 和 http://domain3/servlets-examples,您应该在相应页面的开头看到 Domain 2 和 Domain 3。

结论

希望本 HOWTO 能够让您逐步了解在 Apache 作为前端的情况下,运行 Tomcat 的多个实例需要哪些步骤。还有许多其他选项可用于生成类似的设置。例如,您可以将 Apache 用作前端,并在运行 Tomcat 的多个服务器之间进行负载均衡。有关可用选项的更多信息,请参阅 mod_jk 的文档。

加载 Disqus 评论