[스프링 MVC 3.2] Async Servlet

작년 스프링 원 발표 동영상이다. 여기서 설명하는 Async Servlet 관련 내용만 정리한다. 모르지 보다가 내키면 다른것도 하고.. 아님 안하고 내맘.

우선 이녀석이 등장하게 된 배경부터 설명한다. (나말고 발표자가. 아마도 로센?)

Request-Response 모델은 누구나 다 알고 지금도 많이 사용하듯이 웹브라우저에서 사용자가 뭔가 요청을 보내면 서버가 응답해주는 방식이다.

그러다 Ajax가 등장한다. 전체 페이지를 로딩해오는게 아니라 페이지 일부만 변경하는 방법이다. 기술적으로는 자바스크립트로 Request-Response를 처리하는 거고, 자바스크립트로 setTimeout인가 뭔가를 사용해서 주기적으로 요청을 보내서 응답을 받은걸로 화면 일부를 갱신하는 기술이 가능해졌었다.

그런게 바로 ‘live’-ness 인데, 굳이 옮기자면 “생동감”이라고나 할까. 애플리케이션마다 각기 다른 수준의 생동감이 필요하다.

트위터나 페북에 올라오는 스트림을 주기적으로 가져오거나, 최신 뉴스를 주기적으로 가져오는 것 정도야 괜찮겠지만, 채팅이나 온라인 협업 같은 경우라면 10초, 5초 주기로 갱신해가며 대화하기는 불편할꺼다. 거의 실시간적인 생동감이 필요하다.

그래서 등장한게 Long Polling. 이것도 사실 Request-Response 모델이다. 단 하나 다른게 있다면 Response를 바로 하는게 아니라 클라이언트로 줄 만한 데이터가 있을때까지 서버가 요청을 붙잡고 안놔준다는거… 그러다 필요한 데이터가 오면 그때 클라이언트로 응답을 보내고, 응답을 받은 클라이언트는 곧바로 다음번 요청을 보내서 또 서버한테 데이터 있을 때까지 가지고 있다가 응답 달라고 하는거다. 이걸 이제 얼핏 비껴보면 서버가 클라이언트로 Push하는거처럼 보이기도 하는거지.

그런데 이게 문제가 뭐냐면, 데이터가 바로바로 올라오면 상관없는데, 클라이언트한테 줄 데이터가 안오면 서버 리소스가 계속해서 붙잡힌 상태로 대기 중이기 때문에 서버쪽에 부하가 상당해진다.

일반적으로 서블릿 컨테이너는 “Thread-Per-Request” 모델을 사용하는데, 롱폴링이 많아지면 요청을 잡고있는 Thread가 많아져서 새로운 요청을 받아줄 쓰레드가 금방 모자르게 되는거지.. 두둥. 그래서!! 서블릿 3 어싱크 기능이 등장!

어떻게 할려는거냐면… 단순하게 말해서 쓰레드를 바꿔치기 하려는거지..

대기해야 하는 작업을 Request 처리하는 쓰레드 말고 다른 쓰레드(흔히 워커쓰레드라고 부름)한테 위임시키고 Request 쓰레드는 빨리 반납해서 그 자원으로 새로운 Request를 처리하려는거지.

그래야 똑같은 방식(롱폴링)으로 동작하면서도 더 많은 요청을 처리할 수 있을테니까.

서블릿 쓰레드 모델을 표현하면 다음과 같다.

[Servlet Thread]

– Start processing

– Do some work

– Send response

완전 단순하지.. 근데 이걸 이제 중간에 끊어서 다른 놈한테 주려는거야… 요렇게.

[Servlet Thread]

– Start processing

– Switch to async mode

– Exit thread leaving response open

[Application Thread]

– Produce or receive result

– Dispatch to container to resume processing

[Servlet Thread]

– Start processing

– Process result from application thread

– Send response

복잡해보이지만, 사실 단순하지. 서블릿 쓰레드가 요청 처리 시작하면 어싱크 모드로 바꾸고 쓰레드는 응답하지 않고 끝내버리지, 그럼 서블릿 쓰레드는 반나되고 다른 요청 처리하겠지뭐. 그런 어싱크 처리는 애플리케이션 쓰레드가 처리하고 그 결과를 다시 컨테이너한테 주는거야. 그럼 컨테이너가 다시 서블릿 쓰레드 띄워서 애플리케이션 쓰레드가 보내준 결과 받아서 응답 만들어서 보내는거지.

여기서 질문… 왜 애플리케이션 쓰레드가 바로 응답을 보내지 않고 다시 서블릿 쓰레드를 거쳐서 보내느냐…

이건 마일스톤1에서 시도해봤었는데 안돼. HttpServeltRequest를 컨테이너 쓰레드가 아닌곳에서는 사용할 수가 없어서 포워딩(JSP 렌더링할때 하는 포워딩 알지?)을 할 수가 없어. 암튼 그래서안되고. 스프링 3.2 어싱크나 보자고.

