비동기 처리

최혜원·2023년 8월 2일
0

Spring

목록 보기
19/19
post-thumbnail

데이터 처리 모델을 쉽게 표현하자면 데이터를 받는 방식이라고 할수 있습니다.
이 방식에는 동기식 처리와 비동기식 처리 모델이 존재합니다.
이 둘의 개념에 대해 알아보고 둘의 차이점을 알아보겠습니다.

https://velog.velcdn.com/images%2Fslobber%2Fpost%2F0722db09-f9f9-4f61-8708-4e9d53924fee%2F%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3%20(1).jpeg

💡동기(synchronous)

  • 동기는 데이터의 요청과 결과가 한 자리에서 동시에 일어나는것을 말합니다.

요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져야 합니다.

사용자가 데이터를 서버에게 요청한다면 그 서버가 데이터 요청에 따른 응답을 사용자에게 다시 리턴해주기 전까지 사용자는 다른 활동을 할 수 없으며 기다려야만합니다.

ex)

브라우저를 실행시키는 시간이 약 10분이 소모된다고 가정했을시, 브라우저가 실행되는10분이라는 시간 동안 사용자는 컴퓨터💻의 다른 프로그램들을동작시키지 못하며 브라우저가 켜지는 그 순간만을 계속 기다려야한다는 것입니다. 이 시간이 10분이든 100분이든 관계없이 한 개의 데이터 요청에 대한 서버의 응답이 이루어질 때까지 계속 대기해야만합니다.


🕯 비동기(Asynchronous)

  • 비동기는 동시에 일어나지 않는다는 의미입니다. 요청한 결과는 동시에 일어나지 않을거라는 약속입니다.

서버에게 데이터를 요청한 후 요청에 따른 응답을 계속 기다리지 않아도되며 다른 외부 활동을 수행하여도되고 서버에게 다른 요청사항을 보내도 상관없습니다

Spring webflex → 비동기

동기와 비동기의 장단점

💡 동기

장점 : 설계가 매우 간단하고 직관적이다.

단점 : 결과가 주어질 때까지 아무것도 못하고 대기해야 합니다.


🕯 비동기

장점 : 요청에 따른 결과가 반환되는 시간 동안 다른 작업을 수행할 수 있습니다.

단점 : 동기식보다 설계가 복잡합니다.

Block vs Non-Block

👀 블록 과 논블럭의 차이

블록 과 논블럭은 동기, 비동기와 연결되는 개념입니다.

🧱 블록이란?

  • 블록이란 동기의 개념에서 만들어진 상태로

브라우저가(크롬) 실행되는 시간이 10분(요청)이라고 가정했을시에

브라우저가(크롬) 이 오픈(결과) 되기전까지 다른 브라우저(사파리 등등)은 계속 대기해야 하는 상태를 블록 상태라고 부릅니다.


⚙️ 논블록이란?

  • 논블록이란 비동기 개념에서 만들어진 상태로

브라우저가(크롬) 실행되는 시간이 10분(요청)이라고 가정했을시에

브라우저가(크롬) 이 오픈(결과) 되기전까지 다른 브라우저(사파리 등등)은 시간을 대기 하지 않고 제약없이 자유롭게 사용할수 있는 상황을 논블록 상태 라고 부릅니다.


📝 블럭과 논블럭 정리

  • 블록이란 동기의 개념에서 만들어진 상태
  • 논블록이란 비동기 개념에서 만들어진 상태

!https://blog.kakaocdn.net/dn/s4GhX/btry69JJgmj/LkjtlqXss4xSsMQUCnjx21/img.png

내 서비스가 지금 블락 방식

→ 타 서비스 * DB 호출 시에 응답이 올 때까지 기다린다

내 서비스가 지금 비동기 방식

→ 내 서비스는 바로 상태 응답을 내어주고, 나중에 처리한 후에 응답을 전송하는 형태

API Thread

→ thread 비동기 작업 하나 당 한 개씩

크롬 찾았다

다운로드는 받잖아요

다운로드를 받는 동안에 브라우저를 사용할 수 있쬬?

블럭 → 막는다

브라우저는 논블럭

크롬

여러분들이 메일 서비스를 만들다고 해볼께요

메일을 보내고 보내여 → 비동기

메일을 잘 받았는 지 안받는 지는 따로 status 조회 API → 읽었는 지 확인

Spring Async

@Async

@Async Annotation은 Spring에서 제공하는 Thread Pool을 활용하는 비동기 메소드 지원 Annotation이다.

기존 Java에서 비동기 방식으로 메소드를 구현할 때는 아래와 같이 구현할 수 있었다.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class GillogAsync {

    static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public void asyncMethod(final String message) throws Exception {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                // do something
            }
        });
    }
}

java.util.concurrent.ExecutorService을 활용해서 비동기 방식의 method를 정의 할 때마다,

위와 같이 Runnable의 run()을 재구현해야 하는 등 동일한 작업들의 반복이 잦았다.

With @Async

@Async Annotation을 활용하면 손쉽게 비동기 메소드 작성이 가능하다.

만약 Spring Boot에서 간단히 사용하고 싶다면, 단순히 Application Class에 @EnableAsync Annotation을 추가하고,

@EnableAsync
@SpringBootApplication
public class SpringBootApplication {
    ...
}

