spring로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

spring로 검색한 결과
등록일:2008-03-16 15:49:01
작성자:
제목:[프레임워크 전략 ②] 프레임워크 이머전스의 미학


이머전스란 전체는 부분의 합보다 크게 되는 현상을 말한다. 혼자서는 도저히 발휘할 수 없는 개체들이 하나의 프레임워크에 융합되어 거대한, 새로운 힘을 만드는 것이다. 하지만 n개의 개체들이 모여 n개를 초과하는 무엇을 만들기란 어려운 일이다. 그럼에도 불구하고 JBoss를 비롯한 우수한 프레임워크들은 이러한 기적을 이뤄내고 있다. 특집 2부를 통해 JBoss와 JBoss를 이용하는 애플리케이션이 이머전스 되는 모습을 분석해 보자.

이제 개발자 혼자서 프로그램의 처음과 끝을 모두 개발하는 시대는 지났다. 시스템 라이브러리나 프로그래밍 언어에서 지원하는 라이브러리를 제외하더라도, 우리는 각종 코드 샘플, 유틸리티 라이브러리, 미들웨어 서비스, 프레임워크 등을 이용하여 애플리케이션을 개발한다.

어차피 소프트웨어 개발의 철학은 재사용 가능한 것들을 이용하여 그 이상의 부가가치를 창조하는 작업이다. 따라서 이미 개발된 품질 좋은 프로덕트들을 이용함으로써 생산성을 높이는 일은 개발자의 숙명이 된 셈이다.

사실 필자는 이 ‘숙명’이 불편하다. 기술적으로 매력적인 부분들은 이미 오픈소스나 상용 솔루션으로 제공되고 있는 탓이다. 또한 이미 만들어진 프로덕트를 ‘소비’하여 개발해야 하는 비즈니스 애플리케이션 일변도의 우리나라 개발 지형이 개발자가 기술적으로 더 연구 개발 할 기회를 박탈하기 때문이다.

원천 기술의 생산은 외국에서하고 우리나라는 단지 그 기술을 소비와 가공을 통해 진행하기만 하는 프로젝트 환경이 불편하다. 결과적으로 한국에서 대부분의 프로젝트에는 기술의 심층을 잘 이해하는 엔지니어보다 이미 만들어진 라이브러리를 잘 이해하고 다룰 수 있는, 즉 생산력을 가진 개발자를 요구하고 있다.

이런 문제를 해결할 대안으로 오픈소스 개발을 통해 기술적인 욕구와 연구를 해야 한다는 것이 필자의 소견이다.

  프레임워크의 이머전스한 측면

요즘 필자는 프레임워크를 볼 때 이머전스(창발)란 개념이 떠오른다. 앞서 설명한 바와 같이 프레임워크를 이용함으로써 더욱 완성된, 새로운 부가가치의 애플리케이션을 창조할 수 있기 때문이다. 이것은 이머전스에서 ‘전체는 부분의 합보다 크다’는 명제에 충실하다.

프레임워크를 잘 활용하면 1+1의 결과가 2를 초과하는 효과를 낳는다. 또한 JBoss나 스프링(spring), 하이버네이트(Hibernate) 같은 큰 프레임워크의 내부를 보면 이들도 다른 프레임워크들을 조합하여 이머전스 되는 현상을 확인할 수 있다.

혼자서는 도저히 발휘할 수 없는 에너지들이 하나의 프레임워크에 융합되어 거대한, 새로운 힘을 만들어 내는 것이다. 이 개념은 사실은 그다지 생경한 것도 아니다.

웹 2.0에서의 매쉬업(mash-up)이나 시너지 효과도 같은 측면을 갖고 있다. ‘애플리케이션+프레임워크’의 관계에선 두 개 이상의 조합이 가능하지만, 프레임워크 내부에선 그것과 비교할 수 없을 만큼의 많은 서브 프레임워크들의 조합으로 구성된다. 즉, 그 만큼의 상호작용으로 이머전스가 이뤄진다는 것이다.

실제로 이런 프레임워크를 다운로드 받아 lib 폴더를 열었을 때 상당히 많은 jar 파일들을 볼 수 있을 것이다. 이머전스란 구성 요소에 없는 특성이나 행동이 상위계층(전체구조)에서 돌연히 출연하는 현상을 말한다.

n개의 에너지들이 모인다면 n개의 에너지만 발휘하더라도 만족할 터인데 -병렬 프로세싱이나 분산 컴퓨팅을 생각해보라- n개 이상의 효과를 볼 수 있다는 것은 이들의 조합을 통해 시너지를 촉진할 수 있어야 하는 마법이 숨겨있다는 사실을 의미한다.

필자는 이 글을 통해 JBoss의 내부구조를 설명함으로서, 하위 프레임워크들이 조합되어 이머전스 되는 모양과, 프레임워크를 어떻게 사용해야 애플리케이션이 이머전스 할 수 있는지를 소개할 예정이다.

  이머전스를 위한 아키텍처 인프라 - JBoss 마이크로 커널 컨테이너

