[Spring] Servlet 3.0 & Servlet 3.1

leehyunjon·2022년 6월 21일
0

Spring MVC

목록 보기
4/4
post-thumbnail

이전 내용에서는 많은 사용자의 요청을 처리하기 위한 클라이언트 요청과 서블릿 컨테이너간의 Connector에 대해서 공부했었다.

이전 내용을 잠깐 설명하자면 다수의 커넥션을 관리하기 위해 커넥션당 하나의 스레드를 할당하는 BIO Connector 대신 Poller 통해 사용가능한 소켓에 스레드를 할당하는 NIO Connector를 사용하게 된다.

NIO Connector의 등장으로 클라이언트와 서블릿 컨테이너간 커뮤니케이션이 NonBlocking이 되었다.
하지만 요청을 처리하기 위해 Servlet 3.0이전의 서블릿단에서는 여전히 Sync-Blocking 방식으로 동작했기 때문에 Blocking 현상이 발생하게 된다.
즉, 서블릿에서의 처리 스레드를 기다리기 위해 요청 스레드가 결국 idle상태에 놓이게 되는 것이다.

이에 따라 Servlet 3.0과 3.1에서 Aynce Servlet과 NonBlocking I/O를 지원하게된다.


Servlet3.0과 3.1에 대해 배우기 전에 동기와 비동기 Block과 NonBlock을 잘 모른다면 따로 공부하고 보는게 좋다.
https://velog.io/@hyunjong96/Sync-Async-Block-Non-Block


Servlet 3.0 이전

public class HelloServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) 
    throws IOException {
		res.setContentType("text/plain");
		res.setCharacterEncoding("UTF-8");
		
		PrintWriter writer = res.getWriter();
		writer.println("Hello");
        //서블릿 실행 종료시 클라이언트에 응답 전송 및 스트림 종료
	}
}

Servlet3.0이전에는 서블릿에서 클라이언트의 요청 스레드에서 요청에 대한 응답을 생성한다.
해당 스레드에서 모든 처리가 끝나게 되면 서블릿 컨테이너는 응답 전송 후 클라이언트와의 연결을 종료한다.

따라서 요청이 들어왔을때 하나의 요청이 하나의 스레드를 요청이 끝날때까지 점유하게되어 쓰레드가 부족해지는 현상이 발생하게 된다.

Servlet 3.0

Servlet 3.0에서 추가된 비동기 기능은 요청과 별도로 스레드를 처리할수 있도록 하였다.

					출처 : https://www.youtube.com/watch?v=aSTuQiPB4Ns
                    

요청을 하는 스레드(서블릿 컨테이너)요청을 처리하는 스레드(서블릿)가 분리되어 처리한다.

@WebServlet(urlPatterns = "/hello", asyncSupported = true)
public class HelloServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) 
    throws IOException {
		final AsyncContext asyncContext = req.startAsync();

		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}

				HttpServletResponse response = 
                (HttpServletResponse)asyncContext.getResponse();
                
				response.setContentType("text/plain");
				response.setCharacterEncoding("UTF-8");

				try {
					PrintWriter writer = response.getWriter();
					writer.println("Hello");
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				System.out.println("complete response");
				asyncContext.complete();
			}
		}).start();

		System.out.println("doGet return");
	}
}

@WebServlet어노테이션의 asyncSupported속성을 true로 설정함으로써 비동기 기능을 지원하게 하였다.

HttpServletRequeststartAsync()메서드를 통해 AsnycContext객체를 생성해주는데, 이는 서블릿의 메서드가 종료되어도 클라이언트와의 연결은 끊어지지 않는다.

이때 서블릿의 메서드가 종료되면 비동기 기능을 생성한 요청 스레드는 서블릿 컨테이너가 관리하는 스레드 풀에 반납된다.

AsyncContext를 통해 클라이언트에게 응답할 HttpServletResponse를 생성하고 응답이 생성되면 complete()메서드를 호출하여 요청에 대한 응답 및 클라이언트와의 연결을 종료한다.

