멀티 서버, 멀티 스레드 환경에서도 스케줄러를 하나만 실행하기 위해 Shedlock을 사용했습니다.
implementation 'net.javacrumbs.shedlock:shedlock-spring:6.10.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-redis-spring:6.10.0'
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtLeastFor = "10s", defaultLockAtMostFor = "90s") //Lock 이 유지되는 최소,최대 시간
public class SchedulingConfig {
@Value("${spring.profiles.active}")
private String profile;
@Value("${spring.application.name}")
private String appName;
@Bean
public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
String prefix = appName + ":" + profile;
return new RedisLockProvider(redisConnectionFactory, prefix);
}
}
defaultLockAtLeastFor : Lock을 유지하는 최소 시간 기본 설정
defaultLockAtMostFor : Lock을 유지하는 최대 시간 기본 설정
해당 코드는 RedisLockProvider를 사용하고 있습니다.
@Component
@RequiredArgsConstructor
@Slf4j
public class HubRouteInfoScheduler {
private final HubRepository hubRepository;
private final HubRouteInfoRepository routeInfoRepository;
private final HubService hubService;
private final HubRouteInfoRepository hubRouteInfoRepository;
//기본 설정 2시간마다
@Scheduled(
fixedRateString = "${scheduler.route-infos.fixed-rate}",
initialDelayString = "${scheduler.route-infos.initial-delay}"
)
// 멀티 서버, 멀티 스레드 환경에서도 스케줄러를 하나만 실행 / lock 유지 최소, 최대 시간
@SchedulerLock(
name = "generateMissingHubRouteInfos",
lockAtLeastFor = "${shedlock.route-infos.at-least}",
lockAtMostFor = "${shedlock.route-infos.at-most}"
)
public void generateMissingRoutes() {
log.info("[{}] HubRouteInfoScheduler started at {}", LocalDateTime.now(), Thread.currentThread().getName());
long start = System.currentTimeMillis();
int createdCount = 0;
List<UUID> hubIds = hubRepository.getActiveHubIds();
if (hubIds.size() < 2) return;
Set<RoutePairDto> existing = new HashSet<>(routeInfoRepository.findExistingParisIn(hubIds));
// 전체 순서쌍(출발!=도착) 생성 → 차집합으로 미존재 목록 계산
List<RoutePairDto> missing = new ArrayList<>();
for (UUID dep : hubIds) {
for (UUID arr : hubIds) {
if (!dep.equals(arr)) {
RoutePairDto pair = new RoutePairDto(dep, arr);
if (!existing.contains(pair)) {
missing.add(pair);
}
}
}
}
log.debug("missing size : {}", missing.size());
Map<UUID, Hub> hubCache=hubService.getHubByIds(hubIds).stream()
.collect(Collectors.toMap(Hub::getHubId, Function.identity()));
for (RoutePairDto pair : missing) {
try {
Hub departureHub = hubCache.get(pair.departureId());
Hub arrivalHub = hubCache.get(pair.arrivalId());
Long durationMin = DistanceTimeUtil.estimateDurationMinutes(departureHub.getLatitude(),departureHub.getLongitude(),arrivalHub.getLatitude(),arrivalHub.getLongitude());
Double distanceKm = DistanceTimeUtil.calculateDistanceKm(departureHub.getLatitude(),departureHub.getLongitude(),arrivalHub.getLatitude(),arrivalHub.getLongitude());
HubRouteInfo routeInfo = HubRouteInfo.create(pair.departureId(),pair.arrivalId(),durationMin,distanceKm);
hubRouteInfoRepository.save(routeInfo);
createdCount++;
} catch (BusinessException e) {
log.warn("ErrorCode : {}, Message: {}",e.getErrorCode(),e.getMessage());
} catch (DataIntegrityViolationException e) {
log.warn("유니크 제약 조건 위반 departureId: {}, arrivalId: {}",pair.departureId(),pair.arrivalId());
} catch (Exception e) {
log.warn("Route create failed: {} -> {}", pair.departureId(), pair.arrivalId(), e);
}
}
long elapsed = System.currentTimeMillis() - start;
log.info("[HubRouteInfoScheduler] finished at {} — created {} new routes in {} ms",
LocalDateTime.now(), createdCount, elapsed);
}
}
@SchedulerLock 어노테이션을 통해 shedlock을 적용할 수 있습니다.