[Spring] Async 대해

Spring에서의 기본적인 처리는 순차적인 처리이다. 이런 방식은 bocking 현상을 야기하고 만약 덩치가 큰 비즈니스 로직을 수행해야 한다면 여러 Thread로 나누어서 처리 후 다시 결과를 합치는 과정을 거쳐야 한다.

Spring에서 Async 사용

Async process를 사용하려면 @EnableAsync 어노테이션을 붙이고 thread pool에 대한 설정을 해주어야 한다. 아래는 kotlin 문법으로 작성한 설정이다.

@Configuration
@EnableAsync
class AsyncConfig {
    @Bean
    fun taskExecutor(): TaskExecutor {

        val taskExecutor = ThreadPoolTaskExecutor()
        taskExecutor.corePoolSize = 10
        taskExecutor.setQueueCapacity(50)
        taskExecutor.maxPoolSize = 30
        taskExecutor.setTaskDecorator(LoggingTaskDecorator())

        return taskExecutor
    }
}

corePoolSize 는 최초 가용 thread 개수이다.

QueueCapaCity 는 최대로 대기할 수 있는 task 개수를 의미 한다.

maxPoolSize 는 Queue에 있는 대기열이 찼을경우 가용 thread 개수의 최대치를 설정한다.

TaskDecorator 는 함수 호출 되기전에 호출되는 녀석이라고 보면 된다

(Asnyc로 동작하면 새 ThreadContext로 바뀌므로 이전 Context를 복사하는 역할을 함. 참고 블로그)

위 방법외에도 xml파일로도 설정할 수 있는데 개인적으로 비추이다.

<task:executor id="taskExecutor" pool-size="10" />
<task:annotation-driven executor="taskExecutor"/>

Spring Async는 두가지 모드를 지원한다. Fire and forget modeResult retrieval mode.

아래 코드는 두가지 방식에 대하여 간단히 구현한 코드이다.

    // **Fire and forget mod** 구현, 무거운 job를 호출전 단계에서 쪼개여 실행 가능
    @Async("taskExecutor")
    fun updateUser(userPasswordDTO: UserPasswordDTO){
        Thread.sleep(1000)
        var user = userRepository.findOne(user.name.eq(userPasswordDTO.name)).get()
        //이전 암호 체크와 새 비번으로 수정
        if(user!=null){
                if(user.password==userPasswordDTO.prePassword) {
                    user.password = BCrypt.hashpw(userPasswordDTO.password, BCrypt.gensalt())
                }
            }
    }
    //**Result retrieval mode**로 구현, 호출하는 쪽에서 다른 작업을 한 후 대기하여 결과를 취합 용도 사용
    @Async("taskExecutor")
    fun creatUser(userDTO: UserDTO):CompletableFuture<String>{
        var user = userRepository.findOne(user.name.eq(userDTO.name)).get()
        //이전 암호 체크와 새 비번으로 수정
        if(user!=null){
            return  CompletableFuture.completedFuture("The user name already used")
        }
        else{
            val newUser = User(userDTO.name,userDTO.password)
            return  CompletableFuture.completedFuture(newUser.toString())
        }
    }

사용자 생성과 비번 수정을 예로 코드를 작성해 보았다. 사실 이런 예를 들면 비현실적이지만 thread sleep를 이용하여 Asnc을 구현해 보았다.

 

아래는 위의 Service에 대한 Test 코드 이다.

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = ["server.port=8081"])
class OrderApplicationTests() {

    @Autowired
    private lateinit var userService: UserService

    @Test
    fun test_register_user() {
        //test creat user
        var usrDto = UserDTO("techfox","123")
        //return 이 있는 async 함수
        var future = userService.creatUser(usrDto)

        var userPasswordDTO = UserPasswordDTO("techfox","123","456")
        userService.updateUser(userPasswordDTO)

        Thread.sleep(2000)
        future.join()
    }
}

마지막 sleep은 Asnyc 기다리기를 위한 sleep이다.

 

Ref:

https://jeong-pro.tistory.com/187

https://brunch.co.kr/@springboot/267

https://jsonobject.tistory.com/233

https://brunch.co.kr/@springboot/267