diff --git a/build.gradle.kts b/build.gradle.kts index b0ee365..b260db1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ repositories { } dependencies { + implementation("org.redisson:redisson-spring-data-27:3.19.2") implementation("org.springframework.boot:spring-boot-starter-aop") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-redis") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt index 09989bd..0f0cdf3 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/RedisConfig.kt @@ -1,5 +1,8 @@ package kr.co.vividnext.sodalive.configs +import org.redisson.Redisson +import org.redisson.api.RedissonClient +import org.redisson.config.Config import org.springframework.beans.factory.annotation.Value import org.springframework.cache.annotation.EnableCaching import org.springframework.context.annotation.Bean @@ -26,6 +29,16 @@ class RedisConfig( @Value("\${spring.redis.port}") private val port: Int ) { + @Bean(destroyMethod = "shutdown") + fun redissonClient(): RedissonClient { + val config = Config() + config.useSingleServer() + .setAddress("redis://$host:$port") + .setConnectionMinimumIdleSize(1) + .setConnectionPoolSize(5) + return Redisson.create(config) + } + @Bean fun redisConnectionFactory(): RedisConnectionFactory { val clientConfiguration = LettuceClientConfiguration.builder() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt index b07c6fe..675cf1c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/scheduler/AudioContentReleaseScheduledTask.kt @@ -1,17 +1,20 @@ package kr.co.vividnext.sodalive.scheduler import kr.co.vividnext.sodalive.content.AudioContentService +import org.redisson.api.RedissonClient import org.springframework.beans.factory.annotation.Qualifier import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler import org.springframework.scheduling.support.CronTrigger import org.springframework.stereotype.Component import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit import javax.annotation.PostConstruct import javax.annotation.PreDestroy @Component class AudioContentReleaseScheduledTask( private val audioContentService: AudioContentService, + private val redissonClient: RedissonClient, @Qualifier("audioContentReleaseScheduler") private val audioContentReleaseScheduler: ThreadPoolTaskScheduler ) { private var scheduledTask: ScheduledFuture<*>? = null @@ -19,7 +22,25 @@ class AudioContentReleaseScheduledTask( @PostConstruct fun release() { scheduledTask = audioContentReleaseScheduler.schedule( - { audioContentService.releaseContent() }, + { + val lockName = "lock:audioContentRelease" + val lock = redissonClient.getLock(lockName) + + try { + // 5초 동안 락을 시도하고, Watchdog 활성화 (-1 설정) + if (lock.tryLock(5, -1, TimeUnit.SECONDS)) { + println("acquired : $lockName") + audioContentService.releaseContent() + } else { + println("Failed to acquire lock : $lockName") + } + } finally { + if (lock.isHeldByCurrentThread) { + lock.unlock() + println("release : $lockName") + } + } + }, CronTrigger("0 0/15 * * * *") ) }