비동기로 작동하길 원하는 method 위에 @Async Annotation을 붙여주면 사용할 수 있다.

public class GillogAsync {

    @Async
    public void asyncMethod(final String message) throws Exception {
        ....
    }
}

위와 같은 사용은 간단하지만 @Async의 기본설정인 SimpleAsyncTaskExecutor를 사용한다.

!https://velog.velcdn.com/images%2Fgillog%2Fpost%2F37347b0e-42d5-47db-ad12-4267fa101838%2Fimage.png

본인의 개발 환경에 맞게 Customize하기에는 직접 AsyncConfigurerSupport를 상속받는 Class를 작성하는 것이 좋다.

AsyncConfigurerSupport

아래와 같은 AsyncConfigurerSupport를 상속받는 Customize Class를 구현하자.

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); -> 기다리는 스레드 -> 비동기용 스레드
        executor.setMaxPoolSize(30);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("DDAJA-ASYNC-");
        executor.initialize();
        return executor;
    }
}

여기서 설정한 요소들은 아래와 같다.

  • @Configuration : Spring 설정 관련 Class로 @Component 등록되어 Scanning 될 수 있다.
  • @EnableAsync : Spring method에서 비동기 기능을 사용가능하게 활성화 한다. → Application or config
  • CorePoolSize : 기본 실행 대기하는 Thread의 수**
  • MaxPoolSize : 동시 동작하는 최대 Thread의 수 → 동작이 부하가 있으면 thread
  • QueueCapacity : MaxPoolSize 초과 요청에서 Thread 생성 요청시,해당 요청을 Queue에 저장하는데 이때 최대 수용 가능한 Queue의 수,Queue에 저장되어있다가 Thread에 자리가 생기면 하나씩 빠져나가 동작
  • ThreadNamePrefix : 생성되는 Thread 접두사 지정

위와 같이 작성한 후 비동기 방식 사용을 원하는 method에 @Async Annotation을 지정해주면 된다.


주의사항

@Async Annotation을 사용할 때 아래와 같은 세 가지 사항을 주의하자.

  1. private method는 사용 불가 → AOP → Spring proxy 객체
  2. self-invocation(자가 호출) 불가, 즉 inner method는 사용 불가
  3. QueueCapacity 초과 요청에 대한 비동기 method 호출시 방어 코드 작성

위 주의사항을 아래 사진과 함께 설명을 해보면,

!https://velog.velcdn.com/images%2Fgillog%2Fpost%2F5bb64a29-5263-4fcc-9f02-cffea4162137%2Fimage.png

출처 : https://dzone.com/articles/effective-advice-on-spring-async-part-1

@Async의 동작은 AOP가 적용되어 Spring Context에서 등록된 Bean Object의 method가 호출 될 시에,

Spring이 확인할 수 있고 @Async가 적용된 method의 경우 Spring이 method를 가로채 다른 Thread에서 실행 시켜주는 동작 방식이다.

이 때문에 Spring이 해당 @Async method를 가로챈 후, 다른 Class에서 호출이 가능해야 하므로,

private method는 사용할 수 없는 것이다.

또한 Spring Context에 등록된 Bean의 method의 호출이어야 Proxy 적용이 가능하므로,

inner method의 호출은 Proxy 영향을 받지 않기에 self-invocation이 불가능하다.

위 주의사항을 아래 예시 코드와 함께 살펴보자

self-invocation(자가 호출) 불가

위에서 작성한 AsyncConfig를 사용하는 Spring Project에서 아래와 같이,

같은 Class에 존재하는 method에 @Async Annotation을 작성해 비동기 방식을 사용해보자.

@Controller
public Class TestController {

    @Async
    public void asyncMethod(int i) {
        try {
            Thread.sleep(500);
            log.info("[AsyncMethod]"+"-"+i);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }

    @GetMapping("async")
    public String testAsync() {
        log.info("TEST ASYNC");
        for(int i=0; i<50; i++) {
            asyncMethod(i);
        }
        return "";
    }
}

!https://velog.velcdn.com/images%2Fgillog%2Fpost%2F925c2314-0f9a-43b6-978b-bb5f6eef6dd7%2Fimage.png

작동 결과를 보면 비동기 방식으로 호출되지 않았고, 동기적으로 호출 순서대로 동작하는 것을 확인할 수 있다.

자가 호출에서는 @Async 사용이 불가하다.

하지만, @Service로 Bean 등록된 Service를 통해 주입하여 위 코드를 다시 작성해보면,

@Service
public class TestService {
    @Async
    public void asyncMethod(int i) {
        try {
            Thread.sleep(500);
            log.info("[AsyncMethod]"+"-"+i);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

@AllArgsConstructor
@Controller
public Class TestController {

    private TestService testService;

    @GetMapping("async")
    public String testAsync() {
        log.info("TEST ASYNC");
        for(int i=0; i<50; i++) {
            testService.asyncMethod(i);
        }
        return "";
    }

}

!https://velog.velcdn.com/images%2Fgillog%2Fpost%2F97f0ec1f-c0f3-4d2f-b539-c573524b6707%2Fimage.png

위 사진과 같이 호출 순서에 상관없이 비동기 방식으로 method가 호출 되었고,

AsyncConfig에서 prefix로 작성한 접두사도 정상적으로 붙은 것을 확인할 수 있다.

profile
어제보다 나은 오늘

0개의 댓글