ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링부트 Web-Flux 01 - 기초
    스프링/WEB-FLUX 2021. 10. 25. 16:03

    유투버 '데어 프로그래밍'님 강의 참조 

     

     

    01 Web-Flux란?

    • Spring 5에서 새롭게 추가된 모듈로써 클라이언트와 서버에 reactive 스타일(반응형 웹사이트식의 방식)의 어플리케이션 개발을 도와주는 모듈
    • 장점 : 고성능, spring 과 완벽한 통합, netty 지원, 비동기 non-blocking 메세지 처리
    • 단점 : 오류처리가 다소 복잡하다. Back Pressure 기능 없음
    • 다음 링크를 참조(https://happymemoryies.tistory.com/24) 하자면 일반 스프링 MVC모델 방식은 유저가 늘어나면 늘어날수록 성능 차이가 나는데 이 이유는 MVC방식은 서버의 하드웨어 성능으로 커버가 가능한 구간을 지나면 쓰레드 풀이 점점 많아지고 Queue가 쌓이게 되면서 점점 느려지게 되는 방식이다. 
    • 이를 보안하고자 만든것이 WebFlux이며 위의 나열된 장점 + 요청을 Event-Driven 방식을 통해서 처리하기때문에 엄청나게 효율적으로 서버가 구동 된다
      (가장 쉬운 예로는 구글 혹은 네이버에서 로그인 해놓고 누가 메일을 보내면 클라이언트가 요청을 하지 않았는데도 불구하고 메일을 받았다고 알려주거나 메일의 개수 숫자가 바뀐다는 등을 들 수 있다.)

     

     

     

     

    02 Reactive Programming 배경

    • 먼저 Reactive Programming의 정의는 간단하게 어떤 요청이 있으면 바로 반응을 한다 인데 이 Reactive Programming이 나오기 전에는 항상 요청 후에 응답을 기다리는 식의 프로그래밍 방식이었다.
      A는 B/C를 거치는동안 아무것도 할 수 없다. 
    • 이 방식에서의 문제는 A가 아무것도 못한다도 있지만 다른 또 하나의 문제는 C가 바뀐 시간을 B에게 알려줬는데도 불구하고 A는 요청을 하지 않았기 때문에 바뀐시간을 요청전까지는 절대로 알 수 없다는 것이다. 이 문제를 해결하기위해 나온것이 리액티브 프로그래밍 방식이다

     

     

     

     

    03 WebFlux의 탄생

    • 일단 가장 중요한 개념은 클라이언트는 2순위로두고 1순위로 서버가 다른일을 할 수 있도록 만드는것이 중요하다. 더 쉽게 말해 A가 B에게 요청하고 B가 C로 다시 요청을 했을 때 B의 서버가 다른일을 하지 못한다면 다른 클라이언트 D,E,F등이 B에게 요청을 못 하게 된다는 것이다. (B가 C의 응답을 계속해서 기다리기 때문) 
    • 기존에는 스레드를 계속해서 늘려서 시간을 분배하는 방식으로 한다거나 컨텍스 스위칭이란것을 통해 마치 동시에 여러개가 한번에 처리되도록 보이게 했었다. 이 두가지 방식다 엄청나게 오래 걸리는 단점이 있었는데 이를 보완하게 된 방식이 비동기 처리 방식.
    • 비동기 처리방식은 이벤트 루프라는 영역에서 요청된 기억을 저장 하게 되고 서버는 거기에 등록된 요청들을 끊임없이 처리하도록 설계되어있는데 기존 비동기 방식은 이벤트 루프에 저장된 시간이 지나면 응답이 끊어져버리는 단점이 있었다. 
    • 위의 배경과 단점들을 보안하기 위해 나온것이 WEB-FLUX 이며 이는 각각의 요청과 응답이 이루어질동안 서버가 멍을 못때리도록 하기위해 1)이벤트 루프 를 만들고 거기에서 끊임없이 응답이 될 수 있도록 2) 흐름을 유지 즉 Stream(flux)를 유지하도록 한다
    • WEB-FLUX 구현시에는 RDBMS(MySQL/Oracle)은 불가능하고 NoSQL을 써야 한다. 하지만 라이브러리 R2DBC를 써서 구현이 가능 하다. 

     

     

     

     

    04 WebFlux 직접 코딩 및 실행 해보기 

    • 필터와 필터 컨피규레이션 세팅
      롬복/데브툴/스프링웹 선택
      public class MyFilter implements Filter{
      
      	@Override
      	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      			throws IOException, ServletException {
      		System.out.println("필터 실행 됨!!!!!");
      	}
      }
      
      
      @Configuration
      public class MyFilterConfig {
      
      	@Bean
      	public FilterRegistrationBean<MyFilter> addFilter(){
      		System.out.println("필터 등록 됨!!!");
      		FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>(new MyFilter());
      		bean.addUrlPatterns("/*");
      		return bean;
      	}
      }

    작동이 잘 된다

     

    • 이제 여기서 MyFilter를 통해 간단한 응답 텀을 알아 보자
      public class MyFilter implements Filter{
      
      	@Override
      	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      			throws IOException, ServletException {
      		System.out.println("필터 실행 됨!!!!!");
      		
      		HttpServletResponse serveletResponse = (HttpServletResponse) response;
      		serveletResponse.setContentType("text/plain; charset=utf-8");
      		PrintWriter out = serveletResponse.getWriter();
      		
      		for (int i=0; i<5; i++) {
      			out.print("응답:"+i+" ");
      			out.flush();
      			try {
      				Thread.sleep(1000);
      			} catch (InterruptedException e) {
      				e.printStackTrace();
      			}
      		}
      	}
      }​
    • 위와 같이 세팅을 하면 서버가 시작될때마다 필터가 등록이 되고 MyFilter에서 세팅한대로 for문이 도는데 5초가 걸린다. 그 5초동안 '응답+i'을 버퍼를 쌓고 지우고를 반복 5초 뒤에 한번에 응답이 나오는것을 볼 수 있다. 
      5초간의 로딩이 있고 그 이후 응답이 온다. 
    • 이 이유는 컨텐트타입(MIME)이 'plain'이기 때문이다. 컨텐트 타입을 아래와 같이 변경 하고 테스트 해보자
      serveletResponse.setContentType("text/event-stream; charset=utf-8");​
      5초가 걸리는게 똑같지만 하나씩 응답값을 돌려 준다. 
    • 여기까지가 가장 기초적인 플럭스가 어떻게 돌아가는지를 볼 수 있다. 위에 설명했듯이 서버를 멍때리게 하지 않고 계속해서 살려서 응답시간내에 모든 응답을 하나씩 하나씩 계속해서 끊기지않고 하도록 한다. 

     

     

     

     

     

    05 SSE 구조 알고 넘어가기

    • 위에서 플럭스가 뭔지 간단하게 맛을 보았다. 이제 SSE(응답을 계속 유지 하는 방식)를 flux에 추가하여 응답을 계속해서 유지해보자 
      빨간 구간이 SSE 구간이 되고 위에서 언급했듯이 SSE 구간을 통해 응답을 계속 유지시키고 이 응답이 유지되는 동안 다른 처리를 받아보도록 할 예정!

    모든 응답 이후에도 계속 서버가 돌아가는것을 볼 수 있다

     

    • 이제 서버가 계속 돌아가는 상태에서 데이터를 받아서 '응답:4' 이후에 찍히도록 해보자. 세팅은 아래와 같다

    @Component
    public class EventNotify {
    	
    	private List<String> events = new ArrayList<String>();
    	private boolean change = false;
    	
    	public void add(String data) {
    		events.add(data);
    		change = true;
    	}
    	
    	public boolean getChagne() {
    		return change;
    	}
    	
    	public void setChange(boolean change) {
    		this.change = change;
    	}
    	
    	public List<String> getEvents(){
    		return events;
    	}
    }
    public class MyFilter2 implements Filter{
    	
    	private EventNotify eventNotify;
    	
    	public MyFilter2(EventNotify eventNotify) {
    		this.eventNotify = eventNotify;
    	}
    
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		System.out.println("필터2 실행 됨!!!!!");
    		eventNotify.add("새로운 데이터!!");
    	}
    }
    • MyFilter2에서 데이터를 계속해서 넘기는 역할

     

     

    • MyFilterConfig에서 주소에따라 다른 요청들을 받도록 세팅. 
    @Configuration
    public class MyFilterConfig {
    	
    	@Autowired
    	private EventNotify eventNotify;
    
    	@Bean
    	public FilterRegistrationBean<MyFilter> addFilter(){
    		System.out.println("필터 등록 됨!!!");
    		FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>(new MyFilter(eventNotify));
    		bean.addUrlPatterns("/sse");
    		return bean;
    	}
    	
    	@Bean
    	public FilterRegistrationBean<MyFilter2> addFilter2(){
    		System.out.println("필터 등록 됨!!!");
    		FilterRegistrationBean<MyFilter2> bean = new FilterRegistrationBean<>(new MyFilter2(eventNotify));
    		bean.addUrlPatterns("/add");
    		return bean;
    	}
    }

     

     

    • 이제 주소창에서 '/add' 로 넘어 올때 서버가 계속 돌아가고 있으니 데이터를 받아 보자 

     

    → 테스트

    • 요청에따라 서버는 응답을 해주고 그 요청에대한 응답이 끝나도 서버는 계속 돈다. 거기에 '/add'라고 요청을 하면 응답이 끝나도 서버가 돌기때문에 계속해서 요청 받고 응답이 나가고 반복이 되는 것. 

    여러개를 계속해서 서버가 유지되면서 응답이 되는것을 볼 수 있다

     

    • 여기까지 보면 서버가 계속해서 유지되기때문에 '/sse'라는 요청이 여러곳에서 와도 계속해서 끝 데이터가 쌓이고 응답되고 하는것을 볼 수 있다. 끝 데이터만 받는 것을 '합', 전체데이터를 한번에 받는것을 '콜드' 라는 개념들이 있는데 이런것들을 어떻게 만들지, 어떠한 복잡한 개념이 들어가는지는 라이브러리를 통해서 해결이 가능 하다.

     

    • 정리를 하자면 MVC에서도 RESPONSE를 유지 할 수 있다. 다만 MVC는 멀티스레드 기반이기 때문에 응답이 유지되면 스레드가 종료되지않고 트래픽양이 늘어나는 단점이 있다. 즉 단일 스레드에서 비동기로 사용한다면 더 효율적으로 구동이 가능하다. 
    • 응답의 유지 범위는 데이터 크기만큼만 지속적으로 유지 혹은 무한으로 유지하는 방식이 있는데 그것을 각각 Flux/SSE 라고 부른다. 
    • 또한 직접 코드를 구현해보면 알겠지만 절대로 좋은 코드가 아니다. 최적화가 되어 있지도 않다. 이것을 라이브러리로 해결 가능 하고, 인터페이스만 공부하면 쉽게 쓸 수 있다.
    • 해당라이브러리를 통해 '백프레셔'라는게 가능해지는데 이는 100개의 대한 응답을 10개로 나눠서 응답하던지 한번에 100개를 동시에 응답하는지 등의 컨트롤리 가능해 진다. 또한 몇개의 응답만 받는다던지의 여러 설정들이 가능해 진다. 

     

    즉 Web-flux란?

    1. reactive-streams의 구현체이다.

    2. 단일스레드 비동기 방식이다.

    3. Flux를 쉽게 사용할 수 있다.

    4. 물론 SSE도 적용하면 사용가능하다.

     

     

Designed by Tistory.