| @@ -26,6 +26,7 @@ repositories { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | 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-aop") | ||||||
|     implementation("org.springframework.boot:spring-boot-starter-data-jpa") |     implementation("org.springframework.boot:spring-boot-starter-data-jpa") | ||||||
|     implementation("org.springframework.boot:spring-boot-starter-data-redis") |     implementation("org.springframework.boot:spring-boot-starter-data-redis") | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
| package kr.co.vividnext.sodalive.configs | 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.beans.factory.annotation.Value | ||||||
| import org.springframework.cache.annotation.EnableCaching | import org.springframework.cache.annotation.EnableCaching | ||||||
| import org.springframework.context.annotation.Bean | import org.springframework.context.annotation.Bean | ||||||
| @@ -26,6 +29,19 @@ class RedisConfig( | |||||||
|     @Value("\${spring.redis.port}") |     @Value("\${spring.redis.port}") | ||||||
|     private val port: Int |     private val port: Int | ||||||
| ) { | ) { | ||||||
|  |     @Bean(destroyMethod = "shutdown") | ||||||
|  |     fun redissonClient(): RedissonClient { | ||||||
|  |         val config = Config() | ||||||
|  |         config.useSingleServer() | ||||||
|  |             .setAddress("rediss://$host:$port") | ||||||
|  |             .setSslEnableEndpointIdentification(true) | ||||||
|  |             .setSslTruststore(null) | ||||||
|  |             .setDnsMonitoringInterval(30000) | ||||||
|  |             .setConnectionMinimumIdleSize(0) | ||||||
|  |             .setConnectionPoolSize(5) | ||||||
|  |         return Redisson.create(config) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Bean |     @Bean | ||||||
|     fun redisConnectionFactory(): RedisConnectionFactory { |     fun redisConnectionFactory(): RedisConnectionFactory { | ||||||
|         val clientConfiguration = LettuceClientConfiguration.builder() |         val clientConfiguration = LettuceClientConfiguration.builder() | ||||||
|   | |||||||
| @@ -1,17 +1,20 @@ | |||||||
| package kr.co.vividnext.sodalive.scheduler | package kr.co.vividnext.sodalive.scheduler | ||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.AudioContentService | import kr.co.vividnext.sodalive.content.AudioContentService | ||||||
|  | import org.redisson.api.RedissonClient | ||||||
| import org.springframework.beans.factory.annotation.Qualifier | import org.springframework.beans.factory.annotation.Qualifier | ||||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler | ||||||
| import org.springframework.scheduling.support.CronTrigger | import org.springframework.scheduling.support.CronTrigger | ||||||
| import org.springframework.stereotype.Component | import org.springframework.stereotype.Component | ||||||
| import java.util.concurrent.ScheduledFuture | import java.util.concurrent.ScheduledFuture | ||||||
|  | import java.util.concurrent.TimeUnit | ||||||
| import javax.annotation.PostConstruct | import javax.annotation.PostConstruct | ||||||
| import javax.annotation.PreDestroy | import javax.annotation.PreDestroy | ||||||
|  |  | ||||||
| @Component | @Component | ||||||
| class AudioContentReleaseScheduledTask( | class AudioContentReleaseScheduledTask( | ||||||
|     private val audioContentService: AudioContentService, |     private val audioContentService: AudioContentService, | ||||||
|  |     private val redissonClient: RedissonClient, | ||||||
|     @Qualifier("audioContentReleaseScheduler") private val audioContentReleaseScheduler: ThreadPoolTaskScheduler |     @Qualifier("audioContentReleaseScheduler") private val audioContentReleaseScheduler: ThreadPoolTaskScheduler | ||||||
| ) { | ) { | ||||||
|     private var scheduledTask: ScheduledFuture<*>? = null |     private var scheduledTask: ScheduledFuture<*>? = null | ||||||
| @@ -19,7 +22,25 @@ class AudioContentReleaseScheduledTask( | |||||||
|     @PostConstruct |     @PostConstruct | ||||||
|     fun release() { |     fun release() { | ||||||
|         scheduledTask = audioContentReleaseScheduler.schedule( |         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 * * * *") |             CronTrigger("0 0/15 * * * *") | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user