티스토리 뷰


안녕하세요. 이번에는 최근 개발 중에 막혔던 부분에 관련해서 적어보고자 합니다. 그러고 보니 2월 23일 이후로 작성된 포스트가 없네요. 아래 내용 건 때문에 너무 바쁘다 보니 시간이 영 없었습니다.
아무튼 적고자 하는 내용은 Thread 에 관련 된 내용입니다. 제가 문제점이라고 생각하고 파악한 내용으로 정확히는 Thread 사용 중 BLOCK, LOCK 현상이 일어 날 시 (I/O, 외부 통신 등으로 인한) 회피 방법입니다.
회피 방법이라고 적은 이유는 제가 아래 사용한 방법이 완벽한 문제 해결 방법이 아닐 것 같아서 말이죠.
그런데 이러한 상황을 어디에 어떻게 물어야 할 지, 검색을 많이 해 보았는데 제 상황과 비슷한 처지의 문제에 대한 답변들을 많이 찾을 수 없어서 나름 해결한 방법입니다. 이 점 참고하시고 보시면 될 것 같습니다.

일단, 소스와 함께 로직 설명을 먼저 한 후 어떻게 해결 하였는지 적어보도록 하겠습니다.


- Controller A


// 목록 조회

ArrayList list = listService.getList();


if (list.size() > 0) {


​// Thread 생성

UserSearchThread[] userSearchThread = new UserSearchThread[list.size()];

Thread[] threads = new Thread[list.size()];

UserVO[] userVOArray = new UserVO[list.size()];


try {

for (int i=0; i<list.size(); ++i) {

// userVO 생성

UserVO userVO = new UserVO();


// thread 실행 완료 여부 N 설정

userVO.setThreadEndYn("N");


// 조회 key 셋팅

userVO.setUserKey(list.get(i).getUserKey());


userSearchThread[i] = new UserSearchThread[userVO];

threads[i] = new Thread(userSearchThread[i]);


// Thread 수행

threads[i].start();


userVOArray[i] = userVO;

}

} catch (Exception e) {

e.printStackTrace();

throw e;

} finally { }


int threadCount = list.size();

int endThreadCount = 0; // 종료된 Thread 카운트 (end thread count)


int errorThreadCount = 0; // 에러 Thread 카운트 (error thread count)


long timeout = 1 * 60 * 1000; // 전체 Thread 타임아웃 :: 60초 (thread total time out :: 60 seconds)


long defaultTimeOut = 30 * 1000; // 개별 Thread 타임아웃 :: 30초 (per thread time out :: 30 seconds)


long startTime = System.currentTimeMillis();


// 스레드 카운트가 종료된 카운트 보다 크고 타임아웃 전일 시

while (endThreadCount < threadCount && System.currentTimeMillis() - startTime < timeOut) {

for (int i=0; i<threadCount; ++i) {

// 종료된 Thread 경우 패스

if (userVOArray[i].getThreadEndYn("Y")) continue;


long threadStartTime = userVOArray[i].getStartTime();


// 진행 중일 경우

if (userSearchThread[i].getResultCode == 0) {

// 개별 타임아웃 체크

if (System.currentTimeMills() - threadStartTime > detaultTimeOut) {

userVOArray[i].setThreadEndYn("Y");

userVOArray[i].setEndTime(System.currentTimeMills());

endThreadCount++;

errorThreadCount++;

thread[i].interrupt();

}

} else if (userSearchThread[i].getResultCode() == 1) {

// API 호출 정상 종료. 성공인 경우

userVOArray[i].setThreadEndYn("Y");

userVOArray[i].setEndTime(System.currentTimeMills());

endThreadCount++;

thread[i].interrupt();

} else if (userSearchThread[i].getResultCode() == -1) {

// 실패한 경우

userVOArray[i].setThreadEndYn("Y");

userVOArray[i].setEndTme(System.currentTimeMills());

endThreadCount++;

errorThreadCount++;

thread[i].interrupt();

}


​​// Thread 가 살아있으며 동작중이라면 0.1 초 sleep 을 준다.

if (thread[i].isAlive()){

thread[i].sleep(100);

}

} // for

} // while

} // if



- Thread Class


