1. 시작에 앞서

참고 도서는 자바 고양이 Tomcat 이야기 입니다.


2. 개요 

Tomcat은 5.x 부터 cluster 기능을 제공하기 시작했으며, 부족하긴 하지만 현재 session cluster가 가능합니다. 이 session cluster 기능은 Tomcat의 tribes 라는 통신 모듈에 의하여 구현되어 있습니다.

물론 그러기 위해서는 앞 단에 Apache HTTP Server 등을 두고 AJP Connector를 이용한 연동 작업이 필요합니다. 그 부분은 이번 Article에서는 기술하지 않습니다.


3. Tomcat 레벨

3-1. Session Manager 종류

Tomcat의 session 관련 manager는 다음의 총 4가지 입니다.

종류 상세
org.apache.catalina.session.StandardManager 가장 기본적인 Manager 로 하나의 Tomcat 인스턴스에만 유효함
org.apache.catalina.session.PersistentManager Session persistence 를 위하여 파일시스템 혹은 DB 를 사용
org.apache.catalina.ha.session.DeltaManager Cluster 환경에서 사용하며, cluster 간에 모든 session 을 replication 
org.apache.catalina.ha.session.BackupManager Cluster 환경에서 사용하며, 정해진 backup 인스턴스로만 replication 

3-1-1. PersistentManager 

File Based Store와 JDBC Based Store의 두 방식이 있습니다. 먼저 File Based Store 는 설정된 디렉토리 내 개별 파일에 session 정보가 저장됩니다. 파일명은 session id 기반입니다. 그리고 JDBC Store 는 JDBC Driver를 통해 연결된 DB 내 사전 정의된 table에 session 정보가 저장됩니다.

실제적인 in-memory replication 방식의 session manager 는 이제부터 설명할 DeltaManager 와 BackupManager 입니다.

3-1-2. DeltaManager

모든 Tomcat 인스턴스 사이의 session 이 replication 됩니다. All-to-all replication 방식이라고도 표현되는데 소규모 cluster에서는 큰 무리가 없지만 cluster 그룹에 참여하는 인스턴스가 증가할 수록 문제가 될 수 있습니다. 즉, 모든 node 가 모든 session을 가져야 하기 때문에 많은 메모리를 사용하게 되고 network traffic 이 증가하게 되죠. 심지어 응용이 배포되어 있지 않은 인스턴스까지 session replication의 대상이 됩니다.

3-1-3. BackupManager

DeltaManager의 문제점을 해결하기 위해 사용됩니다. 오직 하나의 인스턴스와 그 인스턴스의 backup 인스턴스 간에만 session이 replication 됩니다. 즉, master-backup 형태입니다. DeltaManager에 비하여 메모리를 덜 사용하고 network traffic도 적습니다. 다만 아직 DeltaManager만큼 검증되어 있지 않다고 알려져 있습니다.

3-2. server.xml 설정

각 Tomcat 인스턴스의 server.xml에 cluster 관련 설정 추가가 필요합니다.

  • <Membership>

멤버 그룹을 정의하는 설정으로 동일 클러스터로 묶고자 하는 Tomcat 인스턴스들은 동일한 값으로 설정합니다.

  • <Receiver>

클러스터 그룹으로부터의 메세지와 세션 정보를 받는 것으로, 각 인스턴스는 서로 다른 포트를 사용합니다.

  •  <Sender>

클러스터 그룹으로 자신의 정보를 전송합니다.

3-3. 설정 적용

먼저 가장 기본 설정을 적용하여 보겠습니다.

즉, server.xml에서 비활성화되어 있는 <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> 를 활성화시키고 Tomcat을 기동합니다.

INFO: Starting Servlet Engine: Apache Tomcat/7.0.53-dev
Mar 25, 2014 8:02:15 PM org.apache.catalina.ha.tcp.SimpleTcpCluster startInternal
INFO: Cluster is about to start
Mar 25, 2014 8:02:15 PM org.apache.catalina.tribes.transport.ReceiverBase bind
INFO: Receiver Server Socket bound to:/192.168.1.100:4000
Mar 25, 2014 8:02:15 PM org.apache.catalina.tribes.membership.McastServiceImpl setupSocket
INFO: Setting cluster mcast soTimeout to 500
Mar 25, 2014 8:02:15 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:4
Mar 25, 2014 8:02:16 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Done sleeping, membership established, start level:4
Mar 25, 2014 8:02:16 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:8
Mar 25, 2014 8:02:17 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Done sleeping, membership established, start level:8

위 Tomcat 로그에서 "INFO: Starting Servlet Engine: Apache Tomcat/7.0.53-dev" 라인 이후 부터는 cluster 설정된 경우에만 출력되는 로그입니다.

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> 을 활성화, 즉 아주 기본 설정의 cluster 설정을 적용하면 DeltaManager를 사용하게 됩니다. 하지만 DeltaManager 의 단점은 위에서 기술한 바와 같이 all-to-all 이기 때문에 cluster 규모가 큰 경우에는 권장하지 않습니다.

