-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- 레디스 분산 락을 적용하기 위해 redisson 등록 - AlertScheduler 인터페이스 분리 - 기존 AlertService 스케줄러 메서드 삭제 및 이동 - RedissonAlertSchedulerTest 테스트 작성
- Loading branch information
1 parent
dab7f33
commit 72806c7
Showing
9 changed files
with
174 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/main/java/com/atwoz/alert/application/AlertScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.atwoz.alert.application; | ||
|
||
public interface AlertScheduler { | ||
|
||
void deleteExpiredAlerts(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/java/com/atwoz/alert/exception/exceptions/AlertLockException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.atwoz.alert.exception.exceptions; | ||
|
||
public class AlertLockException extends RuntimeException { | ||
|
||
public AlertLockException() { | ||
super("알림 락 획득 과정에서 예외가 발생하였습니다."); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/main/java/com/atwoz/alert/infrastructure/RedissonAlertScheduler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.atwoz.alert.infrastructure; | ||
|
||
import com.atwoz.alert.application.AlertScheduler; | ||
import com.atwoz.alert.domain.AlertRepository; | ||
import com.atwoz.alert.exception.exceptions.AlertLockException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.redisson.api.RLock; | ||
import org.redisson.api.RedissonClient; | ||
import org.springframework.scheduling.annotation.Scheduled; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class RedissonAlertScheduler implements AlertScheduler { | ||
|
||
private static final String MIDNIGHT = "0 0 0 * * ?"; | ||
private static final String DELETE_ALERT_LOCK = "delete_alert_lock"; | ||
private static final long WAIT_TIME = 0L; | ||
private static final long HOLD_TIME = 40L; | ||
|
||
private final AlertRepository alertRepository; | ||
private final RedissonClient redissonClient; | ||
|
||
@Scheduled(cron = MIDNIGHT) | ||
@Override | ||
public void deleteExpiredAlerts() { | ||
RLock lock = redissonClient.getLock(DELETE_ALERT_LOCK); | ||
boolean isLocked = false; | ||
try { | ||
isLocked = lock.tryLock(WAIT_TIME, HOLD_TIME, TimeUnit.SECONDS); | ||
if (isLocked) { | ||
alertRepository.deleteExpiredAlerts(); | ||
return; | ||
} | ||
throw new AlertLockException(); | ||
} catch (InterruptedException e) { | ||
throw new AlertLockException(); | ||
} finally { | ||
if (isLocked) { | ||
lock.unlock(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/test/java/com/atwoz/alert/infrastructure/RedissonAlertSchedulerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package com.atwoz.alert.infrastructure; | ||
|
||
import com.atwoz.alert.domain.Alert; | ||
import com.atwoz.alert.domain.AlertRepository; | ||
import com.atwoz.helper.IntegrationHelper; | ||
import org.junit.jupiter.api.DisplayNameGeneration; | ||
import org.junit.jupiter.api.DisplayNameGenerator; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.auditing.AuditingHandler; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.Optional; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
import static com.atwoz.alert.fixture.AlertFixture.알림_생성_id_없음; | ||
import static com.atwoz.alert.fixture.AlertFixture.옛날_알림_생성; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.SoftAssertions.assertSoftly; | ||
|
||
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) | ||
@SuppressWarnings("NonAsciiCharacters") | ||
class RedissonAlertSchedulerTest extends IntegrationHelper { | ||
|
||
@Autowired | ||
private RedissonAlertScheduler redissonAlertScheduler; | ||
|
||
@Autowired | ||
private AuditingHandler auditingHandler; | ||
|
||
@Autowired | ||
private AlertRepository alertRepository; | ||
|
||
@Test | ||
void 생성된_지_60일을_초과한_알림은_삭제_상태로_된다() { | ||
// given | ||
Long memberId = 1L; | ||
|
||
LocalDateTime pastTime = LocalDateTime.now() | ||
.minusDays(61); | ||
auditingHandler.setDateTimeProvider(() -> Optional.of(pastTime)); | ||
Alert savedOldAlert = alertRepository.save(옛날_알림_생성()); | ||
|
||
auditingHandler.setDateTimeProvider(() -> Optional.of(LocalDateTime.now())); | ||
Alert savedAlert = alertRepository.save(알림_생성_id_없음()); | ||
|
||
// when | ||
redissonAlertScheduler.deleteExpiredAlerts(); | ||
|
||
// then | ||
Optional<Alert> foundSavedAlert = alertRepository.findByMemberIdAndId(memberId, savedAlert.getId()); | ||
Optional<Alert> foundSavedOldAlert = alertRepository.findByMemberIdAndId(memberId, savedOldAlert.getId()); | ||
|
||
assertSoftly(softly -> { | ||
softly.assertThat(foundSavedAlert).isPresent(); | ||
softly.assertThat(foundSavedOldAlert).isPresent(); | ||
Alert recentAlert = foundSavedAlert.get(); | ||
Alert oldAlert = foundSavedOldAlert.get(); | ||
softly.assertThat(recentAlert.getDeletedAt()).isNull(); | ||
softly.assertThat(oldAlert.getDeletedAt()).isNotNull(); | ||
}); | ||
} | ||
|
||
@Test | ||
void 분산_락으로_중복호출을_막는다() throws InterruptedException { | ||
// given | ||
int numberOfThreads = 5; | ||
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); | ||
CountDownLatch latch = new CountDownLatch(numberOfThreads); | ||
AtomicLong atomicLong = new AtomicLong(); | ||
|
||
// when | ||
for (int i = 0; i < numberOfThreads; i++) { | ||
executorService.submit(() -> { | ||
try { | ||
redissonAlertScheduler.deleteExpiredAlerts(); | ||
atomicLong.incrementAndGet(); | ||
} finally { | ||
latch.countDown(); | ||
} | ||
}); | ||
} | ||
|
||
latch.await(40, TimeUnit.SECONDS); | ||
executorService.shutdown(); | ||
|
||
// then | ||
assertThat(atomicLong.get()).isEqualTo(1); | ||
} | ||
} |