Apache Tomcat

Tomcat Session Cluster 에 대하여 (1)

aappsroot·2014년 3월 25일·조회 35,265

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/>을 반드시 추가해야 합니다. 이 부분은 추후에 또 자세히 정리하도록 하겠습니다.

댓글 4

로그인 후 댓글을 남길 수 있습니다.

  • 몽상가몽상가· 2014년 3월 25일
    all to all 세션복제의 단점을 만회하기 위해 나온 백업복제 방식은... 음... 저는 이런 생각이 듭니다. 내 집앞에 있는 눈이 보기 싫고 불편해서 남의 집 앞으로 슬며시 밀어 놓은 것 같다고. 사실 완벽한 세션 클러스터를 위해서는 all to all 복제가 답입니다. 다만 앱스루트님이 말씀하신 것처럼 규모가 커지면 오버헤드가 생기는게 단점입니다. 하지만 이런 결함을 보완하기 위해 나온 백업 방식은 사실 이렇게 얘기하고 있습니다. "난 열심히 복제할 뿐이고 앞단에 밸런서들이 알아서 세션을 유지할 수 있게 노력해봐."라고... 제가 이렇게 말하는 이유는 백업방식(또는 버디방식)의 클러스터는 WAS 단에서는 세션을 복제하고 있지만 WAS에 장애가 생기면 request를 밸런서 단에서 알아서 잘! 복제된 세션이 있는 WAS로 보내야 하기 때문입니다. 만약 세션이 복제되어 있지 않은 WAS로 request를 잘못! 보냈을 때는 온전히 밸런서의 책임이기 때문입니다. 내집 앞 눈은 내가 치웁시다!!!
  • 빅토르최빅토르최· 2014년 3월 25일
    민감한 부분이기는 한데, 개인적으로 Tomcat 의 cluster 기능을 운영환경에서 사용하기는 어렵다고 봅니다. 특히 2 node (=instance) 를 초과하는 경우는 더더욱 그러할 것 같구요. 기술적 근거를 여기에 다 적기는 어렵네요. 여하튼 횡으로 펼쳐진 아키텍처에서는 별도 cluster 솔루션 도입이 정답이라고 생각하고 restful 같은 logic 적 해결책을 고민해 볼 수도 있겠네요.
  • unnamedunnamed· 2014년 9월 30일
    무려 조회수가 500.. 많은 분들이 다녀가시는 것을 보니 신기합니다
  • 열린기술자열린기술자· 2017년 7월 21일
    무려 조회수가 12947..