Tomcat - 按特定顺序启动Web应用程序

19

我知道Tomcat和Servlet规范不支持以特定的顺序启动Web应用程序

然而,这似乎是一个常见的用例,我想知道是否有人发现了一个聪明的解决方法。

我有一个使用Spring Remoting公开共享服务的Web应用程序A,其中Web应用程序B是客户端。只有当Web应用程序A正在运行时,Web应用程序B才能初始化。然而,我的Tomcat总是按线性顺序启动Web应用程序,从Web应用程序B开始。

由于基础设施原因,我必须在同一台Tomcat服务器上运行它们。

有什么想法吗?

谢谢, 罗伊

更新-

事实证明,在我个人的情况下,顺序并不重要。原因是:假设我使用下面的其中一种方法在启动应用程序B之前启动应用程序A。所以应用程序A会启动,但是,由于Spring remoting使用HTTP Invoker,HTTP端口尚未打开(它不会在所有应用程序都启动之前打开)。 因此,A将启动,而B将挂起,因为它正在寻找的端口尚不可用。糟糕。

最终结果是两个单独的Tomcat实例。


让需要等待另一个Web应用程序的Web应用程序等待,而不是失败并放弃。 - Dave Newton
1
嘿,戴夫,不幸的是它们不能并行启动。B开始(好吧,试图启动),然后A会在其后启动。但是,如果B无法启动因为无法连接到A,那么A就永远不会尝试启动。 - Roy Truelove
1
它应该按ASCII顺序启动它们。我在Debian Apache安装中看到的一个技巧是创建000_default,001_default命名约定。这使得查看目录列表的ASCII顺序时更加清晰。 - speeves
如果您的应用程序A依赖于另一个应用程序B,则应按照此处所述的方式实现Tomcat启动生命周期监听器,或者按照此处所述的方式实现Tomcat启动程序检测,并在接收到Tomcat AFTER_START_EVENT后完成应用程序初始化。 - wzona
9个回答

10

我们也遇到了同样的问题,为了解决这个问题,我们依赖于一个滑稽的事实:应用程序的启动顺序是按照在 <tomcat_home>/conf/server.xml 中定义的顺序启动的。

当然,这种方法的缺点是在 server.xml 中硬编码应用程序,但我们可以接受它。


谢谢mindas - 这可能是做这件事情的“最佳”方式,但不幸的是对我来说不起作用 - 我将编辑我的问题以解释原因。 - Roy Truelove

6

这里是Linux的另一个技巧。

我们的一些Web服务应用无法部署,因为WSDL文件出错。如果它们在许多其他应用程序之后被部署或启动,就会发生这种情况。它们启动的顺序取决于在 /opt/apache-tomee/conf/Catalina/localhost 目录中找到的上下文XML文件的顺序。

可以使用 "ls -1f" 来验证。普通的 "ls" 命令会给出排序后的输出。

在过去,这是按照将文件添加到该目录中的顺序来排列的,但是使用ext4文件系统后,顺序基于文件名的哈希值。可以通过以下方式禁用它:

# tune2fs -O ^dir_index /dev/xyz

现在你可以自己决定它们启动的顺序。重新排序:将所有文件移动到一个临时文件夹中,然后按照所需的顺序将它们移回来。


刚发现了这个技巧。感谢您的解释! - Iain Samuel McLean Elder
我不了解“脆弱性”。我以这种方式运行了托管50个应用程序的Tomcat,并且没有习惯于没有充分理由重新启动Tomcat。此外,我的意图并不是打败Tomcat本身的透明度。 - Gerard H. Pille

6

如果您不介意稍微修改一下Tomcat代码并创建自己的Host实例,那么这就非常容易实现。

1)创建org.apache.catalina.core.StandardHost的子类,比如MyHost:

    class MyHost extends org.apache.catalina.core.StandardHost{
        public MyHost (){
        super();
        //changing HashMap for a predictable ordered Map :)
        this.children = new LinkedHashMap();
        }
    } 

2) 在您服务器的xml Host标记()上注册您的类

难以置信,只要您将所有Web应用程序按正确顺序声明在Host标记内部,它就可以解决问题:

    <Host>
     <context app1>
     <context app2>
   </Host>

无论您使用哪个操作系统,Thaen应用程序1都会在应用程序2之前启动。


3
第一步完成后,我们应该把MyHost类放在哪里?可以请您详细说明一下吗? - AabinGunz
我们可以有主机配置的示例吗? - Maayan Hope

6

虽然这是一个旧帖子,但...

另一种解决方法是创建一个自定义的HostConfig类,以您需要的方式排序Web应用程序。

public class OrderedHostConfig extends HostConfig {

    @Override
    protected String[] filterAppPaths(String[] unfilteredAppPaths) {
        String[] files = super.filterAppPaths(unfilteredAppPaths);
        Arrays.sort(files, compare());
        return files;
    }

    private Comparator<String> compare() {
        return (o1, o2) -> {
        // Your own implementation 
        };
    }
}

然后,您可以在主机定义下的 server.xml 文件中引用此类。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" hostConfigClass="your.package.OrderedHostConfig">