@RequestMapping 메서드의 리턴값으로 Callable랑 DefferedResult, AyncTask를 던질 수 있게됐어.

이 중에서 Callable부터 볼까나

java.util.concurrent.Callable

이 객체 담긴 작업은 스프링 MVC가 관리하는 쓰레드에서 돌게 돼. 주로 오래 걸리는 DB 작업, 써드파티 REST API 콜 등에 적당하다네..

DefferedResult

이 객체 담긴 작업은 스프링 MVC 밖에 있는 쓰레드에서 돌게 돼지. 주로 JSM, AMQP 리스너, 또 다른 HTTP 요청을 보내거나. 등등

AsyncTask

Callable하고 같은데 비동기 처리에 타임아웃 값을 추가할 수 있다는군. AsuncTaskExecutor를 선택할 수도 있고. 용도에 따라 여러 쓰레드풀을 두고 선택해서 사용할 수 있다네.. 페이스북용 쓰레드풀, 트위터용 쓰레드풀.. 하는식으로.

질문: 응답을 찔끔 찔끔 보내고 싶은데.. 그때는 어떻게 해?

답: OutputStream에 접근할 수 있으니까 그거 가지고 찔끔 찔끔 보내면 되긴 하는데 JSON은 어쩌고 저쩌고.. HTTP 스트리밍을 하려면 그건 좀 다른건데.. 어쩌구 저쩌구.. 나중에 볼께.

DEMO는 스프링 MVC Showcase에 있음

async  패키지에 CallableController를 보면 됨.

https://github.com/SpringSource/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/CallableController.java

@RequestMapping(“/response-body”)
public @ResponseBody Callable<String> callable() {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return “Callable result”;
}
};
}
@RequestMapping(“/view”)
public Callable<String> callableWithView(final Model model) {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
model.addAttribute(“foo”, “bar”);
model.addAttribute(“fruit”, “apple”);
return “views/html”;
}
};
}
이런 메서드인데 저렇게 Callable 객체로 만든 부분이 서블릿 쓰레드가 아니라 스프링 MVC에서 관리하는 쓰레드에서 실행되는거지. 그리고 리턴되면 다시 서블릿 쓰레드가 받아서 처리하는거고.. 그런 일련의 과정은 로그를 보면 알 수 있고.

자.. 이게 문제의 화면인데. 사부님이 여기서 아주 심각한 문제를 발견하셨지!!! 캬캬캬. 역시 내가 아는 가장 성실하고 인간적이면서 실력까지 최고인 엔지니어야… 왜 단점이 없을까.. 뚱뚱한거?… 암튼, 이 부분에 대해서는 다음주 세미나 때 설명하실테니까 나는 패스.

질문: 쓰레드 스위칭이 일어나는데 성능에 좀 영향이 없냐..

답: 쓰레드 풀에 다양한 옵션이 있으니까 풀 옵션으로 조정하고, 약간의 burden이 있긴 하지만 서블릿 쓰레드를 쉬게하는게 중요하다.

질문: 브라우저는 그거다 끝날때까지 계속 대기중이지?

답: 그렇다.

이 시점에서 질문이 쏟아진다… 다는 못알아 듣겠다..

Callable에서 예외를 던지면 어떻게 되냐..

@RequestMapping(“/exception”)
public @ResponseBody Callable<String> callableWithException(
final @RequestParam(required=false, defaultValue=”true”) boolean handled) {
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
if (handled) {
// see handleException method further below
throw new IllegalStateException(“Callable error”);
}
else {
throw new IllegalArgumentException(“Callable error”);
}
}
};
}

HandlerExceptionResolver로 해결하지요.. 다른 MVC에서 발생하는 예외랑 마찬가지로.

그럼 스프링 MVC 밖에서 동작하는 DefferedResult에서 발생하는 예외는?

https://github.com/SpringSource/spring-mvc-showcase/blob/master/src/main/java/org/springframework/samples/mvc/async/DeferredResultController.java

여기 코드 잘 보면 나오는데, setErrorResult(object)로 예외를 세팅하게 되있고, 그걸 나중에 dispatch 하고나서 역시 HandleExceptionResolver로 해결한다네..

이런거 쓰려면 web.xml에 async-supported 플래그 설정해줘야돼.

OncePerRequestFilter 등 모든 스프링 MVC 필터들이 업데이트 됐다는군. 이건 어싱크 서블릿 스팩이랑 관련있는 내용이로군.

AsyncHandlerInterceptor라는게 추가됐구나;; 자세한건 나중에 자바독을 보기로하고 패스

Async Support 설정하려면 자바 설정이랑 MVC 네임스페이스가 있으니까 알아서..