JBoss는 수많은 서브시스템(프레임워크)들을 통합하여 이머전스 된다. 서로 관계가 없는 프레임워크들이 통합되어 막강한 시너지를 창출하기 위해 통합을 위한 탄탄한 아키텍처가 필요하다. 서브 프레임워크들은 서로에 대해서, 그리고 JBoss에 통합될지 조차 모르는 상태이고 JBoss 또한 어떤 서브 프레임워크를 통합할지 모르고 있다.

J2EE 스펙이 바뀌거나 강력한 새로운 대안 프레임워크가 등장할 때 마다 그들을 포섭할 수 있어야 하는 탓이다. 이렇게 무엇을 사용할지, 무엇들이 조합될지 조차 알기 힘든 황당한 요구 사항을 위해 프레임워크나 컴포넌트 간에 철저히 결합도를 줄일 수 있는(decoupling) 유연한 아키텍처가 필요하다.

끝으로 JBoss는 WAS(Web Application Server) 자체의 한계인 ‘무거움(heavy weight)’을 극복해야 한다. 따라서 JBoss 기동 당시 모든 서비스와 컴포넌트를 메모리에 로딩하지 않기 위해 사용되는 시점에서 동적으로 서비스가 로딩 되어야 한다. 더불어 동적으로 서로를 인식하고 호출할 수 있어야 한다. 이렇게 이머전스를 위한 아키텍처 니즈를 정리하면 다음과 같다.

• JBoss 아키텍처는 여러 서비스, 프레임워크, 컴포넌트들이 잘 취합될 수 있는 강건성을 담보해야 한다.
• 그 것들의 결합도가 극도로 낮아야 한다.
• 가벼움(light weight)을 위해 런타임 시에 각 구성요소들이 plug & play될 수 있도록 동적 로딩, 동적 바인딩이 보장되어야 한다.

JBoss는 이런 저런 니즈를 만족하기 위해 마이크로 커널 아키텍처를 선택했다. 마이크로 커널은 최소한의 핵심적 기능만을 갖고 있다. 또한 프레임워크가 사용하는 서비스(혹은 서브 프레임워크)들의 컨테이너 역할을 하고 있다(<그림 1> 참조).

즉, 마이크로 커널은 이들의 라이프 사이클을 관리하고 이들 간의 동적인 관계를 맺어주기 위한 버스 역할을 한다. 가령 jBPM이 하이버네이트를 사용할 경우 이 둘 간의 관계가 하드 코딩된 강결합으로 이루어지는 것이 아니라 마이크로 커널을 통해 저장하는 ‘어떤 서비스(실제로는 하이버네이트)’에게 요청한다.

무엇보다 동적으로 구성 요소들이 추가(등록), 제거, 재등록, 바인딩(호출)되어 경량의 메모리를 사용할 수 있다.

<그림 1> JEMS : JBoss Enterprise Middleware Suite

마이크로 커널 아키텍처는 말 그대로 아키텍처일 뿐이다. 다시 말해 실체가 없는 개념적인 ‘전략’이다. 마이크로 커널을 무엇으로 어떻게 구현해야 할까? JBoss가 선택한 실체적인 ‘전술’은 JMX(Java Management Extensions)를 이용하는 것이다.

마이크로 커널은 MBean 서버가 되고 그것에 등록, 관리되는 서브 프레임워크들은 MBean 클라이언트가 된다. JMX를 사용함으로써 JMX의 장점들이 마이크로 커널에 그대로 적용된다. 따라서 마이크로 커널에 등록된 서브 프레임워크들은 이제 JMX 스펙에 의해 실시간으로 모니터링과 컨트롤 할 수 있다.

<그림 2> JBoss 마이크로 커널 아키텍처

  이머전스를 위한 아교(Glue) - JBoss AOP

독자가 JavaEE 서버의 디자이너라고 생각해보자. 수많은 서비스와 컴포넌트를 조합하고 일부는 변경되며 또 일부는 제거되어야 한다. 왜냐하면 JavaEE 스펙은 매우 급진적으로 변했기 때문이다. 또한 서비스와 서비스, 컴포넌트와 서비스, 컴포넌트와 컴포넌트 사이의 관계들을 동적으로 관리하기란 만만한 작업이 아니다.

JavaEE 서버는 내부적 변경이 유연해야 하는 시스템이어야 한다. 이런 목적을 위해 마이크로 커널이란 토대가 만들어졌다. 하지만 JBoss는 오픈소스 프레임워크들의 집합체이다. 각각의 오픈소스들은 애초에 JBoss를 겨냥하여 만들어지지 않았기 때문에 등록된 서비스들을 유연하고 동적이며 단순하게 연결시킬 수 있는 ‘수단’이 필요하다.

이번에는 독자가 JavaEE 서버의 애플리케이션 개발자라고 생각해보자. 기존 JavaEE 방식의 컴포넌트가 서비스를 등록/삭제/변경하는 방식은 상당히 불편하다. 표준에 의해 지난하고도 장황한 설정과 연결 작업들을 해줘야 하는 탓이다. 어떤 경우는 비즈니스 로직 코드량보다 보안, 트랜잭션, 로깅 등의 서비스를 사용하기 위한 코드 량이 더 많은 경우가 있다.

