Hi all
Please bear with me as this is a little complex / indirect - I'll explain below why the JServ URL -> zone name abstraction is useful, and then present a couple of workarounds I've thought of for trying to implement the equivalent capability on top of Tomcat. I'd appreciate a critique of these ideas from experienced Tomcat users.
We still run JServ - it's simple, but it's very solid and it works very well. However, it's end of lifed, and while we've only patched it once* since, I'm also contemplating what it would take to move our main application server farm to Tomcat, which would get us out of that business and open up other upgrade paths.
A feature of JServ which we use, and which Tomcat does not appear to have an equivalent to, is the ability for the Apache layer to map URLs to JServ zones (c.f. Tomcat webapps), and thus put a layer of abstraction between the URL and the servlet which gets invoked, which is under Apache's control. As far as I can tell, in Tomcat if the URL passed to Tomcat is "/foo" then the webapp name must be "/foo"
We exploit this to make our upgrade process seamless to clients and eliminate the need for downtime. The setup is like this:
* We have about 250 customer sites running a shared Apache
configuration, and several pools of JServ appservers behind them.
* Each JServ JVM has the same config with multiple zones; each zone
contains a different releases of our software.
* Each client's site has an Apache NameVirtualHost and is mapped to
a zone, and thus a particular version of our software:ApJServMount /site balanace://appservers-grp1/v1
* Application "Foo" is invoked with a URL of the form
http://www.customer.com/site/FooWhen we ship an upgrade, we install the new software in a new zone by cycling the JVM's in and out of service, with no service disruption. Then, to switch a client over to a new version of the software, we just change the apache config, as below, and do "apachectl graceful".
ApJServMount /site balanace://appservers-grp1/v2
The URL for the application does not change, and the new version is picked up.
The beauty of this abstraction is it allows us to upgrade individual sites independently of each other, and more importantly, independently of the resource allocation of sites to pools of JVMs, e.g.
<VirtualHost>
NameVirtualHost www.site1.com
ApJServMount /site balance://appservers-grp1/v1
</VirtualHost>
<VirtualHost>
NameVirtualHost www.site2.com
ApJServMount /site balance://appservers-grp1/v2
</VirtualHost>*Possible Solutions*
Here are some ideas I've thought of, I'd be interested in a critique. Bear in mind the essential objective to keep the external URLs identical.
0. Use separate Tomcat JVMs for different software versions; each has one webapp called "/site"
This would work cleanly, but has two major drawbacks: (i) it requires additional resources (at minimum, RAM to support a pool of JVMs), and (ii) assuming that you're controlling extra resources needed by using n+1 groups of JVMs and migrating a group at a time, it constrains the freedom of what sites get migrated when and forces it to be correlated with, since the first group of migrated sites end up in the first pool of new JVMs, etc.
For these reasons, this solution isn't really adequate.
1. Map URLs in the Apache layer using mod_rewrite, and abstract HttpServletRequest.getRequestURI() in the application
This seems to be the most promising and least invasive, and I've prototyped it and it works. Fortunately, all of our servlets are derived from a common parent class in which we create our own "wrapper" for the HttpServletRequest/Response object, let's call it HomeGrownRequest, which gets passed in API calls etc.
So, in Apache we would do this:
RewriteEngine On RewriteRule ^/site/(.*)$ /v1/$1 [PT] JkMount /v1/* appservers-grp1
In Tomcat, we'd create one webapp for each product version. Tomcat will see the request URL as /v1/Foo and will invoke the "v1" webapp (product version).
In our own Java code, instead of calling HttpServletRequest.getRequestURI() we call HomeGrownRequest.getRealRequestURI() which is implemented like this:
public String getRealRequestURI() {
String foo = my_http_servlet_request.getRequestURI();
return "/site" + foo.substring(foo.indexOf("/",2));
}The other issue with this would be pushing the appropriate cookies - our session handling already pushes its own cookies on top of JServ's ones so not a big deal.
2. Do the mapping in Tomcat, using servlet context request forwarding, and create a "dispatcher" webapp
As per option 1, each product version is installed in a different webapp. Control of the mapping of sites to product versions moves to the appserver layer, and additional webapp called "site" is created which picks up this information and redispatches the request within Tomcat using RequestDispatcher.forward(). The Apache side is fixed and looks like this:
JkMount /site/* appservers-grp1
The upside I can see here is that the version mapping can be changed without touching Apache.
I haven't tried this but I've seen comments from people who have used this that the handling of session cookies and URL rewriting can get messy.
3. As above, but install everything as one big webapp and do the dispatching internally within the application code, using a custom classloader to simulate zones
This seems to be the most flexible and portable, but also strikes me as a fair amount of work to create :-)
* actually, not for a bug, but to improve the load balancing - please email me if curious
-- David Crooke, Chief Technology Officer Convio Inc. - the online partner for nonprofits 11921 N Mopac Expy, Austin TX 78759 Tel: (512) 652 2600 - Fax: (512) 652 2699
