먼저 의존성을 추가한다.

  // Redisson (Spring Boot 3 호환)
  implementation 'org.redisson:redisson-spring-boot-starter:3.36.0'

  // 또는 Spring Boot Starter 없이 Redisson만 사용하려면:
  // Redisson 코어
  implementation 'org.redisson:redisson:3.36.0'

실사용은 별거 없다. 기존 자바의 락 인터페이스를 기반으로 만든거라 비슷한듯.

package net.daum.cafe.ads.domain.shopad.trendpick.woman;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.daum.cafe.ads.domain.shopad.dao.ShopAdDaoResponse;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
@RequiredArgsConstructor
public class TrendPickWomanSchedulingServiceImpl implements TrendPickWomanSchedulingService {

	private final RedissonClient redissonClient;
	private final TrendPickWomanClient trendPickWomanClient;
	private final TrendPickWomanCacheClient trendPickWomanCacheClient;

	//TODO: 스케쥴러가 수집할 itemSize를 동적으로 변경할 필요가 있는지?
	private static final int DEFAULT_ITEM_SIZE = 6;

	//redisson이용한 분산 락 동기화 메서드
	//1분마다 스케쥴링 동작
	@Scheduled(cron = "0 */1 * * * *", zone = "Asia/Seoul")
	public void syncLoadAndSave() {
		String lockKey = "trendPickWoman:schedule:lock";
		RLock lock = redissonClient.getLock(lockKey);
		try {
			//waitTime => 0초 대기, 즉 획득 실패하면 대기 없이 즉시 실패로 간주
			//leaseTime => 50초가 지나면 강제 락 해제 (다음 배치가 1분 간격임. 건강한 파드에게 기회를 넘겨주는 의도)
			boolean isLocked = lock.tryLock(0, 50, TimeUnit.SECONDS);
			if (!isLocked) {
				log.debug("락 획득 실패, 다른 파드에서 작업중입니다. 스케쥴 실행을 스킵합니다.");
				return;
			}
			try {
				log.debug("락 획득 성공, 스케쥴을 실행합니다.");
				loadAndSave();
			} finally {
				lock.unlock();
			}
			//TODO: 적절한 예외 처리 필요
		} catch (InterruptedException ex) {
			log.error("락 획득 중 인터럽트 발생: {}", ex.getMessage());
		}
	}

	private void loadAndSave() {
		try {
			ShopAdDaoResponse shopAdDaoResponse = trendPickWomanClient.requestShopAd(DEFAULT_ITEM_SIZE);
			trendPickWomanCacheClient.saveNewShopAd(shopAdDaoResponse);
			//TODO: 하위 에러 잡아서 적절히 처리 및 자체 발생 에러 처리 위한 catch 분기 필요
		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}
	}
}