-
Notifications
You must be signed in to change notification settings - Fork 37
[5주차] 낙관적락 분산락 적용 #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: kimbro97
Are you sure you want to change the base?
[5주차] 낙관적락 분산락 적용 #104
Changes from all commits
761c084
4ed8201
addb1a1
c007a82
74e9c49
3dcddbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.example.func; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.redisson.api.RLock; | ||
| import org.redisson.api.RedissonClient; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.function.Supplier; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class DistributedLockExecutor { | ||
|
|
||
| private final RedissonClient redissonClient; | ||
|
|
||
| public <T> T executeWithLock(List<String> lockKeys, long waitTime, long leaseTime, TimeUnit timeUnit, Supplier<T> action) { | ||
| List<RLock> locks = lockKeys.stream() | ||
| .map(redissonClient::getLock) | ||
| .toList(); | ||
| boolean allLocked = false; | ||
|
|
||
| try { | ||
| allLocked = redissonClient.getMultiLock(locks.toArray(new RLock[0])) | ||
| .tryLock(waitTime, leaseTime, timeUnit); | ||
|
|
||
| if (!allLocked) { | ||
| throw new IllegalArgumentException("해당 좌석은 현재 다른 사용자가 예매를 진행하고 있습니다."); | ||
| } | ||
|
|
||
| return action.get(); | ||
| } catch (InterruptedException e) { | ||
| throw new RuntimeException(e); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. InterruptedException은 어느 상황에 발생할까요? |
||
| } finally { | ||
| if (allLocked) { | ||
| locks.forEach(lock -> { | ||
| if (lock.isHeldByCurrentThread()) { | ||
| lock.unlock(); | ||
| } | ||
| }); | ||
| } | ||
|
Comment on lines
+36
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쓴 자원은 꼭 반납해줘야 합니다👍 잘하셨어요 |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| package com.example.reservation; | ||
|
|
||
| import com.example.aop.DistributedLock; | ||
| import com.example.entity.reservation.Reservation; | ||
| import com.example.entity.reservation.ReservedSeat; | ||
| import com.example.func.DistributedLockExecutor; | ||
| import com.example.message.MessageService; | ||
| import com.example.repository.reservation.ReservationRepository; | ||
| import com.example.reservation.request.ReservationServiceRequest; | ||
|
|
@@ -14,6 +14,7 @@ | |
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
|
|
@@ -22,20 +23,27 @@ public class ReservationService { | |
| private final MessageService messageService; | ||
| private final ReservationValidate reservationValidate; | ||
| private final ReservationRepository reservationRepository; | ||
| private final DistributedLockExecutor lockExecutor; | ||
|
|
||
| // @DistributedLock(key = "reservation:screening:{#request.screeningId}:seat:{#request.seatIds}", leaseTime = 2, waitTime = 2) | ||
| @Transactional | ||
| @DistributedLock(key = "reservation:screening:{#request.screeningId}:seat:{#request.seatIds}", leaseTime = 10, waitTime = 5) | ||
| public ReservationServiceResponse reserve(ReservationServiceRequest request) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예약 메소드에서 좌석이 붙어 있는지 혹은 최대 3개인지 등등의 검증이 이루어졌으면 좋겠습니다! |
||
|
|
||
| ReservationValidationResult validationResult = reservationValidate.validate(request); | ||
| List<String> lockKeys = request.getSeatIds().stream() | ||
| .map(seatId -> "reservation:screening:" + request.getScreeningId() + ":seat:" + seatId) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 키 생성하는 로직을 따로 메서드로 관리하는건 어때요? |
||
| .toList(); | ||
|
|
||
| return lockExecutor.executeWithLock(lockKeys, 2, 2, TimeUnit.SECONDS, () -> { | ||
| ReservationValidationResult validationResult = reservationValidate.validate(request); | ||
|
|
||
| Reservation reservation = createReservation(validationResult); | ||
| Reservation reservation = createReservation(validationResult); | ||
|
|
||
| reservationRepository.save(reservation); | ||
| reservationRepository.save(reservation); | ||
|
|
||
| messageService.send(); | ||
| messageService.send(); | ||
|
|
||
| return ReservationServiceResponse.of(reservation); | ||
| return ReservationServiceResponse.of(reservation); | ||
|
Comment on lines
+37
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예약을 위해 redis에 특정 좌석이 예약이 되었는지 아닌지 저장하고 있는거 같아요! 관계형 데이터베이스에서 지원하는 낙관적 락을 사용하시는건 어때요? redis는 휘발성 데이터베이스이기 때문에 예약에 대한 정보를 저장하기에는 적합하지 않아 보입니다. 추가로, 형재님이 작성하신 코드의 흐름은 다음과 같아 보여요.
JPA에서 제공하는 @Version 어노테이션을 통한 낙관적 락을 사용해보시겠어요? |
||
| }); | ||
| } | ||
|
|
||
| private Reservation createReservation(ReservationValidationResult validationResult) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잠금에 대한 코드인데 좌석 예약에 대한 비지니스 로직이 들어가 있네요!?