하지만 비즈니스 컴포넌트의 역할은 비즈니스 로직을 수행하기 위한 목적에 충실하기만 하면 된다. 반면 JavaEE 컴포넌트들은 본래의 역할과 무관한 서비스를 다루는 역할들을 많이 포함하고 있다. 비즈니스 컴포넌트와 JavaEE 서비스들을 유연하게 분리시킬 수 있는 ‘수단’이 필요하다.

마이크로 커널  


본래 마이크로 커널은 운영체제 분야에서 탄생됐다. 윈도우 NT 시스템은 가장 유명한 마이크로 커널 시스템이다. NT 커널 이전의 윈도우 시스템은 단일체(Monolithic) 시스템이라고 한다. 단일체 시스템에서는 모든 OS 기능(모듈)들이 커널에 집약되어 있고 이 모듈들은 모두 커널 모드에서 실행된다. 따라서 한 모듈의 변경(상태)은 그 모듈과 전혀 무관한 다른 모듈에게도 영향을 끼친다.

윈도우 98에서 IP 설정을 바꾸면 시스템을 리부팅 해야 하는 이유가 이 때문이다. 하지만 윈도우즈 NT(마이크로 커널)에서는 파일 관리자, 네트워크 관리자, 메모리 관리자 등의 모듈과 각종 디바이스 등이 커널의 일부가 아닌 커널에 등록된 서비스로 관리된다.

또한 실행도 유저모드에서 실행된다. 그래서 IP 설정이 바뀌어도 시스템을 리부팅 할 필요가 없다. 단지 네트워크 관리자의 설정만 변하기 때문이다.

<그림 3> 운영체제에서의 마이크로 커널 아키텍처

이런 구조를 혼합형(Hybrid) 구조라고 부르는데 이런 아키텍처를 채택함으로써 시스템 안정성을 높일 수 있다. 더불어 통합된 인터페이스를 제공하며, 확장성과 이식성이 높아진다. 또한 모듈간의 결합도가 축소되어 한 모듈의 변경이 다른 모듈에게 영향을 끼치지 않고, 모듈 이동적으로 추가 삭제 될 수 있다는 장점도 가진다.

무엇보다 모듈 개발자에게 표준화된 인터페이스를 제공하여 개발 용이성을 제공한다는 것이 가장 큰 효과라 할 수 있을 것이다. <그림 2>와 <그림 3>을 통해 이 두 모습의 장점들을 비교해 보자.




AOP는 역할과 역할 간의 동적이고 유연하게 ‘관계’ 시키기도 하고 ‘분리’ 시키기도 하는 방식을 제공한다. 이것은 역할과 역할간의 관계를 크로스 커팅(Cross-Cutting)하여 분리시키는 방법이다. JBoss는 비즈니스 컴포넌트와 JavaEE 서비 등을 유연하게 분리시키기 위한 ‘수단’으로 AOP를 이용하고 있다.

하나의 컴포넌트(비즈니스 로직)가 실행될 때 마이크로 커널은 JBoss AOP를 이용해 컴포넌트가 필요한(선언한) 서비스들을 동적으로 조합한다. 이를테면 컴포넌트 A가 실행될 때 보안, 캐시, 트랜잭션 등의 서비스들이 하드코딩에 의해 관계되는 것이 아니라, JBoss AOP가 동적으로 컴포넌트 A를 실행시키기 전, 후, 중간에 서비스들을 호출한다.

물론 이 구조는 EJB 3.0의 어노테이션(annotation)이나 XML 설정을 통해 정의된다. 마치 코어 J2EE 패턴에서 인터셉팅 필터(Intercepting Filter)와 유사한 구조로 각 서비스들을 호출하게 된다. 이 각각의 서비스들은 MBean 서버인 마이크로 커널에 등록된 MBean들로 관리된다.

<그림 4> JBoss AOP를 통해 컴포넌트가 호출되는 구조

이처럼 유연하게 동작하면서도 동적이며 단순한 설계는 마이크로 커널의 통제 아래 JBoss AOP의 마법을 통해 이뤄진다. JBoss AOP는 JBoss에 등록된 각 개체(서비스, 컴포넌트)를 엮어주는 아교(Glue) 역할을 하는 셈이다.

JBoss AOP는 컴포넌트와 서비스 간의 관계에서만 이용되는 것이 아니라 서비스와 서비스 간의 관계에서도 똑같이 적용된다. 따라서 서비스와 서비스의 조합 또한 유연하고 동적이며 단순한 관계를 갖는다.

예시를 통해 설명한 바와 같이, 비즈니스 컴포넌트 개발자가 비즈니스 로직에 집중된 컴포넌트를 개발하고 AOP를 통해 서비스를 사용한 것처럼, 프레임워크 개발자는 서브 프레임워크 간의 관계를 AOP를 통해 유연하게 개발할 수 있다.

가령 게임 프레임워크에서 사용하는 통신, 트랜잭션, OR 맵핑, 사용자 관리 등의 프레임워크들은 채팅 프레임워크에서 그대로 사용할 수 있는 것들이다. AOP를 이용하면 상술한 서브 프레임워크들을 채팅 프레임워크에 적합하게 재조합하여 프레임워크 구축을 쉽게 해준다. 이런 이유 때문에 AOP는 프레임워크 개발자에게 더욱 요긴한 도구이다.

