[Springboot] Multithread 비동기 처리 TEST

728x90

Java 언어 에서의 멀티 스레드

Java에서의 Thread 와 Spring에서의 Thread에는 차이가 있다.

Java

자바에서 멀티 스레딩은 java.lang.Thread 클래스를 사용하여 구현된다. Thread 클래스를 상속받거나 Runnable 인터페이스를 구현하여 스레드를 생성하고 실행할 수 있는데, 스레드의 우선순위 및 동기화 등을 수동으로 관리할 수 있다.

Spring

스프링은 자바의 Thread 클래스를 직접 사용하지 않고, 스레드 풀(스레드를 미리 여러 개 만들어 둔 것)을 사용하여 멀티 스레딩을 지원한다. 스프링에서는 TaskExecutor 인터페이스를 사용하여 스레드 풀을 생성하고 사용할 수 있으며, 해당 인터페이스를 구현하여 직접 스레드 풀을 관리할 수도 있다. 또한, @Async 어노테이션을 이용하여 비동기적으로 실행되는 메서드를 간편하게 만들 수 있다.

 


💡 웹 에서 멀티 스레드를 사용하는 이유가 뭘까?

이 질문에 먼저 대답을 할 수 있어야지, 잘 사용을 할 수 있을 것이라 생각한다.

 

🤷‍♂️

저는 웹 프로젝트에서 전체적인 게시판을 담당하였습니다.

처음 게시판 버튼을 클릭시, 게시판으로 페이지가 넘어가야 했습니다.

게시판 버튼을 클릭시, 게시판에 출력될 DB데이터가 많아 시간이 오래 걸리는 현상을 겪었다.

그래서 이러한 문제를 해결하기 위해 검색을 해보다가, 비동기 처리 라는 방식을 알게 되었다.

내가 아는 비동기방식은 Front 단에서 Ajax로 form 데이터를 처리하는 것이였는데,

Spring에서는 서버에서 비동기처리를 통하여 출력하는 방식이 있다는 것을 알게 되었습니다.

 

그래서 멀티 스레드를 사용하여 비동기 처리를 하는 방식을 알아보았고

이 방법에 대한 장점은 이렇게 서술할 수 있습니다.

  1. 성능 향상: 여러 스레드를 사용하면 여러 작업을 동시에 수행하여 전체적인 성능을 향상시킬 수 있습니다.
  2. 응답성 향상: 멀티 스레드를 사용하면 한 스레드에서 블록되는 동안에도 다른 스레드가 계속 실행되므로 응답성이 향상될 수 있습니다. 사용자와의 상호작용이나 실시간 이벤트 처리에 유용합니다.
  3. 자원 효율성: 여러 스레드는 동일한 프로세스 내에서 메모리 등의 자원을 공유할 수 있기 때문에 자원을 효율적으로 사용할 수 있습니다.

→ 그 중에서도 프로세스에 대하여 알아야 했습니다

내가 게시판을 클릭하면 ( 클라이언트 요청→ 서버에서 응답 처리 → http를 통한 응답 반환 )

이런 구조로 진행이 되는데, 그렇다면 이 과정에서 클라이언트의 요청을 빠르게 하기 위해서는

‘서버에서 응답 처리’ 를 빠르게 하는 방법이 중요하다고 생각하였다.

클라이언트가 게시판 버튼을 클릭 시 여러 프로세스가 실행이 되며, OS가 CPU할당을 통하여 처리를

해줘야 할 것이다. 그리고 프로세스 내에 여러 스레드가 있을텐데 이것을 적절하게 분배를 통하여

빠르게 처리할 수 있는 방법이 있을 것이다.

 

 

🎫 게시판으로 페이지가 이동 할 때 이 4가지의 프로세스가 실행이 된다고 생각을 했습니다.

  1. 브라우저 프로세스:
    • 사용자가 브라우저를 통해 웹 페이지를 요청하고 응답을 받는 프로세스입니다.
  2. WS 프로세스:
    • 클라이언트의 HTTP 요청을 받아들이고, 웹 애플리케이션 서버(WAS)로 요청을 전달하는 등의 역할을 하는 프로세스입니다.
  3. WAS 프로세스:
    • 웹 애플리케이션을 실행하고, 클라이언트의 요청에 따라 해당 요청을 처리하는 프로세스입니다. 여기서 Spring 등의 백엔드 프레임워크에서는 스레드 풀을 이용하여 여러 스레드를 생성하여 요청을 처리할 수 있습니다.
  4. 데이터베이스 프로세스:
    • 게시판과 관련된 데이터를 가져오거나 업데이트하는 등의 데이터베이스 관련 작업을 처리하는 프로세스입니다.

이 중에서 제가 개선해야 할 프로세스는 WAS프로세스와 데이터베이스 프로세스라고 생각했습니다.

