-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 구매자 입찰 과정은 원자적으로 처리되어야한다. (#296)
* refactor: (Member) 다른 사용자에게 포인트를 송금하는 기능 추가 * feat: 포인트가 음수가 되는 경우에 대한 검증 추가 - validatePositiveAmount를 통해 생성 또는 거래하려는 포인트가 양수인지 검증한다. - 거래 포인트가 음수인 경우를 P008 예외로 표현한다. * refactor: 경매 입찰 시, 포인트 거래를 PaymentService로 분리한다. - pointTransfer(senderId, recipientId, amount)를 통해 포인트를 이체한다. * feat: 어노테이션을 통해 락 동작을 수행하는 기능 추가 - 메서드에 DistributedLock 어노테이션을 붙여 분산락 수행을 요청한다. - DistributedLockAspect는 해당 어노테이션이 붙은 메서드를 분산락과 함께 수행한다. - LockProvider를 구현해 사용할 락 방식을 선택할 수 있다. * feat: LettuceLockProvider 클래스 추가 * feat: RedissonLockProvider 클래스 추가 - 시간 초과로 락을 획득하지 못하는 경우를 표현하는 G002 에러 추가 * feat: 환경 변수를 통해 사용할 Lock을 선택하는 LockProviderConfig 추가 - lock.provider 값을 통해 사용할 lock을 선택한다. - lettuce, redisson, none 중에 하나를 입력하면 적용된다. * refactor: AOP를 통해 Lock을 사용하도록 수정 * refactor: 사용되지 않는 AuctioneerProxy 및 구현 클래스 제거 * refactor: Redisson 디버깅용 주석 추 * feat: 서버 과부하로 인한 예외를 표현하는 ServiceUnavailableException 클래스 추가 * feat: 과부하로 인해 정상적으로 락을 획득하지 못하는 경우 ServiceUnavailableException이 발생한다. * feat: Lock 구현체에서 사용되는 값들을 환경변수로 분
- Loading branch information
Showing
21 changed files
with
437 additions
and
176 deletions.
There are no files selected for viewing
52 changes: 0 additions & 52 deletions
52
src/main/java/com/wootecam/luckyvickyauction/core/auction/infra/AuctionLockOperation.java
This file was deleted.
Oops, something went wrong.
6 changes: 0 additions & 6 deletions
6
.../java/com/wootecam/luckyvickyauction/core/auction/service/auctioneer/AuctioneerProxy.java
This file was deleted.
Oops, something went wrong.
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
26 changes: 0 additions & 26 deletions
26
...ootecam/luckyvickyauction/core/auction/service/auctioneer/LettuceLockAuctioneerProxy.java
This file was deleted.
Oops, something went wrong.
37 changes: 0 additions & 37 deletions
37
...otecam/luckyvickyauction/core/auction/service/auctioneer/RedissonLockAuctioneerProxy.java
This file was deleted.
Oops, something went wrong.
52 changes: 52 additions & 0 deletions
52
src/main/java/com/wootecam/luckyvickyauction/core/lock/LettuceLockProvider.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,52 @@ | ||
package com.wootecam.luckyvickyauction.core.lock; | ||
|
||
import com.wootecam.luckyvickyauction.global.aop.LockProvider; | ||
import com.wootecam.luckyvickyauction.global.exception.ErrorCode; | ||
import com.wootecam.luckyvickyauction.global.exception.ServiceUnavailableException; | ||
import java.util.concurrent.TimeUnit; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class LettuceLockProvider implements LockProvider { | ||
|
||
private final RedisTemplate<String, Long> redisOperations; | ||
|
||
@Value("${lock.lettuce.retry_duration:50}") | ||
private int retryDuration; | ||
@Value("${lock.lettuce.max_retry:10}") | ||
private int maxRetry; | ||
@Value("${lock.lettuce.lease_time:200}") | ||
private int leaseTime; | ||
|
||
@Override | ||
public void tryLock(String key) { | ||
int retry = 0; | ||
while (!lock(key)) { | ||
if (++retry == maxRetry) { | ||
throw new ServiceUnavailableException("TimeOut에 도달했습니다. 최대재시도 횟수: " + maxRetry, ErrorCode.G002); | ||
} | ||
|
||
try { | ||
Thread.sleep(retryDuration); | ||
} catch (InterruptedException e) { | ||
throw new ServiceUnavailableException("시스템 문제로 락을 획득할 수 없습니다.", ErrorCode.G003); | ||
} | ||
} | ||
|
||
log.debug("레투스 락 획득! LOCK: " + key); | ||
} | ||
|
||
@Override | ||
public void unlock(String key) { | ||
redisOperations.delete(key); | ||
} | ||
|
||
private boolean lock(String key) { | ||
Boolean success = redisOperations.opsForValue().setIfAbsent(key, 1L, leaseTime, TimeUnit.MILLISECONDS); | ||
return success != null && success; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/com/wootecam/luckyvickyauction/core/lock/NoOperationLockProvider.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,17 @@ | ||
package com.wootecam.luckyvickyauction.core.lock; | ||
|
||
import com.wootecam.luckyvickyauction.global.aop.LockProvider; | ||
|
||
public class NoOperationLockProvider implements LockProvider { | ||
|
||
@Override | ||
public void tryLock(String key) { | ||
// 락 동작을 수행하지 않습니다. | ||
} | ||
|
||
@Override | ||
public void unlock(String key) { | ||
// 락 동작을 수행하지 않습니다. | ||
} | ||
|
||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/com/wootecam/luckyvickyauction/core/lock/RedissonLockProvider.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,45 @@ | ||
package com.wootecam.luckyvickyauction.core.lock; | ||
|
||
import com.wootecam.luckyvickyauction.global.aop.LockProvider; | ||
import com.wootecam.luckyvickyauction.global.exception.ErrorCode; | ||
import com.wootecam.luckyvickyauction.global.exception.ServiceUnavailableException; | ||
import java.util.concurrent.TimeUnit; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.redisson.api.RLock; | ||
import org.redisson.api.RedissonClient; | ||
import org.springframework.beans.factory.annotation.Value; | ||
|
||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class RedissonLockProvider implements LockProvider { | ||
|
||
private final RedissonClient redissonClient; | ||
|
||
@Value("${lock.redisson.wait_time:500}") | ||
private int waitTime; | ||
@Value("${lock.redisson.lease_time:200}") | ||
private int leaseTime; | ||
|
||
@Override | ||
public void tryLock(String key) { | ||
RLock rLock = redissonClient.getLock(key); | ||
|
||
try { | ||
boolean available = rLock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS); | ||
if (!available) { | ||
throw new ServiceUnavailableException("TimeOut에 도달했습니다.", ErrorCode.G002); | ||
} | ||
log.debug("==> 레디슨 락 획득! LOCK: {}", key); | ||
} catch (InterruptedException e) { | ||
throw new ServiceUnavailableException("시스템 문제로 락을 획득할 수 없습니다.", ErrorCode.G003); | ||
} | ||
} | ||
|
||
@Override | ||
public void unlock(String key) { | ||
RLock rLock = redissonClient.getLock(key); | ||
rLock.unlock(); | ||
log.debug("<== 레디슨 락 해제! LOCK: {}", key); | ||
} | ||
} |
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
Oops, something went wrong.