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