To implement multiple virtual machines in Tomcat, follow these steps:
- Modify the configuration file
[root@server conf]# pwd
/usr/local/tomcat/conf
[root@server conf]# vim server.xml
Add the following configuration within the section:
<host appbase="/data/webapps1/" autodeploy="true" name="webapp1.example.com" unpackwars="true">
<valve classname="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="webapp1_access_log" suffix=".txt"></valve>
</host>
<host appbase="/data/webapps2/" autodeploy="true" name="webapp2.example.com" unpackwars="true">
<valve classname="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="webapp2_access_log" suffix=".txt"></valve>
</host>
- Prepare the web application data
[root@server conf]# mkdir -pv /data/webapps{1,2}/ROOT
[root@server conf]# vim /data/webapps1/ROOT/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta charset="utf-8"></meta>
<title>JSP Example</title>
The following content is dynamically generated by the server
<br></br>
<%=request.getRequestURL()%>
[root@server conf]# vim /data/webapps2/ROOT/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta charset="utf-8"></meta>
<title>JSP Example</title>
The following content is dynamically generated by the server
<br></br>
<%=request.getRequestURL()%>
- Set proper permissions
[root@server conf]# chown -R tomcat.tomcat /data/webapps{1,2}/
- Test the configuration
[root@client ~]# vim /etc/hosts
10.0.0.159 webapp1.example.com webapp2.example.com
[root@client ~]# curl http://webapp1.example.com:8080/
[root@client ~]# curl http://webapp2.example.com:8080/
Customizing Tomcat Access Logs and Reverse Proxy Configuration
- Customizing Access Log Format
To customize the access log format in Tomcat, modify the AccessLogValve configuration:
<valve classname="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt"></valve>
The pattern parameter defines the format of the log entries. Common placeholders include:
- %h - Remote host address
- %l - Remote logical username (always '-')
- %u - Remote user if authenticated
- %t - Date and time of the request
- %r - First line of the request
- %s - HTTP status code
- %b - Size of response in bytes
- Setting Up Reverse Proxy
To configure Nginx as a reverse proxy for Tomcat:
# Install Nginx from your repository
# yum install nginx -y
# vim /etc/nginx/nginx.conf
# Configure reverse proxy for all requests
location / {
proxy_pass http://backend-server1.example.com:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Test configuration and restart
nginx -t
systemctl restart nginx
Important: The proxy_pass directive determines which backend server receives the requests. If you need to route based on the original request, ensure the Host header is properly forwarded using proxy_set_header.
Implementing Tomcat Cluster with Memcached Session Manager
Environment Preparation
- Ensure time synchronization is properly configured (NTP or Chrony)
- Configure firewall rules appropriately
| IP Address | Hostname | Role | Services |
|---|---|---|---|
| 10.0.0.156 | proxy.example.com | Nginx, Apache HTTPD | |
| 10.0.0.157 | tomcat1.example.com | Application Server 1 | JDK8, Tomcat8 |
| 10.0.0.158 | tomcat2.example.com | Application Server 2 | JDK8, Tomcat8 |
- Configure Load Balancer on Proxy Server
Using Apache HTTPD as a load balancer:
[root@proxy ~]# vim /etc/httpd/conf.d/tomcat.conf
<Proxy balancer://tomcat-cluster>
BalancerMember http://tomcat1.example.com:8080 loadfactor=1
BalancerMember http://tomcat2.example.com:8080 loadfactor=1
</Proxy>
<VirtualHost *:80>
ServerName proxy.example.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://tomcat-cluster/
ProxyPassReverse / balancer://tomcat-cluster/
</VirtualHost>
[root@proxy ~]# systemctl restart httpd
- Configure Tomcat Servers for Clustering
2.1 Configure First Tomcat Server
[root@tomcat1 ~]# vim /usr/local/tomcat/conf/server.xml
<Host name="tomcat1.example.com" appBase="/data/webapps" autoDeploy="true">
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="10.0.0.157"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
</Host>
[root@tomcat1 ~]# systemctl restart tomcat.service
2.2 Configure Second Tomcat Server
[root@tomcat2 ~]# vim /usr/local/tomcat/conf/server.xml
<Host name="tomcat2.example.com" appBase="/data/webapps" autoDeploy="true">
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="10.0.0.158"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
</Host>
[root@tomcat2 ~]# systemctl restart tomcat.service
2.3 Configure Web Applications for Clustering
On both Tomcat servers, modify the web.xml file to make the application distributable:
[root@tomcat1 ~]# mkdir -p /data/webapps/ROOT/WEB-INF/
[root@tomcat1 ~]# cp -a /usr/local/tomcat/webapps/ROOT/WEB-INF/* /data/webapps/ROOT/WEB-INF/
[root@tomcat1 ~]# vim /data/webapps/ROOT/WEB-INF/web.xml
<web-app>
...
<description>Sample web application</description>
<distributable/>
</web-app>
[root@tomcat1 ~]# chown -R tomcat.tomcat /data/webapps/
[root@tomcat1 ~]# systemctl restart tomcat.service
[root@tomcat2 ~]# mkdir -p /data/webapps/ROOT/WEB-INF/
[root@tomcat2 ~]# cp -a /usr/local/tomcat/webapps/ROOT/WEB-INF/* /data/webapps/ROOT/WEB-INF/
[root@tomcat2 ~]# vim /data/webapps/ROOT/WEB-INF/web.xml
<web-app>
...
<description>Sample web application</description>
<distributable/>
</web-app>
[root@tomcat2 ~]# chown -R tomcat.tomcat /data/webapps/
[root@tomcat2 ~]# systemctl restart tomcat.service
JVM Garbage Collection Algorithms and Memory Generations
- Mark-Sweep Algorithm
This algorithm consists of two phases: marking and sweeping.
- Marking phase: Identifies all accessible objects and marks them.
- Sweeping phase: Removes all unmarked objects (garbage) from memory.
Characteristics:
- Advantage: Simple algorithm implementation
- Disadvantage: Creates memory fragments, though it doesn't waste memory space
- Efficiency: Can be affected when there are many objects to remove
- Mark-Compact Algorithm
This algorithm also has two phases: marking and compacting.
- Marking phase: Identifies and marks all accessible objects.
- Compacting phase: Moves objects to one end of memory and compacts them.
Characteristics:
- Advantage: Eliminates memory fragmentation by creating contiguous blocks of free memory
- Disadvantage: The compacting process consumes additional CPU resources
- Efficiency: Lower than mark-sweep due to the overhead of moving objects
- Copying Algorithm
This algorithm divides memory into two equal-sized spaces (A and B). Only one space is used at a time.
- When space A is filled, live objects are copied to space B, and space A is cleared.
- The roles of A and B are then swapped for the next cycle.
Characteristics:
- Advantage: No memory fragmentation, efficient garbage collection regardless of object count
- Disadvantage: Wastes 50% of memory capacity as only half is available for use
- Heap Memory Generations
The Java heap is divided into three generations:
- Young Generation: Where new objects are allocated
- Old Generation: Where long-lived objects reside
- Metaspace: Stores class metadata (replaced PermGen in Java 8+)
Young Generation Structure
- Eden Space: Where all new objects are initial created
- Survivor Spaces (S0 and S1): Two spaces of equal size where objects survive after garbage collection
Minor GC (Young Generation Collection)
- When Eden space is full, a Minor GC is triggered.
- Live objects in Eden are copied to one Survivor space (S0 or S1).
- Eden is then cleared.
- When Eden fills again, another Minor GC copies live objects from both Eden and the active Survivor space to the other Survivor space.
- The process repeats, with objects being copied between Survivor spaces as they survive multiple collections.
Major GC (Old Generation Collection)
- Occurs less frequently than Minor GC as objects in the Old Generation have longer lifespans.
- Typically uses Mark-Compact algorithm due to the higher number of surviving objects.
- When the Old Generation fills up, it triggers a Major GC (also known as Full GC).
- A Major GC often includes a Minor GC first for better efficiency.
Memcached Usage, Installation, and MSM Principles
- Common Memcached Command Options
-u username # Run memcached as the specified user (must be non-privileged)
-p port # Network port to listen on (default: 11211)
-m num # Maximum memory size in MB (default: 64)
-c num # Maximum simultaneous connections (default: 1024)
-d # Run as a daemon
-f factor # Memory allocation growth factor (default: 1.25)
-v # Verbose output
-vv # Even more verbose output
-M # Return error when memory is exhausted instead of LRU eviction
-U UDP_port # UDP port number (0 to disable UDP)
- Basic Memcached Commands
Connect to memcached using telnet:
[root@server ~]# telnet localhost 11211
# Add a key-value pair
add mykey 1 60 4
test
STORED
# Retrieve a value
get mykey
VALUE mykey 1 4
test
END
# Update a value
set mykey 1 60 5
test1
STORED
get mykey
VALUE mykey 1 5
test1
END
# Delete a key
delete mykey
DELETED
get mykey
END
# Flush all data
flush_all
OK
quit
- Compiling and Installing Memcached
[root@server ~]# apt update && apt install gcc make libevent-dev -y
[root@server ~]# tar xf memcached-1.6.20.tar.gz
[root@server ~]# cd memcached-1.6.20/
[root@server ~]# ./configure --prefix=/usr/local/memcached
[root@server ~]# make && make install
[root@server ~]# ln -s /usr/local/memcached/bin/memcached /usr/local/bin
[root@server ~]# useradd -r -s /sbin/nologin memcached
[root@server ~]# memcached -u memcached -d -f 2 -vv
Verify the installation:
[root@server ~]# ss -ntl | grep 11211
LISTEN 0 1024 127.0.0.1:11211 0.0.0.0:*
LISTEN 0 1024 [::]:11211 [::]:*
- MSM (Memcached Session Manager) Principles
MSM is a component that enables Tomcat to store session data in memcached or Redis, providing:
- Session replication across multiple Tomcat instances
- High availability by preventing session loss if a server fails
- Scalability by allowing horizontal scaling of application servers
MSM works by intercepting session operations and storing them in the memcached cluster instead of memory. When a request arrives at any Tomcat instance, the session is retrieved from memcached if it's not already in local memory.
To configure MSM in Tomcat:
- Add the MSM library to Tomcat's lib directory
- Configure the MSM parameters in context.xml
- Ensure the web application is marked as distributable in web.xml