이외에 기본 설정은 다음과 같습니다.

  • Multicast address 는 228.0.0.4 
  • Multicast port 는 45564 
  • Replication message listen 을 위한 TCP port 는 4000~4100 중 첫 번째 가능한 port
  • ClusterSessionListener와 JvmRouteSessionDBinderListener의 두 listener가 설정됨
  • TcpFailureDetector와 MessageDispatch15Interceptor의 두 interceptor가 설정됨

이는 Tomcat 의 McastServer.class 에서도 확인이 가능합니다.

    public McastService() {
        //default values
        properties.setProperty("mcastPort","45564");
        properties.setProperty("mcastAddress","228.0.0.4");
        properties.setProperty("memberDropTime","3000");
        properties.setProperty("mcastFrequency","500");

    }

세 번째의 Replication message listen을 위한 포트는, 위의 로그에서는 4000번을 사용한 것이 확인되었습니다.

자 그럼, 여기서 동일 장비에 인스턴스를 하나 더 띄워보도록 하겠습니다. 앞서 띄운 인스턴스를 1번 인스턴스, 이번에 띄우는 인스턴스를 2번 인스턴스라고 하겠습니다.

INFO: Starting Servlet Engine: Apache Tomcat/7.0.53-dev
Mar 25, 2014 8:07:35 PM org.apache.catalina.ha.tcp.SimpleTcpCluster startInternal
INFO: Cluster is about to start
Mar 25, 2014 8:07:35 PM org.apache.catalina.tribes.transport.ReceiverBase bind
INFO: Receiver Server Socket bound to:/192.168.1.100:4001
Mar 25, 2014 8:07:35 PM org.apache.catalina.tribes.membership.McastServiceImpl setupSocket
INFO: Setting cluster mcast soTimeout to 500
Mar 25, 2014 8:07:35 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:4
Mar 25, 2014 8:07:35 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 1, 100}:4000,{192, 168, 1, 100},4000, alive=320006, securePort=-1, UDP Port=-1, id={-95 106 1 -13 59 107 72 -112 -120 30 30 120 55 102 -85 116 }, payload={}, command={}, domain={}, ]
Mar 25, 2014 8:07:36 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Done sleeping, membership established, start level:4
Mar 25, 2014 8:07:36 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:8
Mar 25, 2014 8:07:36 PM org.apache.catalina.tribes.io.BufferPool getBufferPool
INFO: Created a buffer pool with max size:104857600 bytes of type:org.apache.catalina.tribes.io.BufferPool15Impl
Mar 25, 2014 8:07:37 PM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Done sleeping, membership established, start level:8

2번 인스턴스의 Replication message listen 포트가 4001이 되었습니다. 그리고 무엇인가 1번 인스턴스에서는 없던 로그가 2번 인스턴스 기동 로그에서 확인됩니다. 다음이죠.

Mar 25, 2014 8:07:35 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 1, 100}:4000,{192, 168, 1, 100},4000, alive=320006, securePort=-1, UDP Port=-1, id={-95 106 1 -13 59 107 72 -112 -120 30 30 120 55 102 -85 116 }, payload={}, command={}, domain={}, ]

그럼 1번 인스턴스는 아무런 변화가 없을까요? 아닙니다.

Mar 25, 2014 8:07:36 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 1, 100}:4001,{192, 168, 1, 100},4001, alive=1033, securePort=-1, UDP Port=-1, id={-70 -11 54 0 29 114 78 -95 -93 -35 -117 -87 -3 98 -98 -92 }, payload={}, command={}, domain={}, ]

1번 인스턴스도 2번 인스턴스를 잘 인식하였음을 확인할 수 있습니다. 로그 시간으로 보니 1초 차이네요.

자, 마지막으로 3번 인스턴스를 추가로 띄웁니다. 그리고 3번 인스턴스의 기동 로그를 보니 1번 인스턴스, 2번 인스턴스를 차례대로 인식하면서 올라오는 것을 확인할 수 있습니다. 물론 1번 인스턴스와 2번 인스턴스의 로그에는 3번 인스턴스를 새롭게 인식한 것을 확인할 수 있구요.

Mar 25, 2014 8:19:33 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 1, 100}:4000,{192, 168, 1, 100},4000, alive=1037106, securePort=-1, UDP Port=-1, id={-95 106 1 -13 59 107 72 -112 -120 30 30 120 55 102 -85 116 }, payload={}, command={}, domain={}, ]
Mar 25, 2014 8:19:33 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 1, 100}:4001,{192, 168, 1, 100},4001, alive=717605, securePort=-1, UDP Port=-1, id={-70 -11 54 0 29 114 78 -95 -93 -35 -117 -87 -3 98 -98 -92 }, payload={}, command={}, domain={}, ]

4. 응용 레벨

HTTP Session 객체에 저장하려는 모든 instance는 java.io.Serializable을 implements 해야 합니다. 그리고 응용의 web.xml에 <distributable/>을 반드시 추가해야 합니다. 이 부분은 추후에 또 자세히 정리하도록 하겠습니다.