AOP의 비용  


필자가 실제로 프로젝트 중에 경험했던 일이다. 클래스에서 자주 사용되는 멤버 변수인 Log4J의 로거 선언이나 DTO 혹은 DB를 다루는 관용적인 코드들의 중복을 제거하고 싶었다.

하지만 이 코드들을 익스트랙트 메소드(Extract Method)나 풀업 메소드(Pull Up Method) 하기엔 적당히 비즈니스 로직 코드와 커플링 되어 있어 울며 겨자 먹기로 계속 써야만 했었다. C++ 프로젝트였다면 매크로나 템플릿을 이용해 간단히 제거할 수 있지만 자바 프로젝트였기 때문에 언어상 사용할 수 없는 방법이었다.

하지만 시간이 지날수록 반복적인 코드들은 더욱 증가했고 더 이상 견딜 수 없는 지경에 이르렀다. AOP를 사용한다면 컴파일 타임 혹은 런타임에 특정 코드들을 비즈니스 로직 코드와 조합하여 실행시킬 수 있을 것이기에 AOP의 사용을 검토했었다.

이미 정의된 어스펙트를 사용하는 것이 아니라, 어스펙트를 만들고 AOP 프레임워크가 제공하는 API를 통해 등록과 설정하는 일이 생각보다 작업량이 많았다. 무엇보다 필자를 갈등하게 했던 부분은 혹시 AOP를 모를지도 모르는 이 시스템의 인계자에게 AOP 학습을 강요하게 될 수도 있다는 점이었다.

여기서 필자는 ‘복잡도를 해결하기 위한 복잡도’에 대해서 생각하게 되었다. 분명히 필자가 설명했던 중복된 코드들은 코드의 품질을 떨어뜨린다. 반면에 이 문제를 해결하기 위해 도입한 솔루션이 갖는 복잡도에 대해서 우리는 검토하지 않는다.

커넥션 하나로 DB를 접근하는 장황한 JDBC 함수가 두 개 정도 있는데, 이 장황하고 중복된 코드들을 없애기 위해 하이버네이트를 도입하는 경우도 이에 해당할 것이다. 장황한 중복코드를 인정할 수밖에 없는 부담보다 하이버네이트를 학습하고 시스템에 추가하는(게다가 다른 사람에게 학습을 요구해야 하는) 부담이 몇 배는 더 클 것이다.

새로운 솔루션을 도입할 때 우리는 간혹 집단 최면 같은 것에 빠지곤 한다. 그 솔루션의 문제 해결 능력만 보이고 ‘그 솔루션 자체가 갖는 문제’에 대해선 관대해진다. 왜냐하면 그 솔루션의 문제 해결능력이 세련되고 훌륭하기 때문이다. 솔루션 자체가 갖는 문제(복잡도)와 솔루션이 해결하는 복잡도를 비교하여 대차대조표를 만들어보는 것은 좋은 습관이다.

그래서 필자는 그 프로젝트에서 그냥 지저분하게 살기로 결심했다. 결코 그 둘의 조합이 이머전스 될 것 같지 않았게 때문이다.




메시징 버스 - JGroups
클러스터링 구조는 클러스터링 그룹에 등록된 하나의 서버에 장애가 발생했을 때 다른 서버로 대체될 수 있어야 한다. 따라서 사용자가 서버 A에 로그인 했다면 같은 클러스터링 그룹의 다른 서버 B에서도 그 정보가 공유되어야 한다. 서버 A에 장애가 발생하면 사용자는 서버 B에게 서비스를 요청하기 때문이다.

어떻게 하면 클러스터링 상황에서 하나의 JBoss 서버에 입력된 로그인 상태를 다른 서버들과 공유할 수 있을까? 가장 손쉽고 효과적인 방법은 멀티 캐스팅을 하는 것이다. 즉, 서버 A에 사용자 홍길동이 로그인 했다면 서버 A는 클러스터링 그룹에 속한 모든 서버들에게 멀티 캐스팅하여 홍길동이 로그인한 사실을 알린다. 이 메시지를 전달 받은 모든 서버는 홍길동에 대한 세션을 만든다. 이로써 홍길동은 서버 B에서도 서버 A의 세션 정보를 그대로 사용할 수 있게 된다.

JBoss는 서로 다른 서버들이 같은 정보를 공유(또는 복제)할 수 있도록 JGroups 프레임워크를 지원한다. JGroups는 멀티 캐스팅을 통해 그룹 메시징을 제공하는 프레임워크이며 향후 JBoss의 클러스터링 기능을 수행하는데 중요한 역할을 한다. 구조는 JMS의 토픽 모델과 유사하다.

JGroups가 다른 메시징 프레임워크보다 차별화된 장점은 메시징 스택을 자유롭게 관리할 수 있다는데 있다. 그룹에 소속된 하나의 서버가 다른 서버에게 메시지를 멀티 캐스팅할 때 여러 가지 추가적인 처리들이 필요할 수 있다.

