Search
🌔

Spring Retry 활용하여 동시성 제어하기

가. Spring Retry 설정법

1) 의존성 적용
gradle 기반 의존성 적용
// usecase-core 모듈 내 gradle에 적용 dependencies { implementation("org.springframework.retry:spring-retry") }
Kotlin
복사
2) EnableRetry 설정
기본 설정
@EnableRetry @SpringBootApplication class PointManagementApplication fun main(args: Array<String>) { runApplication<PointManagementApplication>(*args) }
Kotlin
복사
retry 적용을 위해 Configuration에 @EnableRetry 적용
Retry의 우선순위 조정 설정, 참고로 기본 값은 LOWEST_PRECEDENCE
@Configuration @EnableRetry class AppConfig { } @Configuration class RetryConfig : RetryConfiguration() { override fun getOrder() = Ordered.HIGHEST_PRECEDENCE }
Kotlin
복사

나. 활용 예시

1) 기본 사용법
@Retryable로 정의된 메소드는 value로 지정된 예외 발생 시 기본 설정에 따라 1초 간격으로 3회 재시도
@Recover로 메소드가 정의되었다면 @Retryable로 정의된 메소드가 3번 재시도 후 실행
@Service public interface MyService { @Retryable(value = SQLException.class) void retryServiceWithRecovery(String sql) throws SQLException; @Recover void recover(SQLException e, String sql); }
Kotlin
복사
code from https://www.baeldung.com/spring-retry
2) 동시성 제어
@Retryable을 커스텀하면 낙관적 락이 걸린 상태에서 동시성 및 정합성 이슈에 효과적으로 대응 가능
backoff 설정은 재시도 전 기다리는 시간에 대한 설정임. 아래와 같이 backoff가 설정되었다면 다음과 같이 시간 간격을 두고 재시도
첫번째 예외 발생 후 100ms 뒤에 재시도, 두 번째 예외 발생 후 200ms 뒤에 재시도, 세 번재 예외 발생 후 400ms 뒤에 재시도
@Retryable( value = [ObjectOptimisticLockingFailureException::class, DataIntegrityViolationException::class], backoff = Backoff(delay = 100, multiplier = 2.0) ) @Transactional fun attachFiles(cmd: UpdateImageFilesCommand) { val image = repository.get(cmd.imageId) ?: throw NotExistDigitalAssetException(DigitalAssetType.STOCK_IMAGE, cmd.imageId.toString()) image.attach(cmd.files) repository.saveAndFlush(image) }
Kotlin
복사
3) 메시지 재처리
처리되지 못한 메시지를 주기적으로 수집해서 재처리
아래의 설정에 따라 retryWorker가 1분(60000ms)마다 500개의 메시지를 수집해서 재처리함
@Profile("prod|stg") @Component class RetryMsgScheduler(val retryWorker: RetryWorker) { private val logger = LoggerFactory.getLogger(javaClass) companion object { const val FETCH_SIZE = 500 } @Scheduled(fixedDelay = 60000) fun schedule() { val now = LocalDateTime.now() logger.info("retry scheduler started $now") val retries = retryWorker.fetch(FETCH_SIZE) if (retries.isEmpty()) { return } retries.forEach { retryWorker.retry(it.id!!) } } }
Kotlin
복사