실행 흐름

  1. 클라이언트 요청 스레드(T1)이 HelloServlet의 doGet()메서드를 실행한다.
  2. T1은 HttpServletRequest의 startAsync()메서드로 AsyncContext객체를 생성한다.
  3. T1은 비동기 기능을 수행할 새로운 스레드(T2)를 생성한다.
  4. T2가 비동기를 처리하는 동안 doGet()메서드는 종료된다.
  5. T1은 서블릿 컨테이너가 관리하는 스레드 풀에 반납된다.
  6. T2는 5초동안 요청을 처리하고 AsyncContext의 getResponse()메서드를 통해 HttpServletResponse객체를 생성한다.
  7. 생성한 HttpServletResponse객체를 통해 클라이언트의 요청에 응답한다.
  8. T2는 AsnycContext의 complete()메서드를 호출하여 클라이언트와의 연결을 종료한다.
  9. T2는 실행 종료된다.

문제점

이처럼 Servlet3.0에서는 클라이언트의 요청시 서블릿단에서 요청을 처리할 새로운 스레드를 생성한 뒤 컨테이너의 스레드 풀에 반환되어 Async Servlet(Async-Blocking)이 되었다.

하지만 만약 용량이 큰 응답을 보내야할 때는 전통적인 I/O(InpuStream, OutputStreamBlocking)를 사용하고 있기 때문에 요청 스레드는 처리 스레드가 완료될 때 까지 blocking이 발생할수 있다.

						출처 : https://hadev.tistory.com/29?category=959073

Servlet 3.1

Servlet 3.1은 Non-Blocking을 지원함으로써 Servlet3.0의 Blocking의 문제를 해결해준다.

					출처 : https://hadev.tistory.com/29?category=959073

Writer/Read Listener를 통해서 Non-Blocking으로 동작하게된다.

ReadListener

onDataAvailable

onDataAvailable은 컨테이너가 서블릿이 읽을 데이터가 있는 즉시 이 메서드를 처음 호출한다.
이후에 ServletInputStream.isReady()이 false를 반환하며 데이터를 Blocking없이 읽을 수 있을때 ServletInputStream.isReady()가 true를 반환했을 때 다시 호출한다.

onAllDataRead

onAllDataRead는 요청에 대한 데이터를 완전히 blocking없이 읽을 수 있을 때 호출한다.
(내부에 WriteListenr를 호출한다.)

onError

에러 발생시

WriteListner

onWritePossible

onWritePossible은 컨테이너가 쓸 데이터가 있는 즉시 해당 메서드를 처음 호출한다.
이후에 ServletOutputStream.isReady()이 false를 반환하며 데이터를 Blocking없이 읽을 수 있을 때 다른 스레드에 의해 다시 호출되어 클라이언트에게 응답을 보낸다.

onError

에러 발생시

만약 클라이언트에서 큰 용량의 데이터가 들어 올때 ReadListener의 메서드를 호출해 완전히 데이터를 읽을 수 있을 때 요청을 처리하게 되고,
큰 용량의 데이터를 반환해야할 때 WriteListener의 메서드를 호출해 완전히 데이터를 보낼수 있을 때 응답을 하게한다.

이렇게 Non-Blocking을 지원함으로써 큰 데이터를 읽고 쓸때 스레드가 점유되는 것을 줄일 수 있다.


공부하면서 헷갈리는 부분도 많고 확실하지 못한 부분은 추상적으로 얘기한 부분이 없지않아 있는 것 같다.
좀 더 많은 레퍼를 참고하면서 수정 및 추가해 나가야할 부분인것 같다.

Reference

https://www.youtube.com/watch?v=aSTuQiPB4Ns

https://homoefficio.github.io/2017/02/19/Blocking-NonBlocking-Synchronous-Asynchronous/

https://javacan.tistory.com/entry/Servlet-3-Async

https://hadev.tistory.com/29?category=959073

https://stackoverflow.com/questions/39802643/java-async-in-servlet-3-0-vs-nio-in-servlet-3-1

https://jongmin92.github.io/2019/03/31/Java/java-async-1/#Servlet-Async

profile
내 꿈은 좋은 개발자

0개의 댓글