diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/redisson/LiquorCtrRedisRepository.java b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/redisson/LiquorCtrRedisRepository.java index 8d11c6a3..6520a9ad 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/redisson/LiquorCtrRedisRepository.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/redisson/LiquorCtrRedisRepository.java @@ -20,7 +20,7 @@ @Component @Slf4j public class LiquorCtrRedisRepository { - + // TODO: ~Repository vs ~Service private static final String LIQUOR_CTR_KEY = "LIQUOR_CTR"; private static final long LOCK_WAIT_TIME = 3L; private static final long LOCK_LEASE_TIME = 3L; @@ -30,7 +30,8 @@ public class LiquorCtrRedisRepository { private final RedissonClient redissonClient; - // TODO: LiquorCtrRedisService vs LiquorCtrRedisRepository -> 취향 차이다... + private final RMapCache liquorCtrs; + public LiquorCtrRedisRepository( final LiquorCtrRepository liquorCtrRepository, final RedissonClient redissonClient, @@ -48,30 +49,20 @@ public LiquorCtrRedisRepository( this.liquorCtrRepository = liquorCtrRepository; this.redissonClient = redissonClient; + liquorCtrs = redissonClient.getMapCache(LIQUOR_CTR_KEY); } public double getCtr(final Long liquorId) { return lookUpLiquorCtr(liquorId).toEntity(liquorId).getCtr(); } - // JpaRepository.updateAgeOne - // RedisRepository.updateImpressionOne - - // 비관전락 했을 때 LiquorCtrRepsotiory.findById() <- @Lock - // Repository에서 락을 건게 아닌가? - // RLock을 Redis에서 빌린다고 생각 -> redissonClient.getLock() 자체가 Redis에 락 요청 보내는거아냐? - - // AOP 구현했을 때 -> @Transactional -> repository에도 붙인다. - // Service 메서드에서 트랜잭션 시작을 원치 않을 수도 있다 - // service.method( repo.m1, repo.m2 ) - // repository에서 @RedisLock 을 붙이는게 자연스럽다...? public void increaseImpression(final Long liquorId) { - final RLock rLock = redissonClient.getLock(LockType.LIQUOR_CTR.getPrefix() + liquorId); + final RLock liquorCtrLock = getLiquorCtrLock(liquorId); try { - rLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); + liquorCtrLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); - replaceLiquorCtr(liquorId, lookUpLiquorCtr(liquorId).increaseImpression()); + liquorCtrs.replace(liquorId, lookUpLiquorCtr(liquorId).increaseImpression()); } catch (final InterruptedException e) { log.error("노출수 갱신에 실패했습니다. | liquorId : {}", liquorId); @@ -79,17 +70,17 @@ public void increaseImpression(final Long liquorId) { throw new SoolSoolException(LiquorErrorCode.INTERRUPTED_THREAD); } finally { - rLock.unlock(); + unlock(liquorCtrLock); } } public void increaseClick(final Long liquorId) { - final RLock rLock = redissonClient.getLock(LockType.LIQUOR_CTR.getPrefix() + liquorId); + final RLock liquorCtrLock = getLiquorCtrLock(liquorId); try { - rLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); + liquorCtrLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); - replaceLiquorCtr(liquorId, lookUpLiquorCtr(liquorId).increaseClick()); + liquorCtrs.replace(liquorId, lookUpLiquorCtr(liquorId).increaseClick()); } catch (final InterruptedException e) { log.error("클릭수 갱신에 실패했습니다. | liquorId : {}", liquorId); @@ -97,15 +88,22 @@ public void increaseClick(final Long liquorId) { throw new SoolSoolException(LiquorErrorCode.INTERRUPTED_THREAD); } finally { + unlock(liquorCtrLock); + } + } + + private RLock getLiquorCtrLock(final Long liquorId) { + return redissonClient.getLock(LockType.LIQUOR_CTR.getPrefix() + liquorId); + } + + private void unlock(final RLock rLock) { + if (rLock.isLocked() && rLock.isHeldByCurrentThread()) { rLock.unlock(); } } // TODO: 만료 테스트는 어떻게 해야할까? private RedisLiquorCtr lookUpLiquorCtr(final Long liquorId) { - final RMapCache liquorCtrs = - redissonClient.getMapCache(LIQUOR_CTR_KEY); - if (!liquorCtrs.containsKey(liquorId)) { final LiquorCtr liquorCtr = liquorCtrRepository.findByLiquorId(liquorId) .orElseThrow(() -> new SoolSoolException(LiquorCtrErrorCode.NOT_LIQUOR_CTR_FOUND)); @@ -120,11 +118,4 @@ private RedisLiquorCtr lookUpLiquorCtr(final Long liquorId) { return liquorCtrs.get(liquorId); } - - private void replaceLiquorCtr(final Long liquorId, final RedisLiquorCtr redisLiquorCtr) { - final RMapCache liquorCtrs = - redissonClient.getMapCache(LIQUOR_CTR_KEY); - - liquorCtrs.replace(liquorId, redisLiquorCtr); - } } diff --git a/src/main/java/com/woowacamp/soolsool/core/member/service/MemberService.java b/src/main/java/com/woowacamp/soolsool/core/member/service/MemberService.java index 7b599d5b..80e1da6e 100644 --- a/src/main/java/com/woowacamp/soolsool/core/member/service/MemberService.java +++ b/src/main/java/com/woowacamp/soolsool/core/member/service/MemberService.java @@ -107,7 +107,7 @@ public void addMemberMileage( final Long memberId, final MemberMileageChargeRequest memberMileageChargeRequest ) { - final RLock rLock = redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); + final RLock rLock = getMemberLock(memberId); try { rLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); @@ -130,6 +130,10 @@ public void addMemberMileage( } } + private RLock getMemberLock(Long memberId) { + return redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); + } + private void unlock(final RLock rLock) { if (rLock.isLocked() && rLock.isHeldByCurrentThread()) { rLock.unlock(); diff --git a/src/main/java/com/woowacamp/soolsool/core/order/service/OrderService.java b/src/main/java/com/woowacamp/soolsool/core/order/service/OrderService.java index b468efc6..27758cfc 100644 --- a/src/main/java/com/woowacamp/soolsool/core/order/service/OrderService.java +++ b/src/main/java/com/woowacamp/soolsool/core/order/service/OrderService.java @@ -91,12 +91,13 @@ public PageOrderListResponse orderList( @Transactional public Order cancelOrder(final Long memberId, final Long orderId) { - final RLock memberLock = redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); - final RLock orderLock = redissonClient.getLock(LockType.ORDER.getPrefix() + orderId); + final RLock multiLock = redissonClient.getMultiLock( + getMemberLock(memberId), + getOrderLock(orderId) + ); try { - memberLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); - orderLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); + multiLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); final Order order = orderRepository.findOrderById(orderId) .orElseThrow(() -> new SoolSoolException(OrderErrorCode.NOT_EXISTS_ORDER)); @@ -115,15 +116,16 @@ public Order cancelOrder(final Long memberId, final Long orderId) { throw new SoolSoolException(OrderErrorCode.INTERRUPTED_THREAD); } finally { - unlock(orderLock); - unlock(memberLock); + multiLock.unlock(); } } - private void unlock(final RLock rLock) { - if (rLock.isLocked() && rLock.isHeldByCurrentThread()) { - rLock.unlock(); - } + private RLock getOrderLock(Long orderId) { + return redissonClient.getLock(LockType.ORDER.getPrefix() + orderId); + } + + private RLock getMemberLock(Long memberId) { + return redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); } @Transactional(readOnly = true) diff --git a/src/main/java/com/woowacamp/soolsool/core/payment/service/PayService.java b/src/main/java/com/woowacamp/soolsool/core/payment/service/PayService.java index d1ffaeae..686d633e 100644 --- a/src/main/java/com/woowacamp/soolsool/core/payment/service/PayService.java +++ b/src/main/java/com/woowacamp/soolsool/core/payment/service/PayService.java @@ -16,6 +16,7 @@ import com.woowacamp.soolsool.core.receipt.service.ReceiptService; import com.woowacamp.soolsool.global.exception.SoolSoolException; import com.woowacamp.soolsool.global.infra.LockType; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -55,21 +56,16 @@ public PayReadyResponse ready(final Long memberId, final PayOrderRequest payOrde public Order approve(final Long memberId, final Long receiptId, final String pgToken) { final Receipt receipt = receiptService.getMemberReceipt(memberId, receiptId); - receiptService.modifyReceiptStatus(memberId, receiptId, ReceiptStatusType.COMPLETED); + final List locks = new ArrayList<>(); + locks.add(getMemberLock(memberId)); + locks.addAll(getLiquorLocks(receipt.getReceiptItems())); - final RLock memberLock = redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); - final List liquorLocks = receipt.getReceiptItems().stream() - .map(ReceiptItem::getLiquorId) - .sorted() - .map(liquorId -> redissonClient.getLock( - LockType.LIQUOR_STOCK.getPrefix() + liquorId)) - .collect(Collectors.toList()); + final RLock multiLock = redissonClient.getMultiLock( + locks.toArray(locks.toArray(new RLock[0])) + ); try { - memberLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); - for (RLock liquorLock : liquorLocks) { - liquorLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); - } + multiLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS); decreaseStocks(receipt); @@ -79,6 +75,8 @@ public Order approve(final Long memberId, final Long receiptId, final String pgT cartService.removeCartItems(memberId); + receiptService.modifyReceiptStatus(memberId, receiptId, ReceiptStatusType.COMPLETED); + orderService.addPaymentInfo( payClient.payApprove(receipt, pgToken).toEntity(order.getId())); @@ -88,11 +86,23 @@ public Order approve(final Long memberId, final Long receiptId, final String pgT throw new SoolSoolException(PayErrorCode.INTERRUPTED_THREAD); } finally { - liquorLocks.forEach(this::unlock); - unlock(memberLock); + multiLock.unlock(); } } + private RLock getMemberLock(final Long memberId) { + return redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId); + } + + private List getLiquorLocks(final List receiptItems) { + return receiptItems.stream() + .map(ReceiptItem::getLiquorId) + .sorted() + .map(liquorId -> redissonClient.getLock( + LockType.LIQUOR_STOCK.getPrefix() + liquorId)) + .collect(Collectors.toList()); + } + private void decreaseStocks(final Receipt receipt) { for (ReceiptItem receiptItem : receipt.getReceiptItems()) { liquorStockService.decreaseLiquorStock(receiptItem.getLiquorId(), @@ -102,12 +112,6 @@ private void decreaseStocks(final Receipt receipt) { } } - private void unlock(final RLock rLock) { - if (rLock.isLocked() && rLock.isHeldByCurrentThread()) { - rLock.unlock(); - } - } - @Transactional public void cancelReceipt(final Long memberId, final Long receiptId) { receiptService.modifyReceiptStatus(memberId, receiptId, ReceiptStatusType.CANCELED);