JGroups는 이런 추가적인 처리 기능을 스택이란 개념으로 관리하여 마치 TCP/IP 스택처럼 메시지를 전송할 때 각각의 처리들을 수행하게 한 뒤에 메시지를 전송한다.

가령 서버가 여러 메시지들을 전송할 때 유독 하나의 메시지 사이즈가 너무 커서 그 메시지를 전송하기 위해 많은 시간이 걸리는 경우가 있다. 이 큰 메시지 때문에 긴급한 다른 메시지가 대기되어 늦게 전송되는 문제가 발생하곤 한다. 이때 큰 메시지를 토막 내어 하나씩 전송하여 다른 메시지 전송 기회를 보장하는 기법이 필요하다.

JGroups에서는 이런 ‘토막 내기(Fragmentation)’ 기능을 제공하기 위해 설정에서 프래그멘테이션 스택을 추가하여 자동으로 메시지를 분할 전송할 수 있도록 지원하고 있다.

또한, 멀티 캐스팅은 본질적으로 UDP 프로토콜을 사용하기 때문에 메시지 순서를 보장하지 않는다. 하지만 대부분의 서버-클라이언트 환경에서는 기본적으로 메시지 순서의 보장을 요구한다.

이때 오더링 스택을 추가하면 메시지 송/수신의 순서를 보장한다. 또한 SSL 통신을 원할 경우 보안 스택을, 플로우 컨트롤을 제공하기 위해 플로우 컨트롤 스택을 추가하면 메시지 송수신 전에 각각의 처리들이 수행된다.

<그림 5> JGroups 구조

<그림 4>와 <그림 5>는 매우 유사한 모양을 갖는다. 컴포넌트를 호출하기 전에 JBoss AOP가 어노테이션으로 선언된 서비스들을 호출하는 것처럼, JGroups는 메시지를 송수신하기 전에 각 스택을 통과하여 메시지에 대한 처리를 담당하게 한다. 더 놀라운 것은 모양만 유사한 것이 아니라 스택을 관리하는 방법 또한 같다는 사실이다.

따라서 각 스택들은 AOP의 선언에 의해 동적으로 추가, 삭제하거나 정렬하게 된다.

<표 1>은 JGroups가 제공하는 유용한 스택 중 일부이다. 여기에는 한글처리 등의 필요한 스택을 사용자가 직접 제작하여 추가할 수도 있다.



JBoss는 JGroups를 통해 클러스터링 그룹 안에 다른 서버들과 SFSB(Stateful Session Bean)의 상태를 공유한다. 앞서 설명한 예에서처럼 사용자의 로그인 정보, 주문도서 목록 정보 등의 세션 정보를 다른 서버에게 복제하여, 하나의 서버에 장애가 발생할 경우 다른 서버가 같은 상태로 서비스 할 수 있다.

이와 같이 JGroups는 JBoss 서버들 사이에 메시지 버스 역할을 담당하며 상당히 유용한 클러스터링 기반 구조를 제공한다.

물론, JGroups는 자체만으로도 사용하기 유용한 그룹 메시징 프레임워크이다. 가령 채팅 프로그램을 만든다고 가정하자. 이때 각 채팅방을 하나의 그룹으로 정의하고 그 그룹에 등록된 사용자에게 메시지를 멀티 캐스팅할 때 단순한 코딩으로 복잡한 처리를 수행하게 한다. <리스트 1>을 살펴보자.

기존의 네트워크 프로그램에서 상상하기 힘들 정도로 압축된 코드 량으로 강력한 기능을 제공한 다는 사실을 확인할 수 있다.


 <리스트 1> JGrups의 코드 샘플



JBossCache
JGroups가 네이티브한 그룹 메시징 통신 프레임워크라면 JBossCache는 복제되는 자료 구조를 제공함으로써 서버들 간의 동일한 메모리 정보를 갖는 캐시를 유지하게 한다. 사실 본질적으로 별로 다를 바 없는 원리이지만 이 둘 간의 차이는 상당히 크다. JGroups에 있어서 정보를 다른 서버로 복제한다는 의미는 메시지를 send() 한다는 행위이다.

하지만 JBoss Cache에서의 복제 행위는 JBossCache 콜렉션에 정보를 put() 혹은 add() 하는 것이다. 단지 어떤 콜렉션에 객체를 추가했을 뿐인데 그 객체가 자동적으로 다른 서버들의 동일한 콜렉션에 put() 혹은 add() 하게 된다.

JGroups와 JBossCache의 인터페이스는 멀티 캐스트 통신 인터페이스와 이를 사용하기 편리하게 분산, 복제하거나 캐시 인터페이스만 다를 뿐이지 본질은 갖지 않는다고 생각할 수 도 있을 것이다. 사실은 JBossCache는 내부적으로 JGroups를 이용한다. 따라서 JGroups의 강력한 스택 설정을 그대로 이용할 수 있다.

하지만 JBossCache는 JGroups를 이용한 유틸리티 수준의 라이브러리 기능만 제공하는 것이 아니다. JGroups에서도 이런 목적을 위해 DistributedHashMap 등의 유틸리티 클래스를 제공한다. JBossCache는 세 가지 종류의 캐시 모델을 제공한다.