뭘 설정할 수 있냐면.

– Async timeout value (밀리세컨드)

– Callable 처리할 AysncTaskExecutor

모든 필터에 async-supported 플래그가 있으니까 설정하고 dispatcher-type도.

WebApplicationInitializer로 설정하는 방법 소개하는데..  난 자바 설정을 잘 안써서… 흠;; 패스

이 뒤부터는 REST 관련 내용이로군… REST 할때의 예외처리에 관한 내용인데 이것도 관심이 가네!! 오.. 호… 그래 REST API는 예외처리가 참 난감했는데 어찌한담… 이건 설렁설렁 봐야지.

 

백기선의 토스3, 1부 코딩 동영상 공개

2009년에 처음으로 스프링 교육을 준비하던 시절 만들었던 코딩 동영상입니다. 교육은 하루 6~8시간씩 총 4일 과정이었고 코딩을 통해서 몸으로 직접 익히는 교육을 구상했었습니다. 그 뒤로 교육 제의가 들어오는 곳이 간간히 있었고 그때마다 교육 내용은 아주 조금씩 변형되었지만 저때 처음 만들어둔 코딩 동영상은 변하지 않고 매번 아주 유용하게 써먹을 수 있었습니다.

하필이면 교육하는 날 제가 몸이 안좋아서 도무지 코딩을 제대로 할 자신이 없을 때 큰 힘이 되었고, 시간이 촉박한데 갑자기 코딩이 막혔을 때에도 매우 유용했습니다. 무엇보다 손은 놀면서 입으로만 코딩할 수 있는 것도 편했고 그럴 때마다 매번 속으로 또는 겉으로 ‘이 동영상을 만들어 두길 진짜 잘했다’고 몇 번이나 되뇌였습니다.

순수하게 코딩만 찍은거라서 아무런 음성이 없습니다. 여기에 육성으로 설명 추가하면서 녹화를 하다가는 말실수 하거나 중간에 다른 소리가 섞여서 녹화를 다시하는 상황이 발생할 수 있기 때문에 일부러 처음 녹화할 때 소리는 빼고 녹화를 했죠.

여기에 동영상 중간 중간 멈춰 가면서 육성으로 설명을 추가로 입히고 소스 코드랑 교재도 다시 정리해서 올리려면 적지 않은 노력이 들기 때문에 그렇게까진 못하겠고, 수고스럽지만 그런걸 다 해서 DVD에 담아서 오프라인으로 팔까도 생각해봤지만 누가 얼마나 사겠냐 싶기도 하고…

이래저래 백기선의 토스3 교육은 이걸로 끝.

[스프링 3.2] 비동기 서블릿 지원 맛보기

https://github.com/SpringSource/spring-framework/pull/69

https://github.com/rstoyanchev/spring-mvc-async-sample

흠냐.. 맨 위에껀 스프링 3.2에 작업중인 코딩 내역이고 아래 링크는 실제로 어떻게 사용할 수 있는 보여주는 예제 프로젝트입니다. 로센이 작업했네요. 멋쟁이.

예제 프로젝트를 받아서 돌려봤는데 매우 깔끔하게 동작합니다. 뷰를 Thymeleaf라는 것을 사용했는데… 흠.. JS랑 맛물리는 뭔가가 있는것 같기도 하고 첨보는거라 뷰가 어떻게 돌아가는지 몰겠네요. 어쨌든 HTML 파일 하나를 사용하고 있습니다.

자바스립트는 두개를 사용하는데 하나는 제이쿼리, 하나는 넉아웃.js 그리고 제이쿼리 플러그인으로 하나더 jquery-stream.js이라고 있기는 한데… 소스코드를 봐도.. 크롬 개발자 도구로 봐도.. 사용하진 않는것 같습니다.

스프링 설정은 자바 설정으로 만들었는데 굳이 설정 파일은 루트와 웹을 나눴으면서 애플리케이션 컨텍스트는 나눠서 등록하지 않는 모습이 이번에도 보이는데… 왜 CCL을 사용하지 않고 DS만 사용할꺼면서 빈 설정은 나누는지 잘 모르겠네요. 그럴꺼면 걍 웹용 설정 파일만 만들어도 될텐데 말이죠. web.xml도 없고 톰캣7 기능을 사용해서 자바 웹 설정으로 web.xml을 대신합니다.

서론이 길었군요. 스프링 3.2부터는 @RequestMapping이 붙은 메서드에서 리턴할 수 있는 타입이 두개가 더 생깁니다. 비동기 서블릿과 관련된 리터타입이죠.

  • 스프링에서 만들어 둔 DeferredResult
  • 자바 컨커런시에 있는 Callable

지금 데모용 프로젝트에서는 DeferredResult를 사용하고 있습니다. DR을 사용하는 부분만 보겠습니다.