需要编译成JAR文件并保存在Tomcat的/lib目录下。在我的情况下为:

/var/lib/tomcat8/lib

我喜欢这种方法,因为:

  1. 只有在相关的地方会强制执行顺序,但无需在主机定义中手动管理所有web应用程序。

  2. 在代码库中搜索.war文件名称也将引用此类,这使得如果文件被重命名,更容易找到。

  3. 对于tomcat 8不需要更改private final字段,请参见此处


这实际上是一个很好的解决方案。在Tomcat 8.5和9.x上进行了测试,效果很好。 - mvreijn
@mvreijn 你测试过了吗?我不确定这是否适用于9.x,至少对于Tomcat v9.0.19来说它不起作用。它也没有出现在官方的Tomcat API文档中。 - wzona
@wzona 是的,我已经按照所述进行了测试。然而,行为可能已经改变,或者只是巧合。 - mvreijn

2

由于Tomcat 9.0.19(包括@Luiz提到的那个版本)中都没有可行的选项,因此我们采用了代码方法,并用最小化的自定义实现替换了Tomcat StandardHost和HostConfig:

public class CustomTomcatHost extends StandardHost {

    public CustomTomcatHost() {
        super();
    }

    @Override
    public void addLifecycleListener(LifecycleListener listener) {
        if (listener instanceof HostConfig) {
            listener = new OrderedHostConfig();
        }
        super.addLifecycleListener(listener);
    }
}

deployApps 函数在 HostConfig 中必须被重写,以使排序适用于所有内容(包括 /webapps 文件夹中的 WAR 文件以及 configbase 文件夹中的描述符 XML 文件(例如 conf/Catalina/localhost)):

public class OrderedHostConfig extends HostConfig {

    public OrderedHostConfig() {
        super();
    }

    public String[] prioritySort(String[] paths) {
        if (paths == null) return null;

        Arrays.sort(paths, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.compareTo(b); //TODO: sort paths based on your criteria
            }
        });

        return paths;
    }

    @Override
    protected void deployApps() {

        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();

        String[] apps = prioritySort(filterAppPaths(appBase.list()));

        // Deploy XML descriptors from configBase
        deployDescriptors(configBase, prioritySort(configBase.list()));
        // Deploy WARs
        deployWARs(appBase, apps);
        // Deploy expanded folders
        deployDirectories(appBase, apps);
    }
}

然后我们将新类放置在Tomcat /lib目录下的一个新的jar文件中,并修改conf/server.xml文件以用我们自己的实现替换主机类:

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true" className="com.example.CustomTomcatHost" >

在启动Tomcat时,按照所需的顺序加载了所有组件。


1
理论上,您可以在contextInitialized()中通过ExecutorService生成一个Runnable,该Runnable反过来会定时检查其他Web应用程序的可用性(可能通过触发HTTP HEAD请求?)。一旦其他Web应用程序可用,就在servlet上下文中设置某些属性来指示。添加一个Filter,该Filter检查该属性的存在并相应地阻止/继续请求。

1

我知道这个问题有点旧了,但当我尝试做同样的事情时发现了它并想用更好的解决方案进行更新...

您可以在server.xml中定义多个服务,它们运行在不同的端口上。服务按照它们在server.xml中出现的顺序依次启动。这意味着您可以拥有例如配置服务在第一个服务中运行,然后依赖它的应用程序在第二个服务中运行(我使用默认的Catalina来运行其他应用程序...)

您可以在此处查看更多信息:
http://wiki.apache.org/tomcat/FAQ/Miscellaneous#Q27

而且,这是在Catalina服务之前包含的服务:

<Service name="ConfigService">
    <Connector port="8081" protocol="HTTP/1.1"
        connectionTimeout="20000"
        redirectPort="8444" />
    <Engine name="ConfigServiceEngine" defaultHost="localhost">
        <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">

            <Context path="/" reloadable="true" docBase="/path/to/your/service/directory" />
        </Host>
    </Engine>
</Service>

如您所见,我使用的是docbase而不是appBase,但如果您愿意,应该能够配置不同的appBase...

注意,更改服务和引擎的名称非常重要。

希望对您有帮助。


0

这里有一个我用来创建两个级别的Web应用程序加载的好技巧。在每个级别中,顺序不能保证。这依赖于Tomcat将首先从tomcat/conf/[Engine Name]/[Host Name]加载上下文描述符,然后才从server.xml中Host元素的appBase属性加载上下文。

只需在您想要在第二级(即稍后)加载的Web应用程序中的任何位置添加以下代码即可。

File contextDescriptor = new File(getParameter("catalina.home"),"/conf/Catalina/localhost/mywebapp.xml");
contextDescriptor.deleteOnExit();

-1

它们是否按照部署的顺序开始运行?

如果我有四个应用程序:1、2、3、4,我按照以下顺序部署它们:1、2、4(重新启动Tomcat)、3(重新启动)。它会在某个地方缓存一些东西以使项目始终以这种顺序启动吗?因此是1、2、4、3。这在这里发生了。我不确定,但也许对某人有帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接