1) 로컬 캐시 : 일반 캐시 클래스와 다를 바 없다. 다른 서버로 캐시 정보를 복제 하지 않는다.
2) 동기 캐시 : 다른 서버로 캐시 정보를 동기적으로 복제한다. 따라서 서버 A에서 캐시에 put()하면 곧바로 다른 모든 서버들의 캐시에 그 정보를 put()하고 이 작업이 마친 수 원래의 put() 메소드가 리턴 된다. 일종의 트랜잭션이 보장되는 것이다.
3) 비동기 캐시 : 동기 캐시와 동일한 동작을 하지만 메시지 전송이 비동기적으로 이뤄진다. 따라서 put() 메소드가 리턴 되기 이전에 다른 모든 서버의 캐시에 그 데이터를 put()하는 것이 보장되지 않는다. 동기 캐시는 메모리의 실시간적인 동기화를 보장하지만, 속도는 비동기 캐시가 더 빠르다.

JBossCache는 POJO 레벨의 캐시를 제공한다. 만약 어떤 객체를 전송한다고 생각해보자. 객체를 전송하기 위해 그 객체는 Serializable 인터페이스를 상속 받아야 한다. 그래야 객체를 스트림으로 만들 수 있기 때문이다. 하지만 JBossCache는 POJO 객체를 put() 하더라도 JBossAOP에 의해 스트림으로 변환이 가능한 POJO로 만들어준다.

JBossCache는 실제로 입력되는 객체를 관리하기 위한 자료구조로 트리를 사용한다. 하지만 사용 편의성이 떨어지는 트리 구조의 불편함을 대폭 개선한다. 개발자에게 친근한 맵(Map)처럼 트리를 사용할 수도 있다.

<그림 6> 분산 복제에서 공유 레퍼런스의 문제점

캐시에 등록된 객체들을 다른 서버의 캐시로 분산 복제할 때에는 몇 가지 난감한 문제들이 발생한다. <그림 6>에서 보는 바와 같이 Joe, Mary 객체는 실제로 동일한 Addr 객체를 참조한다. 이런 상황에서 Joe와 Mary를 각각 JBossCache에 put() 하게 되면 각각의 객체들이 다른 서버들에 복제되어 Addr는 실제로 두 개의 인스턴스로 복제된다.

더 위험한 것은 Joe의 Addr를 변경했을 경우 원본 서버에서는 변경된 Addr을 Joe, Mary가 참조하지만 다른 서버들은 변경된 Addr의 정보를 공유하지 못 하는 것이다. 따라서 다시 Joe와 Mary를 put() 해줘야 동일한 캐시를 유지할 수 있다. 이때 Joe만 put()하면 다른 서버들의 Mary는 Joe에 다른 Addr의 값을 가지게 된다.

이러한 복제 방식을 coarse-grained 복제방식이라 하는데 네트워크 사용량과 메모리 사용량, 에러 가능성이 높은 단점을 갖고 있다. 그래서 JBossCache는 TreeCacheAOP 클래스를 제공한다.

TreeCacheAOP는 <그림 6>과 같은 상황에서 한 개의 Addr만 복제한다. 이는 AOP에 의해 Addr가 레퍼런스 되는 Joe와 Mary 객체를 알고 있기 때문이다. 이때 Joe가 Addr을 변경했을 때 변경된 Addr만 복제되어 네트워크 사용량과 메모리 사용량까지 최소화 시키는 경이로운 마법도 함께 이뤄진다.

이런 방식을 파인그레인드(fine-grained) 복제방식이라고 부른다. TreeCacheAOP에 등록된 객체는 setter 메소드나 ‘=’ 오퍼레이터에 의해 멤버 변수를 변경했을 때 AOP의 인터셉터가 동작하여 곧바로 다른 서버로 변경된 정보만 복제한다. 이것은 Joe의 멤버 변수인 Addr의 멤버 변수를 변경할 때도 마찬가지다.

결과적으로 아무리 객체의 내부 구조가 컴포지션이나 상속으로 복잡해져 있다 하더라도 정교하고 정확한 복제 메커니즘을 이용할 수 있다.

실제로 JBoss는 엔티티 빈(Entity Bean) 프레임워크로 사용하고 있는 하이버네이트에서 TreeCacheAOP를 사용한다. 하이버네이트는 read()할 때 DB를 매번 select 하지 않기 위해 내부적으로 캐시를 제공한다.

하지만 이 서버가 클러스터링으로 엮여있다면 이 캐시 정보를 공유할 때 효율성을 높일 수 있는 경우가 있다. 이때 하이버네이트는 분산 캐시 메커니즘으로 TreeCacheAOP를 사용한다.

  클러스터링 커뮤니케이션

클러스터링이란 하나의 서비스를 위해 여러 개의 서버를 사용하는 것을 말한다. 이렇게 하면 부하를 분산시켜 서버의 가용성을 높일(Load Balancing) 수 있다. 또, 일부 서버에 장애가 발생하더라도 나머지 서버들을 통해 서비스가 정상적으로 운영할 수 있도록 하는 것(무정지형, Fault Tolerant)도 주된 목적이다.

