16장. JBoss EAP 클러스터링
자바기반의 웹 애플리케이션 서버를 사용한 대규모 웹 시스템이 많아지면서 24시간 365일 안정적인 서비스를 제공하면서도 앞으로의 확장성을 보장할 수 있도록 구성하는 것이 매우 중요하게 되었다. 이번 장에서는 웹 애플리케이션 서버를 사용하여 고가용성이 요구되는 웹 시스템을 구축하는 컨설턴트나 관리자, 개발자들에게 JBoss EAP 6 를 활용하기 위해 필요한 내용과 배경 지식에 대해 설명한다.
1.클러스터링 이해
클러스터(Cluster)란 네트워크를 이용하여 마치 하나의 컴퓨터처럼 동작하도록 여러 대의 컴퓨터를 연결하여 구성하는 것을 말한다. 즉 여러 대의 서버들 전체를 한 대의 서버 시스템과 같이 동작하게 하는 기술이나 기능을 말한다. 클라이언트 입장에서는 특정 서버의 상태에 의존하지 않고 클러스터로 묶인 서버 그룹을 마치 하나의 서버에서 서비스를 제공하는 것으로 인식한다. 클러스터 내의 어떤 서버로 접속하든지 동일하게 처리되기 때문에 클라이언트의 요청을 클러스터 멤버에 분산하여 처리하는 것이다. 이렇게 처리함으로써 클라이언트는 여러 개의 서버를 묶은 클러스터가 마치 하나의 서버인 것처럼 보이게 된다.
확장성과 고가용성이 요구되는 웹 시스템에서는 각각의 레이어 마다 고유의 클러스터링 기술을 사용하고 있다. JBoss EAP6를 사용한 웹 시스템 구축에서도 로드밸런싱과 페일오버(failover)를 위해서 클러스터링 기능을 사용한다. JBoss 는 자바 프로세스 단위로 클러스터링을 구성하며, 서버의 물리적인 위치에 관계없이 구성이 가능하다.
특히 고가용성이 요구되는 미션 크리티컬 업무에서 클러스터링은 매우 중요하다. 예를 들어 금융권에서의 인터넷 뱅킹 시스템, 제조업에서 생산공정관리 시스템, 온라인 쇼핑몰 시스템 등의 서비스에서 클러스터를 이용하여 마치 하나의 서버에서 제공하는 것처럼 구성한다. 클러스터링을 통해 특정 서버 장애가 전체 서비스에 영항을 미치지 않도록 구성하는 것이 매우 중요하다.
클러스터를 구성하는 목적은 크게 두 가지로 부하 분산(Load Balancing)과 고가용성(High Availability)이다.
-
부하 분산(Load Balancing)
부하 분산은 클러스터 앞 단에서 모든 클러스터 멤버로 부하를 균등하게 분산하는 것이다. 이론적으로 클러스터를 사용하면 처리량은 클러스터를 구성하는 서버들의 성능을 합친 것이 된다. 처리량을 늘리고 싶다면, 클러스터에 새로운 서버를 추가하여 전체 클러스터의 처리량을 늘릴 수 있다.

그림 1. 로드 밸런서의 역할
-
고가용성(HA ; High Availability)
고가용성은 클러스터 멤버에 장애가 발생할 경우 다른 멤버가 그 역할을 대신할 수 있도록 하는 것이다. HA 클러스터링은 서비스의 다운 타임을 줄여 준다.
클러스터의 한 서버가 중지될 경우에 다른 서버가 그 역할을 대신(Failover) 처리하여 고가용성을 제공한다. 클러스터링을 구성하여 서비스를 중단 없이 항상 사용 가능한 상태를 유지할 수 있다.

