Skip to content

Commit

Permalink
refactor: 여러 개의 RLock을 MultiLock으로 관리 (woowatechcamp#226)
Browse files Browse the repository at this point in the history
* refactor: 여러 개의 RLock을 MultiLock으로 관리

* style: 주석 삭제
  • Loading branch information
hwihwi523 committed Sep 17, 2023
1 parent e9ef697 commit d3e4e0b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +30,8 @@ public class LiquorCtrRedisRepository {

private final RedissonClient redissonClient;

// TODO: LiquorCtrRedisService vs LiquorCtrRedisRepository -> 취향 차이다...
private final RMapCache<Long, RedisLiquorCtr> liquorCtrs;

public LiquorCtrRedisRepository(
final LiquorCtrRepository liquorCtrRepository,
final RedissonClient redissonClient,
Expand All @@ -48,64 +49,61 @@ 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);

Thread.currentThread().interrupt();

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);

Thread.currentThread().interrupt();

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<Long, RedisLiquorCtr> liquorCtrs =
redissonClient.getMapCache(LIQUOR_CTR_KEY);

if (!liquorCtrs.containsKey(liquorId)) {
final LiquorCtr liquorCtr = liquorCtrRepository.findByLiquorId(liquorId)
.orElseThrow(() -> new SoolSoolException(LiquorCtrErrorCode.NOT_LIQUOR_CTR_FOUND));
Expand All @@ -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<Long, RedisLiquorCtr> liquorCtrs =
redissonClient.getMapCache(LIQUOR_CTR_KEY);

liquorCtrs.replace(liquorId, redisLiquorCtr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<RLock> locks = new ArrayList<>();
locks.add(getMemberLock(memberId));
locks.addAll(getLiquorLocks(receipt.getReceiptItems()));

final RLock memberLock = redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId);
final List<RLock> 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);

Expand All @@ -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()));

Expand All @@ -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<RLock> getLiquorLocks(final List<ReceiptItem> 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(),
Expand All @@ -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);
Expand Down

0 comments on commit d3e4e0b

Please sign in to comment.