Tomcat 인메모리 클러스터링
Tomcat은 SimpleTcpCluster를 통해 서버 간 세션을 실시간으로 복제하는 인메모리 클러스터링을 내장 지원합니다. 별도 외부 저장소 없이 Tomcat 설정만으로 세션 공유를 구현할 수 있습니다.
클러스터링 아키텍처
Tomcat 클러스터는 멀티캐스트 또는 유니캐스트로 멤버를 자동 발견하고, 세션 변경이 발생할 때마다 클러스터 내 다른 서버에 복제합니다.
[App1: 10.0.0.1]
세션 A 변경
│
├──▶ [App2: 10.0.0.2] 세션 A 복제
└──▶ [App3: 10.0.0.3] 세션 A 복제
DeltaManager vs BackupManager
| 구분 | DeltaManager | BackupManager |
|---|---|---|
| 복제 방식 | 전체 복제 (All-to-All) | 백업 복제 (Primary + Backup) |
| 복제 대상 | 모든 노드에 복제 | 1개 백업 노드에만 복제 |
| 적합한 규모 | 소규모 (2~4대) | 중규모 (4대 이상) |
| 장애 시 복구 | 어느 서버든 즉시 접수 | Primary 장애 시 Backup이 접수 |
| 메모리·네트워크 부하 | 높음 | 낮음 |
DeltaManager 설정
모든 서버에 세션을 복제합니다. 가장 단순하고 신뢰성이 높습니다.
server.xml 설정 (각 Tomcat 동일하게 적용)
<!-- /opt/tomcat/conf/server.xml -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<!-- 세션 복제 매니저 -->
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<!-- 채널 구성: 멤버 발견 + 데이터 전송 -->
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!-- 멀티캐스트로 클러스터 멤버 자동 발견 -->
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<!-- 수신: 다른 서버에서 복제 데이터 받기 -->
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="10.0.0.1"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<!-- 발신: 다른 서버로 복제 데이터 보내기 -->
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<!-- 인터셉터 -->
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<!-- 세션 복제 밸브: 요청 처리 후 변경된 세션 복제 -->
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.js;.*\.jpeg;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt"/>
<!-- 통계 리스너 -->
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Host name="localhost" appBase="webapps" ...>
</Host>
</Engine>
Tomcat 2, 3 설정 (address만 변경)
<!-- Tomcat 2 -->
<Engine jvmRoute="server2">
<Cluster ...>
<Channel>
<Receiver address="10.0.0.2" port="4000" .../>
</Channel>
</Cluster>
</Engine>
<!-- Tomcat 3 -->
<Engine jvmRoute="server3">
<Cluster ...>
<Channel>
<Receiver address="10.0.0.3" port="4000" .../>
</Channel>
</Cluster>
</Engine>
web.xml: 분산 애플리케이션 선언 (필수)
클러스터링을 사용하는 웹 애플리케이션의 WEB-INF/web.xml에 <distributable/> 태그를 추가해야 합니다:
<!-- webapp/WEB-INF/web.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://jakarta.ee/xml/ns/jakartaee"
version="6.0">
<!-- 이 태그가 있어야 세션 복제 활성화 -->
<distributable/>
<!-- 나머지 설정 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
BackupManager 설정
트래픽이 많아 전체 복제 부담이 클 때 사용합니다. 세션의 Primary 서버와 Backup 서버만 유지합니다.
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">
<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>
<!-- Channel 설정은 DeltaManager와 동일 -->
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership address="228.0.0.4" port="45564"
frequency="500" dropTime="3000"
className="org.apache.catalina.tribes.membership.McastService"/>
<Receiver address="10.0.0.1" port="4000"
className="org.apache.catalina.tribes.transport.nio.NioReceiver"
autoBind="100" selectorTimeout="5000" maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.(gif|js|jpeg|jpg|png|css|html|txt)"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
멀티캐스트가 막힌 환경: 유니캐스트 설정
클라우드나 일부 네트워크에서는 멀티캐스트가 차단됩니다. 이때는 정적 유니캐스트 멤버십을 사용합니다.
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!-- Membership 대신 StaticMembershipInterceptor 사용 -->
<Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor">
<Member className="org.apache.catalina.tribes.membership.StaticMember"
host="10.0.0.2"
port="4000"
uniqueId="{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}"/>
<Member className="org.apache.catalina.tribes.membership.StaticMember"
host="10.0.0.3"
port="4000"
uniqueId="{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3}"/>
</Interceptor>
<Receiver address="10.0.0.1" port="4000"
className="org.apache.catalina.tribes.transport.nio.NioReceiver"
autoBind="100" selectorTimeout="5000" maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
세션 직렬화: Serializable 필수
클러스터 간 세션 복제는 Java 직렬화(Serialization)를 사용합니다. 세션에 저장되는 모든 객체가 java.io.Serializable을 구현해야 합니다.
// 세션에 저장되는 클래스 — Serializable 구현 필수
public class UserSession implements java.io.Serializable {
private static final long serialVersionUID = 1L; // 버전 관리
private Long userId;
private String username;
private String role;
private LocalDateTime loginTime;
// Serializable이 아닌 필드는 transient로 제외
private transient HttpSession httpSession;
// getter/setter...
}
주의: transient로 선언된 필드는 복제에서 제외됩니다.
클러스터 동작 확인
# Tomcat 로그에서 클러스터 멤버 감지 확인
grep "McastService" /opt/tomcat/logs/catalina.out
# INFO: Sending... membership for [/10.0.0.1]
# INFO: Added member [/10.0.0.2:4000]
# INFO: Added member [/10.0.0.3:4000]
# 세션 복제 확인
grep "session" /opt/tomcat/logs/catalina.out | grep -i "replac\|replic"
# 실시간 로그 모니터링
tail -f /opt/tomcat/logs/catalina.out | grep -E "cluster|session|replac"
테스트: 세션 복제 검증
# 1. App1에 접속해 로그인 (세션 생성)
curl -c cookies.txt -X POST http://app1:8080/app/login \
-d "user=admin&pass=secret"
# 2. App2에 직접 접속해도 세션이 유지되는지 확인
curl -b cookies.txt http://app2:8080/app/profile
# 로그인 상태로 프로필 페이지 접근 가능해야 함
# 3. App1 서버 중단 후 로드밸런서를 통해 접속
sudo systemctl stop tomcat1
curl -b cookies.txt http://lb.example.com/app/profile
# 세션이 App2, App3에 복제되어 있으므로 로그인 유지
인메모리 클러스터링의 한계
서버 수 증가에 따른 복제 부하
DeltaManager는 N대의 서버에서 세션 변경 시 N-1번 복제합니다.
3대: 세션 1건 변경 → 2번 복제 (부담 작음)
10대: 세션 1건 변경 → 9번 복제 (부담 증가)
50대: 세션 1건 변경 → 49번 복제 (심각한 부담)
권장: 4대 이상에서는 BackupManager 또는 Redis 세션 공유로 전환.
네트워크 분리(Network Partition) 위험
클러스터 노드 간 네트워크가 일시 단절되면 각 노드가 독립적으로 동작해 세션 데이터가 분기됩니다. 네트워크 복구 후 충돌이 발생할 수 있습니다.
다음 페이지에서는 Redis를 이용한 외부 세션 공유로 이 한계를 해결하는 방법을 알아봅니다.