3

I have a Spring Boot application where each instance of the application is a node in a cluster, and each node needs to be able to talk to the other nodes in the cluster to share information. For performance reasons, this cannot happen via HTTP, although the initial contact for each node in the cluster is via an HTTP call.

To make the initial HTTP call, instances need to be able to obtain the port that the other instances are running on so that they can register with each other. This means that each node needs to know what port its Tomcat instance is using.

This works fine if I deploy a WAR file that contains an embedded Tomcat instance. I register an ApplicationListener for the EmbeddedServletContainerInitializedEvent and get the port from the EmbbededServletContainer.

The problem comes when I deploy the WAR file to an already running Tomcat instance. I am unable to find a way to determine what port that Tomcat instance is running on, and the aforementioned event does not fire anymore as the Tomcat instance is already running.

Anyone got any ideas on how to find out what port is being used?

  • Have you tried anything yet? Here is a post for tomcat7 http://stackoverflow.com/questions/6833947/org-apache-catalina-serverfactory-getserver-equivalent-in-tomcat-7 have you seen it? – Ann Addicks Sep 17 '15 at 19:53
  • Hi Ann. Thanks for the response. I found the answer you referenced a while ago, but it did not work for me at that point. After reading your question I went and tried it out again, and discovered that the code mentioned is sensitive to when it gets called. My code is running before the SpringBoot code has even finished initializing. I have now found ow to make this work, and that resolves my issue. – Bruce Gruenbaum Sep 18 '15 at 22:18

3 Answers3

0

Simply: you can't. The webapp doesn't know the container it is running in. In fact a Tomcat could have multiple connectors and be listening in multiple ports...

joshiste
  • 101
  • 4
  • Thanks. Yeah. This is exactly the answer I expected. As I said in one of my other replies, I'm trying to figure out if this is even possible. We have a working solution with deployment using an embedded Tomcat container. In this case, though, we're deploying in AWS using Elastic Beanstalk and Docker behind a load balancer, and I have to handle discovery in AWS using the Describe API and tags on machine instances. I'm just trying to determine if this is even possible. Your answer seems to corroborate what I thought. – Bruce Gruenbaum Sep 18 '15 at 16:32
0

I know this doesn't answer your question specifically, however there are much easier ways of implementing cluster node discovery.

As an obvious example I would recommend looking into using the shared database. This has been implemented by JGroups as JDBC_PING.

Alex Barnes
  • 7,174
  • 1
  • 30
  • 50
  • Hi Alex, Thanks for the advice. Our shared persistence layer is already a combination of Hazelcast, Cassandra/DynamoDB. This deployment is not an issue with embedded Tomcat instances, but it is an issue when we use a combination of Elastic Beanstalk, Docker, and Tomcat (unembedded). At this point, I'm trying to establish if I'm wasting my time trying to resolve this and we should just turf Docker and unembedded Tomcat, as the application works perfectly without it. – Bruce Gruenbaum Sep 18 '15 at 16:28
0

After some work, the fix to this issue is explained in the code in the following question (thank you Ann Addicks for making me look at it again): org.apache.catalina.ServerFactory.getServer() equivalent in Tomcat 7

This code is very sensitive to where it gets called in a SpringBoot application. My initialization code runs very early in the startup of the SpringBoot application, as a result of the ApplicationPreparedEvent. When I first tried this code, I had it running so early that the code would fail. I'm not quite sure why, but I didn't need to do this initialization that early, so I moved the call to this code a little later in my startup and it solved the problem.

There are a couple of caveats to this code to be aware of:

  1. As joshiste pointed out, this only gets me one of the Tomcat ports, but that's all that I need in my case.

  2. As Alex pointed out, clustering should really be handled by some back-end persistence store, and I am already doing that. The issue is that there are times where one node needs to call another node because of intimate knowledge the other node has in the cluster. It's an unusual situation that is critical to the integrity of the overall cluster.

The following method shows how I finally got this to work:

private int getTomcatContainerPort() throws MalformedObjectNameException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
    int serverPort = 0;
    MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
    ObjectName name = new ObjectName("Catalina", "type", "Server");
    Server server = (Server) mBeanServer.getAttribute(name, "managedResource");
    Service[] services = server.findServices();
    for (Service service : services) {
        for (Connector connector : service.findConnectors()) {
            ProtocolHandler protocolHandler = connector.getProtocolHandler();
            if (protocolHandler instanceof Http11Protocol
                || protocolHandler instanceof Http11AprProtocol
                || protocolHandler instanceof Http11NioProtocol) {
                serverPort = connector.getPort();
                break;
            }
        }
    }
    return serverPort;
}
Community
  • 1
  • 1