그래서 이 프로세스를 개선할 방법으로 스프링부트에서 비동기처리 방식을 통한 멀티스레드 활용을 도입해 보았습니다.

 

 

❓❓❓ 그래서 비동기 처리 방식은 뭘까?

간단하게 말하면 앞선 작업이 끝나기를 대기하며 수행(동기적수행)되는게 아닌,

앞선 작업의 종료까지 대기하지 않고, 다음 작업을 바로 수행(비동기)한다는 의미이다.

 

원래는 프로세스가 실행이 다 끝날 때 까지 기다리고 다음 프로세스가 실행이 되어야 하는데, 비동기 처리 방식을 이용하면 기다리지 않고, 바로바로 처리할 수 있다.

구글에 아주 좋은 예시 사진이 있어서 첨부를 하였습니다.

동기처리는 45sec , 비동기 처리는 20sec → 25초의 차이가 나는 걸 확인할 수 있습니다.

 


 

바로 스프링 부트에서 비동기 처리 방식에 대하여 알아보겠습니다.

 

💡 @Async

 

🔔 0단계)

@Async Spring에는 이러한 Annotation이 있다.

이 Annotation을 비동기처리할 메소드에 선언하면 비동기적으로 동작하게 구현을 할 수 있다.

 

🔔 1단계)

자바에서 Thread를 상속받거나 Runnable 인터페이스를 상속받으며 추가 구현하는 것과 다르게

스프링부트 프로젝트 시작 클래스에 **어노테이션(@EnableAsync)**만 붙이면 끝이다.

@EnableAsync(proxyTargetClass=true)
@SpringBootApplication
public class FinalProjeceZ1oneApplication {

	public static void main(String[] args) {
		SpringApplication.run(FinalProjeceZ1oneApplication.class, args);
	}

}

 

🔔 2단계)

내가 비동기 처리하고 싶은 메소드로 가서 @Async 를 붙여주면 된다.

스프링 MVC 방식이라서 저는 인터페이스를 생성하고, 구현하는 방식으로 해보겠습니다.

public List<User_BoardDto> geyAllList();
@Async
   @Override
   public List<User_BoardDto> geyAllList() {
			//비동기적으로 실행될 메소드
      return boardMapperInter.geyAllList(); 
   }

이 메소드는 간단하게 User_BoardDto에 있는 데이터를 전부 뽑는 메소드 입니다.

 

🔔 2-1단계)

사용법이 간단한 만큼 주의할 점이 있습니다

@Async 은 쓰레드를 재사용하지 않습니다.

즉 이말은 매번 실행시 새로운 쓰레드를 사용한다는 말 입니다.

쓰레드를 매번 새로 생성하는 것 은 큰 자원을 소모하기 때문에

일반적으로는 쓰레드 풀을 미리 만들어 두고 사용을 하였습니다.

 

 

 💡 Java에서 Thread pool이 뭔가요?

🖐 스레드를 효율적으로 관리하고 재사용하는 목적으로 사용되는 자원입니다.

 

Thread pool을 사용해야 하는 이유?

  1. 스레드 풀은 많은 수의 작은 작업을 처리할 때 유용하며, 스레드를 생성하고 소멸하는 비용을 줄여 전체적인 성능을 향상시킵니다.
  2. 쓰레드 재사용: 스레드 풀은 미리 정해진 개수의 스레드를 생성하고 관리합니다. 이 스레드들은 여러 작업을 순차적으로 처리하며, 작업이 완료되면 다음 작업으로 넘어갑니다. 이렇게 스레드를 재사용함으로써 스레드 생성 및 소멸에 따른 오버헤드를 줄일 수 있습니다.
  3. 작업 큐: 스레드 풀은 대기 중인 작업을 큐(Queue)에 유지합니다. 새로운 작업이 요청되면 큐에 추가되고, 스레드가 사용 가능해지면 큐에서 작업을 가져와 실행합니다.
  4. 스레드 수 조절: 스레드 풀은 동적으로 스레드의 개수를 조절할 수 있습니다. 작업 부하가 증가하면 스레드 개수를 늘리고, 작업 부하가 감소하면 스레드 개수를 줄여 최적의 성능을 유지할 수 있습니다.

Java에서는 ThreadPoolExecutor 클래스가 주로 사용됩니다. 이를 통해 스레드 풀의 생성, 관리, 작업 제출 및 작업 완료 여부 확인 등을 수행할 수 있습니다.

또한, @Async가 사용된 매서드가 종료되는지 잘 확인해야한다.

디폴트 타임아웃 설정이 되어 있지 않기 때문에, 커넥션 같은게 계속 유지되는 상태라면 쓰레드가 계속 유지된다.

 

🔔 3단계)

