티스토리 뷰
1. Overview
Blocking/Non Blocking 의미는 차치하고 결국 이런 단어를 언급해 설명하고자 하는 건 요청이 있고 해당 요청에 대한 처리 작업 시간이 길더라도 wait 하지 않고 얼마나 자원을 효율적으로 사용할 수 있는가에 초점이 있다. 오늘은 Spring Boot로 동작 가능한 프로그램을 작성해 실제로 어떻게 동작하는지 확인해보겠다.
2. Servlet Thread (Blocking)
tomcat은 default 로 200개의 써블릿 쓰레드를 설정해서 요청을 처리한다. 만약에 200개의 쓰레드가 모두 사용 중이라면 이후 요청은 서블릿 쓰레드가 확보 될 때까지 대기를 해야된다. 테스트를 쉽게 하기 위해서 톰캣의 스레드 갯수를 1개로 변경하고 테스트를 하겠다.
2.1 application.yml
spring boot 에서는 application.yml 파일에 설정으로 쓰레드의 갯수를 변경할 수 있다.
server:
tomcat:
threads:
max: 1
2.2 Controller
테스트를 위한 간단 controller 를 만들겠다. 이름을 Path Parameter 전달하면 10초후에 대괄호를 씌워 반환한다.
@GetMapping("sync/{name}")
public String sync(@PathVariable String name) throws InterruptedException {
log.info(" ---> in sync");
TimeUnit.SECONDS.sleep(10);
return "[" + name + "]";
}
2.3 테스트
Spring Boot서버를 시작하고 URL을 요청해 보겠다. 최초 1회 요청 후 F5 를 눌러 재 요청하거나 브라우저 탭을 여러게 띄어서 계속적으로 요청해보겠다. 해당 메서드에서 10초를 대기하기 때문에 첫번째 요청이 끝나기까지 나머지 요청은 Pending 된다.
출력된 로그 문자열(---> in sync)로 확인할 수 있듯이 브라우저에서 아무리 요청을 많이 보내도 10초에 한번씩 출력된다. tomcat 의 max threads 숫자를 높이면 해당하는 숫자만큼 동시 처리가 가능할 것이다.
3. Servlet Thread (Non Blocking)
이제 같은 환경에서 Non Blocking 으로 요청을 처리하는 몇가지 방법을 알아보겠다.
3.1 Callable
Callable 은 Java에서 Multi Threading 처리를 위해 Java 1.5 에서 추가된 인터페이스이다. Servlet 3.0 부터는 Servlet 에서 리턴값을 Callable 을 리턴하면 비동기 처리가 가능하다. Spring @MVC 3.2 이상에서도 해당 스펙을 지원한다. 아래 코드는 Callable 리턴하는 예제 코드이다.
@GetMapping("callable/{name}")
public Callable<String> getName(@PathVariable String name) {
log.info(" ---> in callable");
return () -> {
TimeUnit.SECONDS.sleep(10);
return "[" + name + "]";
};
}
자바8 lamda를 이용해 Callable 을 구현한 익명함수를 반환했다.
3.2 Callable 테스트
10초에 delay 타임이 있기 때문에 응답이 바로오지는 않지만 로그를 확인해 보면 요청이 올때마다 바로 메서드에 진입하는걸 확인할 수있다. 처음에 Node.js 가 세상에 나오면서 Java Servlet 에 비교해 빠른 응답과 많은 처리량을 할 수 있던 차이점이 이와 비슷한 맥락이다.
3.3 DefferedResult
DefferedResult 역시 Spring @MVC 3.2 이상에서 Servlet 의 비동기 처리를 위한 객체이다. Controller 에서 DefferedResult 를 생성해서 반환하고 이후에 해당 객체의 메서드로 종료 처리 또는 예외 처리가 가능하다. Callable 과 가장 큰 차이점은 비동기 처리를 위한 쓰레드 작업이 내부가 아닌 외부에서 관리 된다는 점이다.
@GetMapping("deffer/{name}")
public DeferredResult deferredResult(@PathVariable String name) {
log.info("==> in deferredResult");
DeferredResult<String> rtn = new DeferredResult<>();
Runnable anotherThread = () -> {
try {
TimeUnit.SECONDS.sleep(10);
rtn.setResult("[" + name + "]");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(anotherThread).start();
return rtn;
}
Controller 에서는 DefferedResult<String> 객체를 생성해서 반환만 해준다. 그리고 별도의 쓰레드에서 작업이 완료되면 setResult() 메서드를 호출해서 종결처리를 한다. 또는s etErrorResult() 메서드를 이용해서 에러 처리를 할 수 있다. 이 부분은 자바스크립트에서 비동기 처리를 할때 언급되는 callback 과 Promise 패턴의 차이점이기도 하다.
3.4 DefferedResult
DefferedResult 를 이용한 URI 호출 역시 Callable 과 마찬가지로 페이지 요청마다 로그가 출력되는걸 확인할 수 있다.
4. Conclusion
간략한 예제를 이용해 Spring Boot에서 Cotroller의 비동기 응답처리를 하는 법을 알아봤다. 물론 세부적 자원에 대한 고려 사항이 더욱 많을 수 있겠지만 단편적인 부분만 봤을 때 많은 요청을 처리하기 위한 좋은 방법이 될 수 있을 것 같다. 예제 코드 전체는 아래 링크에 가면 확인할 수 있다.
'Spring' 카테고리의 다른 글
Spring Boot Reactive + Postgresql(r2dbc) (0) | 2020.09.23 |
---|---|
Async method with Spring boot (0) | 2020.08.06 |
H2 Console in WebFlux (0) | 2020.07.28 |
static resources in spring boot (0) | 2020.07.11 |
Spring Security (Spring Boot) #4 (0) | 2020.02.28 |
- Total
- Today
- Yesterday