이렇게 여러 서버들이 동질의 서비스를 할 수 있으려면 먼저 각 서버들이 서로를 알고 있어야 하며 서로의 메모리 상태를 동일하게 유지해야 한다. 이렇게 서비스되는 서버들의 집합을 클러스터링 그룹이라고 하는데 JBoss에서는 파티션이란 개념으로 표현한다.

서비스 운영 중 하나의 서버에 문제가 생겼을 때 다른 서버가 서비스를 대체할 수 있으려면 어떤 처리들을 해 두어야 할까? 보통 자금이 충분하다면 L4 스위치를 사용한다. 하지만 꼭 스위치를 사용하지 않더라도 소프트웨어적으로 해결할 수 있다. JBoss에서는 인텔리전트 스텁(intelligent stub)을 사용하여 이 문제를 해결한다.

우리는 보통 EJB 호출을 위해 JNDI를 통해 EJB의 스텁을 얻어온다. JBoss가 스텁을 만들 때 하나의 EJB에 대한 정보를 얻어오는데 인텔리전트 스텁은 파티션에 속한 동종의 다른 EJB 호출 정보도 같이 포함하고 있다. 그래서 부하분산이나 무정지형 처리를 할 때 다른 서버의 동일한 EJB를 호출한다.

<그림 7> JBoss Fault Tolerant

물론 호출된 EJB는 JGroups에 의해 장애가 생긴 서버의 EJB와 동일한 상태를 유지한다. 부하 분산의 경우를 생각해보자. 부하를 분산시키려면 파티션에 등록된 서버들이 균일하게 서비스 하도록 해야 한다(하나의 서비스에만 부하가 많이 걸린다면 부하 분산이 되지 않는 것이다).

인텔리전트 스텁은 부하 분산의 형평성을 보장하기 위해 다음에 호출할 서비스를 선택하기 위한 정책을 제공한다. 다음은 JBoss 인텔리전트 스텁의 부하 분산 정책이다.

• RoundRobin : 순서 순환 방식
• FirstAvailable : 파티션 내의 모든 서버들에게 핑(ping)을 날려 최초로 도달하는 서버를 선택하는 방식이다. 이때 선택된 서버는 다른 클라이언트들에게 공유되지 않는다. 즉, 다른 클라이언트의 스텁 리스트에서 제거된다.
• FirstAvailableIdenticalAllProxies : FirstAvailable방식과 동일하나 선택된 서버는 다른 클라이언트도 이용할 수 있다.

Farming
클러스터링 환경에서 애플리케이션을 배포 하는 상황을 상상해보자. 하나의 파티션에 A, B, C 서버가 있을 때 A, B, C 순서로 애플리케이션을 배포 한다면 A부터 C까지 모두 배포할 동안 모든 서버가 동일한 서비스를 제공할 수 없다. 왜냐하면 애플리케이션이 A, B, C 순서로 서비스되기 때문이다.

그렇다고 서버를 모두 종료하고 배포한 후 다시 기동하는 무모한 시도를 할 수는 없는 일이다. 무중단 운영이 보장되어야 하는 서비스의 경우에는 특히 더 그렇다. 아무튼 이런 탓에 n개의 서버가 있을 경우 애플리케이션도 n번 배포해야 하는 부담이 생긴다.

JBoss는 이런 상황을 위해 Farming이란 기술을 제공한다. JBoss의 /farm 디렉토리에 패키징된 애플리케이션 파일을 복사하면 JBoss는 자동적으로 파티션 내의 다른 서버들에게 배포한 패키지 파일을 복제해준다.

HA-JNDI Lookup
지금까지 JGroups를 기반으로 한 JBoss의 복제에 대해서 장황한 소개를 했다. 클러스터링을 제공하기 위한 기본 기능이 모든 서버의 상태를 동기화 하는 것이기에 동기화의 수단으로 복제를 사용한다. 하지만 JBoss가 복제하지 않는 것이 있다. 바로 JNDI 서비스다. 하이버네이트 캐시와 JNDI 트리에 대해서 생각해보자.

하이버네이트 캐시에 저장된 객체는 DB에 insert나 update를 많이 할수록 상태 변화가 자주 이뤄진다. 그만큼 복제가 이뤄진다. 하지만 JNDI의 경우 일단 서버가 기동되고 약간의 시간이 흐르면 JNDI 트리에 등록된 객체는 그다지 상태를 변경하지 않는다. 아마도 이런 연유에서 굳이 잘 변경되지 않는 정보들은 복제하지 않는 듯하다.

하지만 A 서버 내의 클라이언트가 B 서버 내의 EJB를 호출할 때 A 서버의 클라이언트는 B 서버의 EJB의 스텁을 가져와야 한다. 이때 A 서버는 B 서버의 JNDI를 통해 B 서버의 EJB 스텁을 가져올 수 있다.