위 Thread pool을 다루기 위해서는 @Configuration을 통해 추가 설정을 해줘야 합니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class AsyncConfig {

	@Bean
	public ThreadPoolTaskExecutor taskExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(10); // 코어 스레드 풀 크기 설정
		executor.setMaxPoolSize(20); // 최대 스레드 풀 크기 설정
		executor.setQueueCapacity(500); // 작업 큐 용량 설정
		executor.setThreadNamePrefix("AsyncThread-"); // 스레드 이름 접두사 설정

		// 작업이 완료된 후 스레드 풀이 종료될 때까지 대기할 시간 설정 (단위: 초)
		executor.setAwaitTerminationSeconds(60); //60초 대기 설정

		executor.initialize(); //쓰레드 설정 초기화 -> 쓰레드 풀 사용가능 상태로 만듬
		return executor;
	}
}

다양한 메소드를 통해서, 쓰레드를 컨트롤 할 수있는데 기본적인 이정도만 해두었습니다.

그리고 @Async 로 정의 된 메서드는 private으로 생성하면 안되는 조건이 있습니다.

이유는 나중에 더 공부해서 포스팅을 해보도록 하겠습니다.

이 단계를 작성하면은, 비동기 처리를 하기 위한 준비는 끝났다

참고로 @Async 어노테이션이 적용된 메서드는 AsyncConfig 클래스에서 설정한 스레드 풀에서

자동으로 실행됩니다.

 

🔔 4단계)

이제 컨트롤러에서 코드를 짜고, 테스트를 해보면 되는 것이다.

@GetMapping("/community")
   public ModelAndView boardmain() {

      ModelAndView modelAndView = new ModelAndView();
			
			List<User_BoardDto> All = boardService.geyAllList();
      modelAndView.addObject("All",All);

			modelAndView.setViewName("/2/community/cmMain");
      return modelAndView;
}

 

🔔 5단계)

저는 JSP - JSTL을 사용하여 폼을 구성했습니다

<c:forEach items="${All}" var="dto" varStatus="loop">
							<p class="rank">
								<em>${loop.index + 1}</em>&nbsp;&nbsp;&nbsp;&nbsp;
								<a href="/company/detail?cnotice_num=" + ${dto.cnotice_num} 
								style="text-decoration-line: none; color: black;"</a>
							//게시판 데이터를 출력하는 로직 많음
							</p>
</c:forEach>

게시판에 있는 데이터를 모두 출력하기 위해 JSTL 문법을 사용했고, forEach 문을 사용하여 게시판 내에 데이터를 출력하였습니다.

 

실행을 해보닌까 평소랑 속도차이가 크게 안나는걸 확인했습니다.

확인방법은 : 스프링부트 실행 IDE인 인텔리제이 console창을 통하여 속도 확인을 하였습니다.

 

 💡 왜 그럴까?

 

생각을 해보니 제가 비동기를 잘 이해하지 못하고 사용하였고,

다른 문제점이 있었습니다.

DB에 데이터가 너무 적었다고 생각합니다

비동기적 실행을 위해서는 불규칙한 데이터를 출력해야하는데, 나는 아주아주 정직한 순차적인 데이터를 출력했기 때문이다.

저는 스프링이 내부적으로 관리하는 ThreadPoolTaskExecutor 를 사용하였습니다.

 

스프링이 관리하는 ThreadPoolTaskExecutor 이 아닌 톰캣 스레드 풀을 사용해야 했을 것 같습니다

 

톰캣은 웹 애플리케이션의 서블릿을 처리하기 위한 스레드 풀을 관리하고 있습니다. 스프링이 제공하는 @Async 어노테이션과 **ThreadPoolTaskExecutor**를 사용하면, 스프링은 자체적으로 비동기 작업에 사용할 스레드 풀을 만들어서 사용하게 됩니다. 이는 톰캣이 제공하는 스레드 풀과는 다릅니다.

 

톰캣의 스레드 풀과 스프링의 **ThreadPoolTaskExecutor**는 서로 독립적으로 동작하며, 각각의 역할에 맞게 사용됩니다.

톰캣 스레드 풀은 주로 HTTP 요청을 처리하고, 스프링이 제공하는 스레드 풀은 애플리케이션 내에서 발생하는 비동기 작업을 처리하는 데 사용됩니다.

 

클라이언트 요청을 처리할 때는 톰캣 스레드 풀을 사용해야 했다고 생각합니다.

추후에 2탄을 만들어, 다시 글을 포스팅 해볼 예정 입니다.

 

댓글로 틀린 부분이 있다면 알려주세요. 지적 감사하게 받겠습니다.

이상 포스팅 마치겠습니다.

 

📘 참고 글

https://akku-dev.tistory.com/89

 

https://sihyung92.oopy.io/spring/1

728x90