그림 2. 웹 애플리케이션 서버의 HA기능
-
JBoss EAP 6 클러스터
JBoss EAP 6에서는 웹 애플리케이션, EJB 애플리케이션, JMS 기능에 각각 클러스터링을 적용할 수 있다. JBoss EAP 6에는 설정을 편하게 할 수 있도록 프로파일 단위로 클러스터링이 가능한 구성 설정 파일을 제공하고 있다. 파일명의 뒷부분에 ha, full-ha 프로파일이 클러스터링을 제공하는 프로파일이다. 클러스터링을 구현하는 핵심기술은 요청에 대한 로드 밸런싱과 상태를 복제하는 기술이다.
먼저 클러스터링을 구현하기 위한 핵심 기술에 대해서 살펴보고, 웹 애플리케이션, EJB 애플리케이션, JMS에 대한 클러스터링 설정 방법을 설명한다.
2.클러스터링의 핵심기술
JGroups
JGroups는 멀티캐스트 프로토콜을 사용하여 신뢰성 높은 통신을 할 수 있도록 구현된 네트워크 통신 라이브러리이다. JBoss EAP 6의 클러스터링 구현, Infinispan의 네트워크 캐시 구현, HornetQ의 클러스터링 구현 등에 JGroups(http://www.jgroups.org)가 사용된다.
JGroups에서 중요한 프로토콜은 가입과 탈퇴(JOIN/REMOVE), 장애 감지(FD, FD_SOCK) 파티션 결합(MERGE), PING이다.
| 항목 | 설명 |
|---|---|
| 가입(JOIN) | JGroups를 시작할 때 만들어지는 멀티캐스트 그룹 가입을 위한 방식이다. 처음으로 가입하여 다른 멤버가 없으면 리더(코디네이터)가 된다. 멤버가 있으면 기존 리더에게 참가 요청을 하여 멤버 리스트에 추가해 달라고 한다. 그러면 리더가 그룹에 속한 모든 멤버들에게 클러스터 멤버 리스트를 배포하고 정보를 공유한다. |
| 탈퇴(REMOVE) | 탈퇴(REMOVE)은 JGroups 정지시 멀티캐스트 그룹에서 탈퇴하기 위한 것이다. 리더가 아닌 일반 멤버가 탈퇴하는 경우에는 리더가 클러스터 멤버 리스트를 업데이트하고 다른 멤버에게 배포한다. 리더 자신이 탈퇴하는 경우엔 두 번째 멤버가 리더가 되고 클러스터 멤버 리스트를 업데이트하여 다른 멤버에게 배포한다. 마지막 멤버가 탈퇴하면 그 그룹은 없어진다. |
| 장애 감지(FD) | 장애 감지(FD; Failure Detection)는 Heart Beat 메시지를 사용하여 오류가 발생한 멤버를 감지하는 것이다. 각 멤버는 Heart Beat 메시지를 수신하면 응답 메시지를 보내 정상적인 상태 임을 알려야 한다. 지정한 타임아웃 시간 동안 재시도하여 응답을 받지 못하면, 멤버에 오류가 발생한 것으로 간주하여 클러스터 멤버에서 제거한다. |
| 장애 감지 소켓(FD_SOCK) | 장애 감지 소켓(FD_SOCK)은 클러스터 멤버들간에 TCP 소켓으로 A → B → C → A처럼 링 모양으로 연결하여 상태를 모니터링하기 위한 프로토콜이다. 링 모양이기 때문에 각 멤버는 그 이웃 멤버만 감시하게 된다. 멤버 B가 정상 종료할 때는 멤버 A에 메시지를 보내 종료되는 것을 알려준다. 만약, 멤버 B에 장애가 발생하여 갑자기 종료되면, 멤버 A는 연결된 소켓이 비정상 종료되는 것을 감지할 수 있다. 장애를 감지하면 확인 단계를 거치고 장애가 확실하면 클러스터 멤버 리스트에서 삭제한다. |
| 파티션 결합 (MERGE2) | MERGE는 통신 장애 등 여러 가지 이유로 멀티캐스트 그룹이 여러 개로 나뉜 경우 쪼개진 그룹을 하나로 결합하기 위한 것이다. 리더(코디네이터)는 주기적으로 리더가 여기 있다는 멀티캐스트 메시지를 보낸다. 만약 그룹이 쪼개져서 만들어진 다른 그룹의 리더가 이 메시지를 받으면 결합 프로세스를 시작한다. {A, B}와 {C, D, E}을 결합하여 하나의 {A, B, C, D, E}의 그룹으로 만든다. |
| PING | 처음 클러스터의 멤버를 발견하는 데 사용하는 프로토콜이다. 멀티캐스트 주소에 MPING 요청을 보내서 리더(코디네이터)를 찾고 다른 멤버들을 찾는다. 멀티캐스트를 사용할 수 없는 환경에서는 TCPPING이나 GOSSIP 서버를 이용한 TCPGOSSIP, 공유 파일 시스템을 이용한 FILE_PING, 데이터베이스 테이블을 사용하는 JDBC_PING, 아마존 AWS 환경에서 S3(Simple Storage Service)를 사용한 S3_PING, 오픈스택의 Swift를 사용한 SWIFT_PING등 다양한 방식의 PING을 설정할 수 있다. |
표 1. JGroups의 주요 프로토콜
JBoss EAP 6에서는 ha, full-ha 프로파일에 jgroups 서브 시스템이 설정되어 있다. jgroups 서브시스템에는 멀티캐스트를 사용하는 udp 프로토콜 스택과 TCP를 사용하는 tcp 프로토콜 스택이 정의되어 있고, 기본적으로 udp 프로토콜을 사용하도록 설정되어 있다. 아래의 내용을 살펴보면, 방금 설명한 FD, FD_SOCK, PING등 여러 가지 복잡한 값들이 설정되어 있다. 대부분 기본값을 그대로 사용하면 된다.
<subsystem xmlns="urn:jboss:domain:jgroups:1.1" default-stack="udp">
<stack name="udp">
<transport type="UDP" socket-binding="jgroups-udp"/>
<protocol type="PING"/>
<protocol type="MERGE3"/>
<protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
<protocol type="FD"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK"/>
<protocol type="UNICAST2"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="UFC"/>
<protocol type="MFC"/>
<protocol type="FRAG2"/>
<protocol type="RSVP"/>
</stack>
<stack name="tcp">
<transport type="TCP" socket-binding="jgroups-tcp"/>
<protocol type="MPING" socket-binding="jgroups-mping"/>
<protocol type="MERGE2"/>
<protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
<protocol type="FD"/>
<protocol type="VERIFY_SUSPECT"/>
<protocol type="pbcast.NAKACK"/>
<protocol type="UNICAST2"/>
<protocol type="pbcast.STABLE"/>
<protocol type="pbcast.GMS"/>
<protocol type="UFC"/>
<protocol type="MFC"/>
<protocol type="FRAG2"/>
<protocol type="RSVP"/>
</stack>
</subsystem>