private class UserSearchThread implements Runnable {


// 유저 정보 API 호출 Service

private UserApiService userApiService = (UserApiService) AbstractServiceFactory.getInstance().getServiceFactory("SPRING").getService(UserApiServiceImpl.class);

// 유저 정보 Value Object

private UserVO userVO;


// API 호출 결과

private int resultCode = 0;


// UserSearchThread 생성자

public UserSearchThread(UserVO userVO) {

this.userVO = userVO;

}


// getter resultCode

public int getResultCode() {

return resultCode;

}


// Thread 수행

public void run() {

try {

// userKey 정보로 API 를 호출하여 사용자 정보를 조회한다.

userVO = userApiService.getUserInfo(userVO.getUserKey());

} catch (OutOfMemoryError ome) {

ome.printStackTrace();

this.resultCode = -1;

} catch (Exception e) {

e.printStackTrace();

this.resultCode = -1;

}

if (userVo != null) {

this.resultCode = 1;

} else {

this.resultCode = -1;

}

}

}




자 먼저 소스는 위와 같습니다. Controller 와 ThreadClass 가 있습니다.
DB 에서 목록을 조회 한 후 목록 사이즈 만큼 thread 를 생성하여 API 를 호출 하도록 하고 정해진 시간 내에 호출이 완료 되었을 경우 스레드를 종료,
타임아웃 또는 오류가 발생하였을 시에도 오류 카운트 증가와 함께 스레드를 종료하는 로직입니다. 하나하나 적자면 아래와 같습니다.

1. 목록 조회
2. 목록 만큼 Thread 생성
3. 반복문에서는 userKey 를 UserVO 에 셋팅하고 각각의 Thread 를 실행.
4. Thread 를 외부 API 를 호출하여 사용자 정보를 조회하며 조회 성공 시 1, 그 외 -1 을 결과코드로 셋팅
5. 전체 스레드가 종료되지 않았고 타임아웃 시간이 되지 않았다면 계속해서 스레드 상태 확인.
6. 정상 코드 반환 시 스레드 종료 및 종료 스레드 카운트 증가, 오류 코드 반환 시 스레드 종료 및 스레드 카운트 증가, 오류 스레드 카운트 증가


이렇게 6 단계로 분류 될 수 있습니다.


자 그렇다면 제가 직면했던 문제에 대해 적어보도록 하겠습니다.
제 경우에는 UserSearchThread 에서 getUserInfo 메소드에서 API 를 호출 시에 DB 에 INSERT 하는 로직이 추가되어 있습니다.
(물론 회원 정보를 왜 계속 넣으시냐고 물으시겠지만 예제로 회원정보이며 진짜는 다른 정보겠죠? ^_^;;)
API 호출하여 회원 정보를 조회 한 후 DB 에 INSERT 후 스레드 종료가 되는 것이죠.
그리고 화면단에서는 화면 설계상 해당 ​DB 를 매번 조회하여 리턴해주는 ControllerB 를 Ajax 로 3초에 한번 씩 호출 하는 부분이 있습니다.
왜 이렇게 효율적이지 않게 만드셨냐고 하면 어쩔 수 없었습니다 흑흑..

아무튼 이러한 상황에 있었는 데 먼저 ​저 Thread 를 수행 시킨 후에 3초에 한번씩 ControllerB 를 호출 시에 API 통신에서 응답이 오래 걸릴 경우에
DB 조회만 하고 빠지는 ControllerB 가 API 통신이 끝날 때 까지 계속해서 대기
하고 있더라구요.
한 두개 스레드가 아니라 50개, 100개 가까운 스레드가 동시에 실행될때 말이죠.
DB INSERT 시 부하일 수도 있을 것 같아서 DB INSERT 를 빼고 하기도 해보고, 로그로 확인해 본 결과 API 통신 시 응답에서 걸리더라구요.

​아무튼 그렇게 계속 타임아웃이 걸리길래 추가한 부분이 빨간색 굵은 글씨로 추가한 부분입니다.

저렇게 현재 alive 되어있는 Thread 에 대해 0.1 초의 sleep 을 주게되면 그 사이 큰 로직 없는 ControllerB 가 싹 지나가게 되더라구요.


아무튼 이러한 방법으로 해결하였습니다.

혹시 저와 같은 현상을 겪고 계신 분들에게 작은 해결책이 되길 바라며 이 글을 보시는 다른 개발자분들께서는 좀 더 올바른,
확실한 방법이 있으시면 댓글 부탁드립니다. 가르침은 정중히, 감사히 받겠습니다.

감사합니다.

p.s 핸드폰으로 쓰다보니 띄어쓰기 하기가 매우 힘드네요. 추후 포스트는 게시글 보정을 하도록 하겠습니다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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 31
글 보관함