Skip to content

Commit

Permalink
Merge pull request #10 from sdfgx123/test/#7
Browse files Browse the repository at this point in the history
[Test] Redis 분산 락 검증 테스트
  • Loading branch information
sdfgx123 authored Dec 6, 2023
2 parents 8b8a1d7 + 2d73268 commit 1f22782
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public interface WorkRepository extends JpaRepository<Work, Long>, WorkRepositor
Page<Work> findByUserAndStartTimeBetween(User user, LocalDateTime startOfweek, LocalDateTime endOfweek, Pageable pageable);
Page<Work> findByUserOrderByStartTimeDesc(User user, Pageable pageable);
Optional<Work> findTopByUserIdOrderByStartTimeDesc(Long userId);
List<Work> findByUserId(Long userId);
}
2 changes: 1 addition & 1 deletion src/main/java/com/fc/mini3server/service/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class AdminService {
private static final LocalDate startOfMonth = today.withDayOfMonth(1);
private static final LocalDate endOfMonth = today.withDayOfMonth(today.lengthOfMonth());
private static final LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
private static final LocalDate endOfLastMonth = today.minusMonths(1).withDayOfMonth(today.lengthOfMonth());
private static final LocalDate endOfLastMonth = today.withDayOfMonth(1).minusMonths(1).withDayOfMonth(today.minusMonths(1).lengthOfMonth());
private final UserService userService;
private final UserRepository userRepository;
private final ScheduleRepository scheduleRepository;
Expand Down
54 changes: 26 additions & 28 deletions src/main/java/com/fc/mini3server/service/ScheduleService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.fc.mini3server._core.handler.Message;
import com.fc.mini3server._core.handler.exception.Exception400;
import com.fc.mini3server._core.handler.exception.Exception500;
import com.fc.mini3server.domain.*;
import com.fc.mini3server._core.handler.exception.Exception404;
import com.fc.mini3server.domain.CategoryEnum;
Expand Down Expand Up @@ -232,36 +231,35 @@ public void startWorkUsingRedis(Long userId) {
String lockKey = "lock:" + userId;
String lockVal = UUID.randomUUID().toString();

Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 10, TimeUnit.SECONDS);
log.info("lockKey : " + lockKey + ", " + "lockVal : " + lockVal);
log.info("acquired : " + acquired);

if (! Boolean.TRUE.equals(acquired)) {
log.info("락 획득 실패, 다른 스레드가 우선순위를 가짐");
return;
}

try {
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 10, TimeUnit.SECONDS);
log.info("lockKey : " + lockKey + ", " + "lockVal : " + lockVal);
if (Boolean.TRUE.equals(acquired)) {
try {
User user = userRepository.findById(userId).orElseThrow(() -> new Exception400("유저를 찾지 못했습니다. 올바른 유저가 요청했는지 확인하십시오."));

Optional<Work> latestWork = workRepository.findTopByUserIdOrderByStartTimeDesc(userId);
if (latestWork.isPresent()) {
Work lastWork = latestWork.get();
if (lastWork.getStartTime().toLocalDate().isEqual(LocalDate.now())) {
throw new Exception400("이미 출근했습니다.");
}
}

Work work = new Work();
work.setUser(user);
work.setStartTime(LocalDateTime.now());
workRepository.save(work);
log.info("INSERT to DB safely");
} finally {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList(lockKey), lockVal);
User user = userRepository.findById(userId).orElseThrow(() -> new Exception400("유저를 찾지 못했습니다. 올바른 유저가 요청했는지 확인하십시오."));

Optional<Work> latestWork = workRepository.findTopByUserIdOrderByStartTimeDesc(userId);
if (latestWork.isPresent()) {
Work lastWork = latestWork.get();
if (lastWork.getStartTime().toLocalDate().isEqual(LocalDate.now())) {
throw new Exception400("이미 출근했습니다.");
}
} else {
throw new Exception500("REDIS LOCK 획득 실패");
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception500("UNKNOWN ERROR");

Work work = new Work();
work.setUser(user);
work.setStartTime(LocalDateTime.now());
workRepository.save(work);
log.info("INSERT to DB safely");

} finally {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList(lockKey), lockVal);
}
}

Expand Down
71 changes: 71 additions & 0 deletions src/test/java/com/fc/mini3server/service/ScheduleServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.fc.mini3server.service;

import com.fc.mini3server.domain.Work;
import com.fc.mini3server.repository.WorkRepository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.assertj.core.api.Assertions.assertThat;


@SpringBootTest
@Transactional
@DisplayName("스케줄 서비스 테스트")
public class ScheduleServiceTest {

@Autowired
EntityManager em;
@Autowired
JPAQueryFactory queryFactory;
@Autowired
ScheduleService scheduleService;
@Autowired
AdminService adminService;
@Autowired
WorkRepository workRepository;

@Test
@DisplayName("레디스 분산 락을 구현 후 동시성 이슈 테스트")
public void testStartWorkUsingRedisWithLock() throws InterruptedException {
Long userId = 142L;
int threadCnt = 10;
ExecutorService service = Executors.newFixedThreadPool(threadCnt);
CountDownLatch latch = new CountDownLatch(threadCnt);

for (int i=0; i<threadCnt; i++) {
service.execute(() -> {
try {
scheduleService.startWorkUsingRedis(userId);
} finally {
latch.countDown();
}
});
}
System.out.println("ON EXECUTE DONE");

latch.await();
service.shutdown();

List<Work> works = workRepository.findByUserId(userId);
if (works == null) {
System.out.println("NULL");

}
int expectedCnt = 1;

assertThat(works.size()).isEqualTo(expectedCnt);
}



}

0 comments on commit 1f22782

Please sign in to comment.