클러스터링 하에서 JBoss는 HA-JNDI((High Available-JNDI)라는 별도의 JNDI 서비스를 제공한다. HA-JNDI는 클러스터링 내의 각 서버에게 자신이 찾고자 하는 객체의 스텁을 검색하는 기능을 담당한다. 역으로 HA-JNDI는 다른 서버의 HA-JNDI가 자신의 JNDI를 검색할 때 검색을 대행해주는 역할을 한다.

<그림 8> JBoss 빌딩 블록

<그림 8>에서 클라이언트가 노드 3에 있는 EJB의 스텁을 얻으려고 한다. 처음에 클라이언트는 노드 2의 HA-JNDI에게 Lookup을 시도한다. 노드 2의 HA-JNDI는 노드 2의 JNDI에게 Lookup을 하고 찾지 못 한다면, 노드 2의 HA-JNDI는 다시 노드 1의 HA-JNDI에게 Lookup을 시도한다.

이렇게 클라이언트가 요청한 EJB의 스텁을 찾을 때까지 파티션 내의 전 노드를 순회하여 Lookup을 시도한다. 필자는 이 모델을 볼 때마다 JGroups를 통해 한꺼번에 전체 노드에게 Lookup을 했으면 더욱 효율적이었을 것 같다는 생각이 든다.

사실 클러스터링은 개념적인 부분도 많고 내용도 방대해서 개발자들이 그 원리를 더욱 궁금해 하는 부분이다. 하지만 어떠한 클러스터링이던 사람이 개발한 것이고, 막상 그 원리를 알고 나면 그다지 신기할 것도 없다. 프레임워크의 인터페이스를 이해하는 것과 프레임워크의 내부를 이해하는 것도 마찬가지다.

요즘 같은 ‘오픈소스 과잉’의 시대에는 공짜로 오픈소스 개발자의 지식을 ‘복제’하기 좋은 환경인 듯하다(물론 오픈소스 프레임워크의 내부 구조에 대한 문서는 좀처럼 구하기 어렵다). 아무튼 프레임워크는 그 내부를 알수록 재미있고, 제대로 사용할 수 있으며 무엇보다 개발자의 실력을 향상시킨다는 것은 틀림없는 사실이다.

프레임워크의 인터페이스를 이해하는 것과 프레임워크의 내부를 이해하는 것의 차이는 결국 ‘알기 위한 용기’의 차이 정도 밖에 되지 않을 것이다.

  프레임워크 활용 전술

ACE/TAO 및 여러 미들웨어와 프레임워크를 아키텍처한 더글라스 슈미츠 교수는 프레임워크를 사용하는데 있어 상당한 시간을 투자해야 한다고 주장한다. 보통 하나의 프레임워크를 프로젝트에 도입하면 첫 번째 프로젝트에서는 사용법을 익히는 수준으로 프레임워크를 이용하게 된다.

두 번째 프로젝트에서는 베스트 프랙티스와 사용 가이드라인을 발견하게 되고 세 번째 프로젝트에서야 비로소 프레임워크를 능숙히 사용할 수 있다고 한다. 개인차가 있겠지만 프레임워크를 능숙하게 사용할 수 있으려면 1년에 가까운 학습 기간이 필요하다고 한다.

하지만 우리가 경험하는 대부분의 프로젝트에서는 책이나 매뉴얼을 정독하고, 몇 개의 샘플코드를 따라 해보고, 약간의 안전성을 테스트 한 후 곧바로 프레임워크를 도입하는 경우가 허다하다(사실 필자의 경험에 따르면 이보다 훨씬 적은 노력을 희생한다).

물론 환경적 제약에 의해서 충분한 시간을 투자할 수 없었지만 습관적으로 프레임워크 학습에 대한 투자를 게을리 하는 경향이 있다. 이렇게 사용된 프레임워크는 처음에 발견하지 못한 문제로 프로젝트 중, 후반에 골칫거리로 전락하는 경우가 많다. 프레임워크를 충분히 이해하지 않고 프로젝트에 적용한 탓이다.

프레임워크에 대한 충분한 이해가 없으니 최적화된 사용을 할 수 없을뿐더러, 프레임워크 규모에 비해 너무 적은 일부 기능만 사용하는 경우도 허다하다. 프레임워크를 도입하기 전에 충분한 시간 투자가 필요하다.

JBoss는 그 자체로도 훌륭한 프레임워크지만, JBoss 내부의 서브 프레임워크도 효용가치가 충분히 높다. JBossCache, JGroups, JBoss Remoting 등 개개의 프레임워크로도 상당히 강력한 기능을 하는 것들이 JBoss 프로젝트에는 많이 있다. 필자는 JBoss의 내부 구조를 공부하면서 JBoss 보다 JBoss 내부의 개개의 프레임워크에 더 큰 매력을 느꼈다.

JBoss는 이런 개별 프레임워크들이 이머전스 되어 빌딩 블록의 집합체가 된 것이다. 이 모델은 JBoss를 사용하는 애플리케이션 개발자에게도 의미심장한 메시지를 전달한다. 우리도 JBoss를 이용하여 더 완성된 부가가치를 갖는 무엇을 만들 수 있기 때문이다.

단지 우리가 염두해야 할 것은 사용할 프레임워크를 충분히 이해하여 그 프레임워크와 나의 코드를 조합하여 창발을 만들어 내려는 노력을 잊지 않는 것이다. @

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.