Skip to content

Commit 8a05639

Browse files
authored
♻️ Refactor - 로깅 개선 및 워커 재시도 로직을 개선한다
♻️ Refactor - 로깅 개선 및 워커 재시도 로직을 개선한다
2 parents bf07e49 + f55c854 commit 8a05639

2 files changed

Lines changed: 51 additions & 88 deletions

File tree

src/main/java/sopt/comfit/report/job/AIReportJobWorker.java

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import jakarta.annotation.PreDestroy;
55
import lombok.RequiredArgsConstructor;
66
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.dao.QueryTimeoutException;
78
import org.springframework.data.redis.core.StringRedisTemplate;
89
import org.springframework.stereotype.Component;
910
import sopt.comfit.global.constants.Constants;
@@ -18,56 +19,93 @@
1819
import sopt.comfit.report.service.AIReportQueryService;
1920

2021
import java.time.Duration;
21-
import java.util.ArrayList;
2222
import java.util.List;
23+
import java.util.concurrent.CopyOnWriteArrayList;
24+
import java.util.concurrent.atomic.AtomicBoolean;
2325

2426
@Slf4j
2527
@Component
2628
@RequiredArgsConstructor
2729
public class AIReportJobWorker {
2830

31+
private static final int WORKER_COUNT = 2;
32+
2933
private final StringRedisTemplate redisTemplate;
3034
private final AIReportJobService reportJobService;
3135
private final AIReportQueryService aiReportQueryService;
3236
private final AIReportCommandService aiReportCommandService;
3337
private final RetryableAiCallerService aiCaller;
3438

35-
List<Thread> workers = new ArrayList<>();
39+
private final AtomicBoolean running = new AtomicBoolean(false);
40+
List<Thread> workers = new CopyOnWriteArrayList<>();
3641

3742
@PostConstruct
3843
public void startWorker() {
39-
int workerCount = 2;
40-
workers = new ArrayList<>();
41-
for (int i = 0; i < workerCount; i++) {
42-
Thread t = Thread.ofVirtual()
43-
.name("report-job-worker-" + i)
44-
.start(this::listen);
45-
workers.add(t);
44+
running.set(true);
45+
workers = new CopyOnWriteArrayList<>();
46+
for (int i = 0; i < WORKER_COUNT; i++) {
47+
spawnWorker(i);
4648
}
47-
log.info("ReportJobWorker {}개 시작", workerCount);
49+
log.info("ReportJobWorker {}개 시작", WORKER_COUNT);
4850
}
4951

5052
@PreDestroy
5153
public void stopWorker() {
54+
running.set(false);
5255
workers.forEach(Thread::interrupt);
5356
log.info("ReportJobWorker 종료");
5457
}
5558

59+
private void spawnWorker(int index) {
60+
Thread t = Thread.ofVirtual()
61+
.name("report-job-worker-" + index)
62+
.start(() -> guardedListen(index));
63+
workers.add(t);
64+
}
65+
66+
/**
67+
* listen()을 감싸서 Error 포함 Throwable이 발생해 스레드가 죽어도
68+
* running 상태면 자동으로 재시작한다.
69+
*/
70+
private void guardedListen(int index) {
71+
while (running.get()) {
72+
try {
73+
listen();
74+
} catch (Throwable t) {
75+
if (!running.get()) break;
76+
log.error("Worker-{} 치명적 오류로 종료, 3초 후 재시작", index, t);
77+
sleep(3);
78+
}
79+
}
80+
log.info("Worker-{} 정상 종료", index);
81+
}
82+
5683
private void listen() {
57-
while (!Thread.currentThread().isInterrupted()) {
84+
while (!Thread.currentThread().isInterrupted() && running.get()) {
5885
try {
5986
String jobId = redisTemplate.opsForList()
6087
.rightPop(Constants.JOB_QUEUE_KEY, Duration.ofSeconds(30));
61-
6288
if (jobId == null) continue;
63-
6489
processJob(Long.parseLong(jobId));
90+
91+
} catch (QueryTimeoutException e) {
92+
log.debug("BRPOP timeout, 재시도");
93+
6594
} catch (Exception e) {
6695
log.error("Worker 루프 에러", e);
96+
sleep(3); // Redis 장애 시 스핀 방지
6797
}
6898
}
6999
}
70100

101+
private void sleep(int seconds) {
102+
try {
103+
Thread.sleep(Duration.ofSeconds(seconds));
104+
} catch (InterruptedException e) {
105+
Thread.currentThread().interrupt();
106+
}
107+
}
108+
71109
private void processJob(Long jobId) {
72110

73111
try {

src/main/resources/logback-spring.xml

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)