Tomcat application server is good at serving dynamic content. Though it can serve static content too, it’s generally left to the Web servers to do the job as its lightweight and specialised. Hence most Java-based web application would generally involve Apache to serve static content while dynamic content is left to Tomcat. Integration between Apache and Tomcat can be done using the JK Connector. The JK Connector uses the Apache JServ Protocol (AJP) to communicate between Apache and Tomcat.
The AJP Connector
The AJP protocol is used for communication between Tomcat and Apache, the extension modules used on Apache are mod_jk or mod_proxy. Both are native code extension modules written in C/C++, on the Tomcat side the software module is the AJP Connector written in Java.
The below diagram shows how the native code Apache module (mod_jk or mod_proxy) works with Tomcat. Apache will receive the incoming request for a JSP or servlet and Apache using the extension modules will pass this request via the AJP protocol to Tomcat. The response is sent back to the Apache via the AJP protocol and back to the client.
The Apache JServ Protocol (AJP) uses a binary format for transmitting data between the Web server and Tomcat, a network socket is used for all communication. The AJP packet consists of a packet header and a payload below is the structure of the packet
As we can see, the binary packet starts with the sequence 0x1234, this is followed by the packet size (2 bytes) and then the actual payload. On the return path, the packets are prefixed by AB (the ASCII codes for A and B), the size of the packet and then the payload.
The major feature of this protocol is
- Good performance on fast networks
- Support for SSL, encryption and client certificate
- Support for clustering by forwarding requests to multiple Tomcat servers
One of the ways the AJP protocol reduces latency is by making the Web servers reuse already open TCP-level connections with Tomcat. This saves the overhead of opening a new socket connection for each request, it’s a bit like a connection pool.
Sample configuration for an AJP Connector is as below
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Tomcat Workers for Apache
A worker represents a running instance of Tomcat, a worker serves the requests for all dynamic web components. However, you can run multiple instances of Tomcat in a cluster to implement load balancing or site partitioning. Each worker is identified by a unique hostname or a unique IP address and port number. You may what to implement multiple workers for the following reasons
- Web application contexts to be served by different Tomcat workers
- Different virtual hosts to be served by different Tomcat workers
- Service more requests than the capacity of a single physical server
To let Apache know where the Tomcat servers and offload work to Tomact a file called workers.properties is created detailing this information. Below are the important attributes to connect Apache to Tomcat.
Worker List
Attribute | Description |
---|---|
work.list | Describe the workers that are available to Apache via a list |
Worker Types
Attribute | Description |
---|---|
ajp13 | This type of worker represents a running Tomcat instance |
lb | Used for load balancing |
status | Display useful information about how the load among the various Tomcat workers is distributed |
jni | Used in the process, this worker handles the forwarding of requests to in-process Tomcat workers using JNDI |
ajp12 | Worker that support the AJP 1.2 protocol |
Other Worker Properties
Attribute | Description |
---|---|
worker.test1.type | Describes the type of worker (see above for types) |
worker.test1.host | The host where the worker Tomcat instance resides |
worker.test1.port | The port the AJP 1.3 Connector Tomcat instance is listening on (default 8009) |
worker.test1.connection_pool_size | The number of connections used for this worker to be kept in a connection pool |
worker.test1.connection_pool_minsize | The minimum number of connections kept in a connection pool |
worker.test1.connection_pool_timeout | The number of seconds that connections to this worker should be left in the connection before expiry |
worker.test1.mount | The contexts paths that are serviced by the worker, you can also use the JkMount directive in the http.conf file |
worker.test1.retries | Controls the number of times mod_jk will retry when a worker returns an error |
worker.test1.socket_timeout | Controls how long a worker will wait for a response on a socket before indicating an error |
worker.test1.socket_keepalive | Indicates if the connection to the worker should be subject to keep alive |
worker.test1.lbfactor | An integer indicating the local-balance factor used by the load balancer to distribute work between multiple instances of Tomcat. |
Worker Loading Balancing Properties
Attribute | Description |
---|---|
worker.bal1.balance_workers | A list of workers to load balance between |
worker.bal1.lock | The type of locking used O (Optimistic) or P (Pessimistic) |
worker.bal1.method | can be set to R (Requests), T (Traffic), B (Busy-ness) R = The worker to use is based on the number of requests forwarded T = The worker to use is based on the traffic that had been sent to the workers B = The worker to use is based on the load dividing the number of concurrent requests by the load factor |
worker.bal1.secret | Sets a default secret password for all workers |
worker.bal1.sticky_session | Tells the mod_jk to respect the sessionID in the request and ensures that the same session is always serviced by the same worker instance. |
worker.bal1.sticky_session_force | This is used for failover |
Example Configurations
Simple example
worker.list = worker1
worker.worker1.type = ajp13
worker.worker1.host = 192.168.0.1
worker.worker1.port = 9009
worker.worker1.connection_pool_size = 5
worker.worker1.connection_pool_timeout = 300
Load Balancing example
worker.list = loadbal1,stat1
worker.tomcatA.type = ajp13
worker.tomcatA.host = 192.168.0.1
worker.tomcatA.port = 8009
worker.tomcatA.lbfactor = 10
worker.tomcatB.type = ajp13
worker.tomcatB.host = 192.168.0.2
worker.tomcatB.port = 8009
worker.tomcatB.lbfactor = 10
worker.tomcatC.type = ajp13
worker.tomcatC.host = 192.168.0.3
worker.tomcatC.port = 8009
worker.tomcatC.lbfactor = 10
worker.loadbal1.type = lb
worker.loadbal1.sticky_session = 1
worker.loadbal1.balance_workers = tomcatA,tomcatB,tomcatC
worker.stat1.type= status
Note: if one of your servers is a slow serving then lower the lbfactor of that server
Apache HTTPD configuration
There are a number of Apache directives that you can configure in the httpd.conf file
Apache mod_jk Directives
Directive | Description |
---|---|
JkWorkerFile | tells mod_jk where to find the workers property file |
JkLogFile | tells mod_jk where to write its logs |
JkLogLevel | sets the level of logging (info, error or debug) |
JkRequestLogFormat | specifies the log format, below are the options that you can use %b or %B - bytes transmitted (not counting HTTP headers) %H - request protocol %m - request method %p - port of the server for the request %r - first line of the request %T - request duration %U - URL of the request with query string removed %v or %V - server name %w - name of the tomcat worker %R - the route name of the session |
JkMount | control the URL matching and forwarding to the Tomcat workers |
Example directive configuration
JkWorkerFile conf/worker.properties
JkLogFile logs/mod_jk.log
JkShmFile logs/mod_jk.shm
JkLogLevel debug
JkRequestLogFormat "%w %U %T"
JkMount /examples/jsp/* loadbal1