비동기식, 이벤트 중심 아키텍처를 현대적인 웹 애플리케이션의 문제에 맞춰 변화시키기 ![]() | ![]() |
![]() |
난이도 : 중급 Constantine Plotnikov, Chief Engineer, Axmor 2008 년 2 월 26 일 표 준 Java™ Platform, Enterprise Edition 5 (Java EE) — 기반 방식으로 개발된 Web 2.0 애플리케이션들은 심각한 성능 및 확장성 문제에 직면해 있습니다. 이유는, Java EE 플랫폼의 디자인 — 특히, 동기식 API의 사용-을 지탱하고 있는 많은 원리들이 Web 2.0 솔루션의 요구 사항에 맞지 않기 때문입니다. 이 글에서는 Java EE와 Web 2.0 방식 간 차이에 대해 설명하고, 비동기식 디자인의 효과를 분석하며, 자바 플랫폼에 비동기식 웹 애플리케이션을 개발할 수 있는 몇 가지 솔루션을 평가해 봅니다. 많은 성공적인 엔터프라이즈 애플리케이션들이 Java EE 플랫폼을 사용하여 생성되었다. 하지만, Java EE의 디자인 원리는 Web 2.0 세대의 애플리케이션들을 효율적으로 지원하지 못한다. Java EE와 Web 2.0 원리들간 불일치에 대한 깊은 이해만이 이러한 불일치를 어느 정도 해결할 수 있는 방식과 툴을 사용하는 것에 대한 결정을 내릴 수 있게 한다. 이 글은 왜 Web 2.0과 표준 Java EE 플랫폼이 어긋난 결합이 될 수 밖에 없는지를 설명하고, 비동기식, 이벤트 중심 아키텍처가 Web 2.0 애플리케이션에 더 적합한 이유를 설명한다. 또한 자바 플랫폼에 비동기식 디자인을 실행함으로써 보다 Web 2.0적인 것으로 만드는 프레임웍과 API를 설명한다.
Java EE 플랫폼은 business-to-consumer (B2C)와 business-to-business (B2B) 애플리케이션들의 개발을 지원하기 위해 만들어졌다. 기업들은 인터넷을 발견하고, 이것을 사용하여 자신들의 파트너와 클라이언트와의 기존 비즈니스 프로세스를 향상시키기 시작했다. 이러한 애플리케이션들은 기존의 엔터프라이즈 통합 시스템(EIS)과 종종 상호 작동했다. Java EE 서버의 성능과 확장성을 평가하는 가장 일반적인 벤치마크에 대한 유스 케이스- ECperf 1.1, SPECjbb2005, SPECjAppServer2004 (참고자료)-가 B2C, B2B, EIS에 대한 집중을 반영한다. Java EE 아키텍처의 확장성에 대한 여러 가지 가정들이 이 벤치마크에 반영된다:
이러한 가정은 Java EE API의 구현 원리가 된다:
Web 1.0에서, 이러한 원리들은 매우 잘 적용되었다. 일부 고유 애플리케이션들은 이러한 바운더리 내에서 잘 맞게 설계될 수 있었다. 하지만, Web 2.0 시대를 효율적으로 지원하지 못한다.
Web 2.0 애플리케이션들은 Java EE를 어렵게 만드는 고유의 요구 사항들을 갖고 있다. 그 중 한 가지는, Web 2.0 애플리케이션이 Web 1.0 시절의 애플리케이션들보다 자주 서비스 API를 통해 또 다른 것을 사용한다는 점이다. Web 2.0 애플리케이션의 중요한 요소는 consumer-to-consumer (C2C) 인터랙션이다: 애플리케이션 소유자는 콘텐트의 작은 부분만 만들어 내고, 더 큰 부분은 사용자에 의해 만들어 진다. Web 2.0 정황에서, 매시업(mash-up) 애플리케이션은 서비스를 사용하고, 노출된 서비스를 SOA의 서비스 API를 통해 제공한다. (Java EE가 SOA를 만났을 때 참조)이러한 애플리케이션들은 B2C 정황에서 서비스를 소비해야 한다. 예를 들어, 매시업은 세 개의 고유 소스에서 데이터 — 날씨 정보, 교통 정보, 지도 —를 가져와야 한다. 이러한 세 개의 고유한 데이터 조각을 가져오는데 필요한 시간은 전체 요청-처리 시간에 추가된다. 데이터 소스와 서비스 API의 증가에도 불구하고, 소비자들은 여전히 높은 응답성을 지닌 애플리케이션을 기대한다. 캐싱(caching) 같은 기술은 레이턴시를 줄일 수 있지만, 모든 상황에 다 맞는 것은 아니다. 예를 들어, 지도 데이터를 저장하여 응답 시간을 줄일 수 있지만, 검색 쿼리의 결과나 실시간 교통 정보를 저장하는 것은 불가능 하거나 비현실적인 일이다. 기본적으로, 서비스 호출은 CPU 리소스의 작은 부분을 클라이언트와 서버에 할당하는 높은 레이턴시의 프로세스이다. 웹 서비스 호출 대부분의 시간은 새로운 연결을 구축하고 데이터를 전송하는데 쓰인다. 따라서, 클라이언트나 서버 측에서 성능을 향상시키는 것은 호출 기간을 줄이는데 어떤 영향도 주지 못한다. 사용자 참여를 가능케 함으로써, 애플리케이션은 활성 사용자 당 서버 요청의 수를 더 많이 갖게 되면서 Web 2.0은 또 다른 도전 과제를 낳았다. 근거는 도처에 존재한다:
이 러한 요소들은 Web 1.0 애플리케이션과 비교하여, 서버에 더 많은 트래픽과 더 많은 요청들을 일으킨다. 부하량이 많을 때, 트래픽은 제어가 어려워진다. (하지만, Ajax는 트래픽 최적화를 위한 더 많은 기회도 제공한다. Ajax에서 생성된 트래픽 양은 같은 유스 케이스를 지원하는 플레인 웹 애플리케이션에 의해 생성된 볼륨 보다 적다.) Web 2.0 애플리케이션들은 이전에 생성된 웹 애플리케이션보다 많은 콘텐트와 크기를 갖고 있다. Web 1.0 시절에, 콘텐트는 비즈니스 엔터티가 명확히 이를 승인한 후에야 기업의 웹 사이트에 공개되었다. 기업은 디스플레이 된 텍스트의 모든 문자에 대한 제어권을 가졌다. 따라서, 예정된 콘텐트가 크기와 관련하여 인프라스트럭처 제약 조건을 위반했다면, 콘텐트는 최적화 되거나 더 작은 청크로 나뉘었다. Web 2.0 사이트는 기본적으로 콘텐트 크기나 생성을 제한하지 않는다. Web 2.0 콘텐트의 많은 부분은 사용자와 커뮤니티에 의해 생성된다. 조직과 기업들은 기여와 콘텐트 생성을 실행할 수 있는 툴만 제공하면 된다. 콘텐트는 이미지, 오디오, 비디오를 많이 사용하게 되면서 크기 면에서도 증가하게 되었다. 클 라이언트에서 서버로 새로운 연결을 구축하는 데에는 상당히 많은 시간이 걸린다. 여러 인터랙션이 기대되지만, 클라이언트/서버 통신이 구축되고 이를 재사용 하는 것이 더 효율적이다. 일관된 연결 역시 클라이언트 공지를 보내는 데에도 유용하다. 하지만, Web 2.0 애플리케이션의 클라이언트는 종종 방화벽 뒤에 있으며, 서버에서 클라이언트로 직접적인 연결을 구축하는 것이 어렵거나 불가능하다. Ajax 애플리케이션은 요청을 보내서 특정 이벤트를 폴링(poll)해야 한다. 폴링 요청의 수를 줄이기 위해, Ajax 애플리케이션은 Comet 패턴을 사용한다(참고자료): 서버는 응답을 보내기 전에 연결을 오픈 된 상태로 지속시키면서 이벤트가 발생할 때까지 기다린다. SIP, BEEP, XMPP 같은 Peer-to-peer 메시징 프로토콜은 연결 영속성을 점점 더 많이 사용한다. 비디오 스트리밍은 이러한 연결 영속성의 혜택을 누리고 있다. Web 2.0 애플리케이션이 많은 사용자를 확보할 수 있었다는 점은 일부 사이트가 "Slashdot 효과" — 유명한 블로그, 뉴스 사이트, 소셜-네트워킹 사이트에 사이트가 언급될 때 발생하는 엄청난 트래픽(참고자료)에 취약하다는 것을 증명한다. 모든 웹 사이트는 일반 부하량 보다 더 큰 트래픽을 핸들할 준비를 해야 한다. 그리고, 이 같은 높은 부하량 때문에 성능이 강등될 경우에는 특히 주의해야 한다.
연산의 레이턴시는 연산 쓰루풋보다 Java EE 애플리케이션에 더욱 영향을 미친다. 애플리케이션이 사용하는 서비스가 거대한 연산량을 핸들할 수 있더라도, 레이턴시가 같거나 증가하는 동안에만 그렇게 한다. 현재 Java EE API는 API 디자인에 함축된 레이턴시에 대한 정의를 위반하기 때문에 이러한 상황을 잘 핸들 하지 못한다. 포럼 또는 블로그에 많은 페이지를 제공하는 경우, 동기식 API를 사용할 때 프로세싱 쓰레드를 차지한다. 각 페이지가 공급 받는데 1초가 걸린다면(많은 페이지를 가질 수 있는 LiveJournal 같은 애플리케이션을 생각해 보자.), 쓰레드 풀에 100개의 쓰레드가 있다면, 초당 100 페이지 이상을 제공할 수 없다. 허용할 수 없는 비율이다. 애플리케이션-서버 성능이 풀에 있는 쓰레드의 수가 늘어나는 만큼 강등하기 시작하기 때문에 쓰레드 풀에 쓰레드의 수를 증가시키는 것은 제한된 효과만 있다. Java EE 아키텍처는 Java EE 동기식 API가 싱글 쓰레드를 지속적으로 사용하기 때문에 SIP, BEEP, XMPP 같은 메시징 프로토콜을 활용할 수 없다. 애플리케이션 서버는 제한된 쓰레드 풀을 사용하기 때문에, 쓰레드를 지속적으로 사용하면 애플리케이션 서버는 이러한 프로토콜을 사용하여 메시지를 송수신 하는 동안 다른 요청들을 핸들링 할 수 없다. 이러한 프로토콜로 보내진 메시지들이 반드시 짧은 것은 아니고(특히 BEEP의 경우), 이러한 메시지들을 생성하려면 웹 서비스나 다른 수단을 사용하여, 다른 조직에 전개된 리소스에 액세스 할 수 있다. 또한, BEEP와 Stream Control Transmission Protocol (SCTP) 같은 전송 프로토콜은 싱글 TCP/IP 연결을 통한 여러 개의 동시적인 논리적 연결을 가질 수 있고, 이는 쓰레드 관리 문제를 더욱 심각하게 한다. 스트리밍 시나리오를 구현하기 위해, 웹 애플리케이션은 표준 Java EE 패턴과 API를 포기해야 했다. 결국, Java EE 애플리케이션 서버는 P2P 애플리케이션을 실행하거나 비디오를 스트리밍 하는데 사용되지 않는다. 커스텀 컴포넌트는 상용 비동기식 로직을 구현하기 위해 Java Connector Architecture (JCA) 커넥터를 사용하는 프로토콜을 핸들하도록 개발된다. (이 글 후반에서 알게 되겠지만, 신세대 서블릿 엔진 역시 Comet 패턴을 핸들링 하는데 일부 비표준 인터페이스도 지원한다. 하지만, 이 지원은 API와 사용 패턴의 관점에서 볼 때, 표준 서블릿 인터페이스와는 매우 다르다.) 마지막으로, 근본적인 Java EE 원리 중 하나는 네트워크 인프라스트럭처에 대한 투자로 트랜잭션 기간을 줄일 수 있다는 것이다. 라이브 비디오 피드의 경우, 네트워크 인프라스트럭처 속도의 증가는 스트림이 생성되는 동안 클라이언트로 보내지기 때문에 요청 동안에는 어떤 영향도 미치지 않는다. 네트워크 인프라스트럭처의 향상은 스트림의 수만 늘려서 많은 클라이언트를 실행하고 더 높은 해상도로 스트리밍을 실행한다.
우 리가 논의했던 문제들을 피할 수 있는 방법은 애플리케이션 디자인 동안 레이턴시를 고려하고, 비동기식, 이벤트 중심 방식으로 애플리케이션을 구현하는 것이다. 애플리케이션이 유휴 상태라면, 쓰레드 같은 유한 리소스들을 차지해서는 안된다. 비동기식 API를 사용하여, 애플리케이션은 외부 이벤트에 폴링하고, 이벤트가 도착할 때 관련 액션들을 실행한다. 일반적으로, 이와 같은 애플리케이션은 여러 이벤트 루프로 나뉘며, 각각 고유 쓰레드에 있게 된다. 비동기식, 이벤트 중심 디자인에서 확실히 얻을 수 있는 것은, 외부 서비스를 대기하는 많은 연산들은, 이들 간 어떤 데이터 의존성이 존재하지 않는 한, 병렬로 실행될 수 있다. 비동기식, 이벤트 중심 아키텍처는 병렬 연산이 전혀 일어나지 않더라도 전통적인 비동기식 디자인보다 더 나은 확장성의 이점이 있다. 비 동기식 API를 사용하는 데서 얻을 수 있는 확장성은 서블릿 프로세스의 간단한 모델을 사용하여 설명될 수 있다. (비동기식 디자인이 Web 2.0 애플리케이션의 확장성 요구 사항에 대한 해답이라는 것을 이미 알고 있었다면, 이 섹션을 과감하게 건너뛰어, Web 2.0 / Java EE 수수께끼에 대한 솔루션 논의를 참조하라.) 우리 모델에서, 서블릿 프로세스는 인커밍 요청에 대한 작업을 수행하고, 데이터베이스를 쿼리하며, 데이터베이스에서 선택된 정보를 사용하여 웹 서비스를 호출한다. 최종 응답은 웹 서비스의 응답에 기반하여 생성된다. 이 모델의 서블릿은 비교적 높은 레이턴시로 두 종류의 리소스를 사용한다. 이 리소스들은 증가하는 부하량 하에서 특징과 작동에 따라 달라진다:
서블릿 실행 시간이 표 1과 같은 단계로 나뉜다고 생각해 보자: 표 1. 서블릿 연산 타이밍 (추상 단위)
그림 1은 실행 동안 비즈니스 로직, 데이터베이스, 웹 서비스간 분배도이다: 그림 1. 실행 단계 동안 시간 분배 ![]() 이 러한 타이밍은 가독성 있는 다이어그램을 제공하기 위해 선택되었다. 실제로, 대부분의 웹 서비스는 프로세싱에 훨씬 더 많은 시간이 든다. 비즈니스 로직 자바 코드의 프로세싱 시간 보다 100에서 300배 정도 많은 웹 서비스 프로세싱 시간이 드는 것으로 보면 된다. 동기식 호출 모델에 기회를 주기 위해, 현실에서는 존재하지 않는 매개변수를 선택했는데, 이 경우, 웹 서비스는 매우 빠르거나, 애플리케이션 서버는 매우 느리거나, 둘 다이다. 두 개에 똑 같은 연결 풀 용량이 있다고 생각해 보자. 두 개의 데이터베이스 트랜잭션만이 동시에 발생할 수 있다. (실제 쓰레드와 커넥션의 수는 실제 애플리케이션 서버의 경우보다 더 크다.) 웹 서비스 호출은 같은 시간이 걸리고, 모두 병렬로 수행될 수 있다. 현실적으로, 웹 서비스 인터랙션 시간은 데이터를 주고 받는 것으로 이루어진다. 실제 작업을 수행하는 것은 웹 서비스 호출에 있어서 작은 부분이다. 이 러한 시나리오에서, 동기식과 비동기식 케이스 모두 낮은 부하량에서 똑같이 작동한다. 데이터베이스 쿼리와 웹 서비스 호출이 병렬로 발생한다면, 비동기식 케이스는 더 나은 작동을 보인다. 오버로드 상황에서 갑작스런 액세스 피크 같은 재미있는 결과가 발생한다. 아홉 개의 동시 요청을 생각해 보자. 동기식 케이스의 경우, 서블릿 엔진 쓰레드 풀은 세 개의 쓰레드를 갖고 있다. 비동기식 케이스의 경우, 우리는 한 개의 쓰레드만 사용하게 될 것이다. 두 경우 모두, 아홉 개의 모든 연결들이 도착할 때마다 수락된다. (대부분의 서블릿 엔진들에서도 마찬가지이다.) 하지만, 처음 세 개가 처리되는 동안, 다른 여섯 개의 연결의 경우 동기식의 경우 어떤 프로세싱도 발생하지 않는다. 그림 2와 3은 동기식 및 비동기식 API 케이스 모두를 모델링 하는 간단한 시뮬레이션 프로그램을 사용하여 만들어진 것이다: 그림 2. 동기식 케이스 ![]() 그 림 2의 각 직사각형은 프로세스의 한 단계를 나타낸다. 직사각형의 첫 번째 숫자는 프로세스 번호(1부터 9)이고, 두 번째 숫자는 프로세스 내 단계 번호이다. 각 프로세스에는 고유의 색깔이 칠해진다. 데이터베이스와 웹 서비스 연산은 개별 라인에 있는데, 데이터베이스 엔진과 웹 서비스 구현에 의해 실행되기 때문이다. 서블릿 엔진은 결과를 기다리는 동안 어떤 것도 수행하지 않는다. 밝은 회색 부분은 유휴(대기) 상태이다. 다이어그램 밑에 있는 다이아몬드 모양의 마커는 한 개 이상의 요청들이 이 부분에서 끝났다는 것을 나타낸다. 마커의 첫 번째 숫자는 추상 단위로 된 시간을 표현한 것이다; 괄호 안에 있는 두 번째 숫자는 이 부분에서 종료하는 요청의 수를 나타낸다. 그림 2에서, 처음 두 개의 요청이 32 포인트에서 끝났고, 마지막 요청이 104 포인트에서 끝나는 것을 알 수 있다. 이제, 데이터베이스와 웹 서비스 클라이언트 런타임이 비동기식 인터페이스를 지원한다고 가정해 보자. 모든 비동기식 서블릿은 싱글 쓰레드를 사용한다. (비동기식 인터페이스는 추가 쓰레드를 사용할 수 있다.) 그림 3은 결과이다: 그림 3. 비동기식 케이스 ![]() 그 림 3을 보면 재미있는 부분이 몇 가지 있다. 첫 번째 요청은 동기식 케이스보다 23% 느리게 끝난다. 하지만, 마지막 요청은 26% 더 빠르다. 이것은 세 배나 더 빠른 쓰레드가 사용될 때 발생한다. 요청-실행 시간은 훨씬 더 규칙적으로 분배되기 때문에, 사용자들은 보다 규칙적인 속도로 페이지를 받을 수 있다. 첫 번째와 마지막 요청에 대한 프로세시 시간차는 80%이다. 동기식 인터페이스의 경우, 225%이다. 이제, 우리가 애플리케이션과 데이터베이스 서버를 업그레이드 하여, 두 배나 더 빨라졌다고 가정해 본다. 표 2는 타이밍 결과이다. (단위는 표 1의 단위와 관련이 있다.): 표 2. 업그레이드 후 서블릿 연산 타이밍
전체적인 요청-처리 시간이 24 시간 단위인데, 이것은 원래 요청 시간의 3/4에 해당한다. 그림 4는 비즈니스 로직, 데이터베이스, 웹 서비스간 새로운 분배도이다: 그림 4. 업그레이드 후 단계들 간 시간 분배 ![]() 그림 5는 동기식 프로세싱 후의 결과를 보여준다. 전체적인 실행 시간은 25% 줄어들었다. 하지만, 이 단계의 분배 패턴은 많이 바뀌지 않았고, 서블릿 쓰레드는 대기 상태에서 훨씬 더 많은 시간을 보낸다. 그림 5. 업그레이드 후의 동기식 케이스 ![]() 그림 6은 비동기식 API를 사용한 프로세싱 후의 결과이다: 그림 6. 업그레이드 후 비동기식 케이스 ![]() 비 동기식 케이스의 결과는 매우 재미있다. 프로세싱이 데이터베이스와 애플리케이션 서버 성능 증가로 인해 더 나아지고 있다. 균등성이 향상되었고, 최악과 최상 요청 처리 시간 차이도 겨우 57%이다. 총 프로세스 시간(마지막 요청이 준비될 때)은 업그레이드 전에 원래 시간의 57%이다. 이것은 동기식 케이스에서 나온 75%에 비하면 매우 큰 향상이다. 마지막 요청(두 경우 모두 request 9)은 동기식 케이스에서 40% 이상 더 빠르고, 첫 번째 요청은 동기식에서 14% 더 느리다. 게다가, 비동기식 케이스에서, 더 많은 병렬 웹 서비스 연산을 실행할 수 있다. 서블릿 쓰레드 풀에서 쓰레드의 수가 제한되기 때문에 동기식 케이스에서는 이러한 병렬 레벨은 불가능하다. 웹 서비스가 더 많은 요청을 처리할 수 있더라도, 아직 활성화 되지 않았기 때문에 서블릿은 이들을 보낼 수 없다. 실제 테스트 결과는 비동기식 애플리케이션이 더 낳은 확장성을 보이고, 오버로드 상황을 더욱 여유롭게 처리한다. 레이턴시는 매우 어려운 문제이고, Moore의 법칙(참고자료)도 우리에게 큰 희망을 주지 않는다. 요즘의 컴퓨팅 향상으로 인해 필수 대역폭이 늘어났다. 대부분의 경우 레이턴시도 같거나, 다소 악화되었다. 이러한 이유로 개발자들은 비동기식 인터페이스를 애플리케이션 서버에 도입하게 된다. 비 동기식 시스템을 구현하는데 사용할 수 있는 많은 옵션들이 있지만, 어떤 패턴도 표준으로 확립되지 않았다. 각각의 방식은 고유의 장단점을 갖고 있고, 상황마다 다르게 작동한다. 이 글 나머지 부분은 자바 플랫폼을 사용하여, 비동기식의 이벤트 중심 애플리케이션을 구현하는 메커니즘의 장단점을 비롯하여 개요를 설명하겠다.
자바 플랫폼에서 일반적인 방식으로 비동기식 인터랙션을 실행하려는 많은 시도가 있었다. 이 모든 시도들은 메시지 전달 통신 모델에 기반한다. 많은 사람들은 actor 모델의 변이를 사용하여 객체들을 정의한다. 그렇지 않을 경우, 이러한 프레임웍은 가용성, 가용 라이브러리, 방식에 있어서 매우 다르다. 참고자료 섹션에는 프로젝트의 웹 사이트와 관련 정보가 있다. Staged event-driven architecture Staged event-driven architecture (SEDA)는 비동기식 프로그래밍과 자율 컴퓨팅의 개념을 결합한 재미있는 프레임웍이다. SEDA는 J2SE 1.4에 도입된 Java NIO API를 위한 가장 큰 인풋들 중 하나이다. 프로젝트 자체는 끝났지만, SEDA는 자바 애플리케이션의 확장성과 적응성에 대한 새로운 벤치마크를 확립했고, 비동기식 API에 대한 개념은 다른 프로젝트에 영향을 주었다. SEDA는 비동기식과 동기식 API 디자인을 재미있는 결과와 결합하려고 한다. 이 프레임웍은 동시성 보다는 가용성에 치중한 것이지만, 사용자의 마음을 살 정도로 충분히 가용적이지 못했다. SEDA에서, 애플리케이션은 스테이지(stage)로 나뉜다. 각 스테이지는 많은 쓰레드를 갖고 있는 컴포넌트이다. 요청은 스테이지로 보내지고, 이곳에서 처리된다. 이 스테이지는 여러 가지 방식으로 고유의 용량을 조절할 수 있다:
처 음 두 개의 개념은 훌륭한 개념이고, 자율 컴퓨팅 개념을 똑똑하게 적용한 것이다. 하지만 세 번째 개념은 이 프레임웍이 널리 적용되지 못했는지의 이유가 된다. 애플리케이션 디자인 동안 별도의 주의가 없으면 교착 상태의 위험을 추가하게 된다:
SEDA 프로젝트의 개념으로 전개된 구현은 Apache MINA 프레임웍일 것이다. (참고자료) 이것은 OSFlash.org Red5 스트리밍 서버, Apache Directory Project, Jive Software Openfire XMPP Server의 구현에 사용된다. 엄 밀히 말해서, E 프로그래밍 언어는 프레임웍이 아니라, 동적으로 유형화 된 함수 프로그래밍 언어이다. 이것의 초점은 안전한 분산 컴퓨팅을 제공하는 것이고, 비동기식 프로그래밍을 위한 구조도 제공한다. 이 언어는 이 분야의 선배 격에 해당하는 것 중 Joule 과 Concurrent Prolog를 표방하지만, 동시성 지원과 전체적인 신택스는 더욱 자연스럽고, 자바, JavaScript, C# 같은 주류 프로그래밍 언어를 배경으로 하고 있는 프로그래머에게 친숙하다. 이 언어는 자바와 Common-Lisp를 통해 구현된다. 자바 애플리케이션에서도 사용될 수 있다. 하지만, 요즘 무거운 서버 측 애플리케이션을 채택하는데 많은 걸림돌이 있다. 대부분의 문제는 개발의 초기 단계이기 때문에 생기는 것이고, 나중에 해결될 것이다. 다른 문제들은 언어의 동적인 특성에 기인하지만, 이러한 문제들은 이 언어가 제공하는 동시성 확장과 연관된다. E는 다음과 같이 비동기식 프로그래밍을 지원하는 핵심적인 언어 구조를 갖고 있다:
이 러한 일부 구조들은 비동기식 컴포넌트를 쉽게 생성할 수 있게 하는 강력하고 유용한 시스템을 제공한다. 이 언어가 실행 환경에 사용되지 않더라도, 복잡한 동시성 문제를 프로토타이핑 하는데 유용하다. 이것은 메시지 전달 원리를 실행하고, 동시성 문제를 처리하기 위한 편리한 신택스를 제공한다. 비록 결과 코드는 고급스러움과 단순함이 부족해 보이지만, 연산자 놀라울 것이 없고, 다른 프로그래밍 언어로도 에뮬레이트 될 수 있다. E는 비동기식 프로그래밍의 가용성에 대한 장벽을 없앴다. 이 언어의 동시성 지원은 다른 언어 기능과 매우 깊은 관련이 있고, 기존 언어의 갱신이 될 수 있다. 언어 기능은 이미 Squeak, Python, Erlang의 개발 정황에서 이미 논의되었다. C#의 iterator 같은 기능보다 더욱 유용하다. AsyncObjects 프레임웍 프로젝트는 순수 자바 코드로 가용성 있는 비동기식 컴포넌트 프레임웍을 만드는데 집중되어 있다. 이 프레임웍은 SEDA와 E 프로그래밍 언어를 하나로 가져오려는 시도를 하고 있다. E와 마찬가지로, 기본적인 동시성 장치를 제공한다. SEDA처럼, 동기식 자바 API와 통합하는 메커니즘을 제공한다. 이 프레임웍의 최초의 프로토타입 버전은 2002년에 릴리스 되었다. 이후로 개발은 중지되었지만, 프로젝트에서 최근 작업을 시작했다. E는 비동기식 프로그래밍이 얼마나 유용할 수 있는지를 보여주었고, 이 프레임웍은 순수 자바 코드에 머무르면서 최대한의 가용성을 기했다. SEDA 에서처럼, 애플리케이션은 여러 개의 이벤트 루프로 나뉜다. 하지만, 이 프로젝트에서는 SEDA와 같은 자가 관리 기능이 구현되지 않는다. SEDA와는 달리, I/O에 더욱 간단한 로드 관리 메커니즘을 사용하는데, 컴포넌트가 훨씬 더 소단위이고, promise는 연산의 결과를 받기 위해 사용되기 때문이다. 이 프레임웍은 E 프로그래밍 언어와 같은 비동기식 컴포넌트, vat, promise의 개념을 구현한다. 새로운 연산자는 순수 자바 코드에 들어갈 수 없으므로, 임의의 객체는 비동기식 컴포넌트가 될 수 없다. 구현은 특정 베이스 클래스를 확장해야 하고, 프레임웍이 구현하는 비동기식 인터페이스가 있어야 한다. 프레임웍에서 제공하는 비동기식 인터페이스 구현은 메시지를 컴포넌트의 vat으로 보내고, 나중에 vat은 메시지를 컴포넌트로 보낸다. 현재 버전(0.3.2)은 Java 5에 호환되고, 일반적인 것을 지원한다. 자바 NIO는 현재 플랫폼에 사용할 수 있다면 사용된다. 하지만, 이 프레임웍은 플레인 소켓이 될 수 있다. 이 프레임웍의 가장 큰 문제는 클래스 동기식 자바 API와 통합이 어렵기 때문에 클래스 라이브러리가 빈약하다는 점이다. 현재, 네트워크 I/O 라이브러리만 구현된다. 하지만, Axis2의 비동기식 웹 서비스와 Tomcat 6의 Comet Servlets(Servlet-specific 또는 IO-specific APIs ) 같은 이 분야의 최신 향상은 이 같은 통합을 단순화 할 수 있다. Waterken의 라이브러리는 연산의 마지막 호출을 지원한다. 하지만, AsyncObjects보다는 덜 자동화 된 것처럼 보인다. 쓰레드 보안 역시 의심스럽다. 오 직 핵심 클래스와 아주 작은 샘플만 릴리스 되었으며, 중요한 애플리케이션과 클래스 라이브러리가 없다. 따라서, 이 프레임웍의 개념이 더 큰 스케일에서는 어떻게 작동할지 확실하지 않다. 필자는 완전한 웹 서버가 구현되었고, 곧 릴리스 될 것으로 예상한다. 이것이 릴리스 되면, 이 프레임웍을 다시 사용해 보는 것도 괜찮을 것이다. Frugal Mobile Objects는 actor 모델에 기반한 또 다른 프레임웍이다. Java ME CLDC 1.1 같은 리소스 제한 환경을 타겟으로 한다. 재미있는 디자인 패턴을 사용하여 리소스 사용을 줄이면서, 인터페이스를 단순화 한다. 이 프레임웍은 리소스 제약 환경에서도 성능과 확장성 문제에 직면할 때 비동기식 디자인의 혜택을 받는다고 주장한다. 이 프레임웍에서 제공되는 API는 방해가 되지만, 프레임웍의 대상 환경의 제약 조건에 의해서 정리될 것이다. Scala는 자바 플랫폼을 위한 또 다른 프로그래밍 언어이다. 자바 기능의 수퍼 세트를 제공하지만, 약간 다른 신택스로 제공한다. 플레인 자바 프로그래밍 언어와 비교해 볼 때 가용성 향상이 생겼다. 재 미있는 기능 중 하나는 액터 기반의 동시성 지원으로서, Erlang 프로그래밍 언어를 모방하여 모델링 되었다. 디자인은 아직 완성되지 않았지만, 기능은 비교적 가용성 있고 언어 신택스에 의해 지원을 받는다. 하지만, Scala의 Erlang 계열의 동시성 지원은 E의 동시성 지원 보다 유용성과 자동화 측면에서 떨어진다. Scala 모델 역시 콜러에 대한 레퍼런스가 모든 메시지와 함께 전달되기 때문에 보안 문제를 갖고 있다. 호출된 컴포넌트가 값을 리턴하는 대신에 호출 컴포넌트의 모든 연산을 호출할 수 있다. E의 promise 모델은 이러한 관점에서 보다 세분화 되어 있다. 블로킹(blocking) 코드와 통신하는데 사용되는 메커니즘은 아직 완벽히 개발되지 않았다. Scala 의 장점은 JVM 바이트코드로 컴파일 한다는 점이다. 이론상으로는, 성능에 영향을 주지 않고, Java SE와 Java EE 애플리케이션에 의해 사용될 수 있다. 하지만, 상용 개발의 적합성은 개별적으로 결정되어야 하는데, Scala는 제한된 IDE를 갖고 있고, 자바와는 달리 벤더의 지원을 받지 못한다. 따라서, 프로토타입 같은 단기 프로젝트용 플랫폼이긴 하지만, 장기적인 프로젝트에 사용하기는 위험하다.
Servlet-specific 또는 I/O-specific API 우 리가 설명했던 문제들이 서블릿, 웹 서비스, 일반적인 I/O 레벨에서는 가장 심각하지만, 여러 프로젝트들이 이러한 문제들을 다루고 있다. 이러한 솔루션의 가장 큰 단점은 애플리케이션의 제한된 범주에 대한 문제만 해결하려고 한다는 점이다. 비동기식 서블릿을 만드는 것이 가능하더라도, 로컬 및 원격 리소스에 비동기식 호출을 할 수 있는 기능 없이는 쓸모가 없다. 비동기식 모델과 비즈니스-로직 코드도 작성하는 것이 가능해야 한다. 또 다른 공통적인 문제는 제안된 솔루션의 유용성인데, 대체로 일반 솔루션보다 저급이다. 하지만, 이러한 시도는 비동기식 컴포넌트를 구현하는 문제를 인식하고 있다는 점에서 주목할 만하다. 프로젝트의 웹 사이트와 관련 정보는 참고자료 섹션을 참조하라. JSR 203은 NIO API의 개정 버전이다. 이 글을 쓰고 있는 현재, 초안 단계에 있고 개발 프로세스 동안 많이 바뀔 것이다. 이 API는 Java 7에 포함될 예정이다. JSR 203은 비동기식 채널(asynchronous channels)이라는 개념을 도입했다. 많은 프로그래머들이 우려하는 바를 해결하고자 계획되었지만, 이 API는 다소 저급 수준에 머물러 있는 것 같다. 최근에는 이전 버전에는 없었던 비동기 File I/O API를 도입했고, 하 지만, JSR의 가장 큰 단점은 파일과 소켓 I/O에만 국한되었다는 점이다. 고급 비동기식 컴포넌트를 생성할 수 있는 구현 블록을 제공하지 않는다. 고급 클래스가 구현되지만, 같은 작업을 수행하는 방식을 제공해야 한다. 자바 언어로 동기식 컴포넌트를 개발할 수 있는 표준 방식이 없다는 것을 생각해 볼 때, 이것은 좋은 기술적 결정인 것 같다. Glassfish Grizzly NIO 지원은 SEDA 프레임웍과 비슷하고, SEDA 문제 대부분을 상속받았다. 하지만, I/O 태스크들을 지원하는데 보다 특화되었다. 제공된 API는 플레인 NIO API보다 고급이지만, 여전히 사용이 까다롭다. Jetty continuations는 매우 전통적이지 않은 방식이다. 컨트롤 흐름의 구현에 예외를 사용하는 것은 API에 있어서는 다소 의문의 여지가 많은 방식이다. (참고자료 섹션의 Greg Wilkins 블로그 참조). 서블릿은 continuation 객체를 요청하고, 여기에 대한 지정된 타임아웃으로 따라서, Jetty는 비동기식 의미론으로 동기식 같은 API를 구현하려고 한다. 하지만, 서블릿이 Tomcat Comet API는 Comet 인터랙션 패턴을 지원하도록 디자인된다. 이 서블릿 엔진은 상태 변화와 데이터를 읽을 수 있는지 여부에 대해 서블릿에 공지한다. 이는 Jetty가 사용하는 것 보다 안정적이고 단순하다. 이러한 방식은 스트림에서 쓰고 읽는데 전통적인 동기식 API를 사용한다. 이와 같은 방식으로 구현된 API는 신중하게 사용된다면 차단되지 않는다. JAX WS 2.0 and Apache Axis2 Asynchronous Web Service Client API JAX WS 2.0과 Axis2는 웹 서비스의 Nonblocking 호출에 API 지원을 제공한다. 웹 서비스 엔진은 웹 서비스 연산이 끝나면 제공된 리스너에게 공지한다. 이는 웹 클라이언트에게도 웹 서비스 사용에 대한 새로운 기회를 연 것이다. 하나의 서블릿에서 여러 독립적인 웹 서비스 호출이 있다면, 모두 병렬로 수행될 수 있기 때문에 전체적인 지연 시간은 줄어든다.
비 동기식 자바 컴포넌트에 대한 필요가 대두되고 있고, 비동기식 애플리케이션 분야가 활발히 개발 중이다. 두 개의 메이저 오픈 소스 서블릿 엔진들(Tomcat과 Jetty)은 개발자들이 가장 힘들어하는 서블릿에 최소한의 지원을 한다. 자바 라이브러리가 비동기식 인터페이스를 제공하기 시작했더라도, 이러한 인터페이스는 공통 테마가 부족하고, 쓰레드 관리와 다른 문제들 때문에 서로 호환도 되지 않는다. 이는 다양한 분야에서 제공되는 비동기식 컴포넌트들을 호스팅 할 수 있는 컨테이너에 대한 필요를 낳았다. 현 재 사용자들에게는 다양한 상황에서 장단점을 갖고 있은 많은 선택권이 주어졌다. Apache MINA는 유명한 네트워크 프로토콜을 위한 지원을 제공하는 라이브러리이기 때문에, 이러한 프로토콜이 필요할 때 좋은 선택이다. Apache Tomcat 6는 Comet 인터랙션 패턴에 대한 지원을 하기 때문에, 비동기식 인터랙션이 이 패턴으로 제한될 때 매력적인 옵션이 된다. 애플리케이션을 처음부터 만들 경우, 기존 라이브러리에서는 많은 혜택을 볼 수 없고, 다양한 인터페이스를 제공하는 AsyncObjects 프레임웍이 알맞다. 이 프레임웍은 기존의 비동기식 컴포넌트 라이브러리 주위에 래퍼를 생성하는데 유용하다. 자바 언어를 위한 일반적인 비동기식 프로그래밍 프레임웍을 생성하는데 초점을 맞춘 JSR을 만들 시간이다. 기존의 비동기식 컴포넌트를 이 프레임웍에 통합하고, 기존 동기식 인터페이스의 비동기식 버전을 생성하기까지 갈 길이 멀다. 각 단계마다, 엔터프라이즈 자바 애플리케이션의 확장성이 향상될 것이고, 우리는 도전에도 직면할 수 있다. 지속적으로 성장하는 인터넷 인구와 네트워크 서비스들은 우리에게는 확실한 도전 과제이다.
|
SSISO Community