스프링부트에서 Async를 사용할 일이 생겼다. 대략적인 비즈니스 로직은 다음과 같다.
1. Scheduler를 통해 DB를 Select 함 2. 대략 1500~2000건의 데이터를 A 서버에 HttpConnection을 이용하여 Request/Response를 받아야 함 3. 최대한 빨리 |
일단 가장 심플하게 생각하자면 @Scheduled를 이용, DB를 쿼리한 후 해당 List를 for문으로 돌리면서 A서버로 httpConnection을 이용하면 될 것 같고 실제로 그렇게 구현했다. 하지만 Request/Response의 응답시간이 건당 약 3초가 소요되며, 전체적으로 건수가 많기에 1500 * 3 = 4500초가 걸리는 초유의 사태가 발생했다. 그래서 결국엔 Async를 이용해 전체로직을 다 뜯어고치게 되었다. 내게 이렇게 오래걸리면 안된다고 왜 미리 말을 안해준거니. 하루에 두 번만 수행하면 된다매!
기본적으로 다음과 같이 소스를 구성했다.
1. Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import org.springframework.scheduling.annotation.Scheduled;
// 중략
@Autowired
HttpConnectorUtil httpConnUtil;
@Scheduled(fixedDelay = 300000)
public void dbSelectScheduler() throws InterruptedException {
logger.info("dbSelectScheduler Start!");
JSONObject queryObj = new JSONObject();
try {
// DB 쿼리부. 대충 1000개의 사이즈를 가진 List라고 치자
List<HashMap<String, Object>> targetList = dbService.selectTargetQuery(queryObj);
for ( int a = 0 ; a < targetList.size(); a++){
Thread.sleep(300); //너무 빨리 요청하면 서버가 못버팀
httpConnUtil.HttpConnection("http://localhost:8080/localTest", targetList.get(a).toString());
}
} catch {
e.printStackTrace();
logger.info("ReqService ERROR! {}" , e.getMessage());
}
logger.info("dbSelectScheduler End!");
}
|
cs |
여기에서는 별다른 로직이 없다. DB에서 쿼리한 갯수만큼 for문을 돌리고, for 내부에서는 지정된 URL에 대해 httpConnector 요청을 날린다.
2. AsyncConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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 선언 필수
@Configuration
// EnableAsync 선언 필수. 여기서 해당 플젝의 AsyncConfigurer를 공통선언/사용
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor(){
ThreadPoolTaskExecutor asyncExecutor = new ThreadPoolTaskExecutor();
asyncExecutor.setCorePoolSize(10); //최소 동작하는 Async Thread 수
asyncExecutor.setMaxPoolSize(20); //최대 동작하는 Async Thread 수
asyncExecutor.setQueueCapacity(100); // 최대 동작 Thread를 초과할 시, 보관되는 Queue의 수
asyncExecutor.setThreadNamePrefix("TESTAsync-"); // Async Thread가 동작할 때 표시되는 Thread Name
asyncExecutor.initialize(); //위 설정 적용
return asyncExecutor;
}
}
|
cs |
현재 프로젝트에서 공통적으로 사용하는 AsyncConfig이다. 보다 자세한 내용은 아래의 블로그에서 확인이 가능하다. 해당 블로그를 통해 많이 배웠다. https://www.hanumoka.net/2020/07/02/springBoot-20200702-sringboot-async-service/
여튼, 기본적으로 숙지해야 할 사항은 이번 플젝은 언제든 Thread의 수가 변할 수 있음을 감안하고 setCorePoolSize, setMaxPoolSize는 application.yml에서 가져올 수 있도록 수정했다. 현재는 위 설정대로 1500건에 대해 300ms의 thread.sleep을 준 상태로 요청을 하며, 아직까지는 문제없이 2500건까지 수행하였다.
@EnableAsync를 통해 프로젝트에서 공통적으로 사용할 AsyncExecutor를 설정한다. 이는 다른 AsyncService에서도 사용하므로 자신의 프로젝트 상황을 고려하여 설정값을 변경하면 된다.
3. AsyncService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import org.springframework.scheduling.annotation.Async;
//다른 import들은 생략
// Service 에서만 Async 어노테이션이 정상동
@Service
public class AsyncService {
//Async를 사용할 때에는 아래의 @Async Annotation을 선언
@Async
public JSONObject HttpConnection(String url , String strJson) throws Exception{
//대충 httpconnection에 필요한것들 선언, 생략
try {
URL url = new URL(url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//conn에 해당하는 내용 생략
OutputStream = os = conn.getOutputStream();
os.write(strJSON.getBytes("UTF-8"));
os.flush();
//리턴내용 생략
} catch (Exception e) {
e.getMessage();
}
return jsonResult;
}
}
|
cs |
실질적으로 Async요청을 하는 부분이다. controller에서는 AsyncService(@Service)를 호출하여, @Async 로 선언된 HttpConnection을 사용하게 된다. 이로써 Http를 호출하는 부분은 Async로 동작하게 된다.
실행 로그는 다음과 같다.
1
2
3
4
5
6
|
2021-06-21 16:39:41.300 INFO 2068 --- [ TEST-Async-3] com.example.AsyncService: [HttpConnectorResult] 1
2021-06-21 16:39:41.400 INFO 2068 --- [ TEST-Async-1] com.example.AsyncService : [HttpConnectorResult] 22
2021-06-21 16:39:41.510 INFO 2068 --- [ TEST-Async-2] com.example.AsyncService : [HttpConnectorResult] 4
2021-06-21 16:39:41.997 INFO 2068 --- [ TEST-Async-10] com.example.AsyncService : [HttpConnectorResult] 11
2021-06-21 16:39:42.010 INFO 2068 --- [ TEST-Async-7] com.example.AsyncService : [HttpConnectorResult] 3331
2021-06-21 16:39:42.314 INFO 2068 --- [ TEST-Async-5] com.example.AsyncService : [HttpConnectorResult] 51
|
cs |
처음에는 AsyncConfig의 설정 방법 등을 몰라 @Async를 선언해준 class에서 위의 executor를 죄다 세팅해주었는데, 이 포스트를 작성하면서 샘플소스를 만들어보니 공통으로 사용가능하단 점을 깨닫게 되었다.
'어장 Develop > 어장 JAVA' 카테고리의 다른 글
[xml] RestAPI XML Return (2) (0) | 2021.07.06 |
---|---|
[xml] RestAPI XML Return (0) | 2021.07.02 |
[SpringBoot] fixed delay, fixed rate의 차이 (0) | 2021.06.23 |
[SpringBoot] application.yml에서 항목 가져오는 방법 (0) | 2021.06.21 |
[JAVA] Enum으로 JSON Parameter Key 관리 (0) | 2021.02.22 |