-
Notifications
You must be signed in to change notification settings - Fork 29
Feature/opixxx step2 #24
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
Conversation
- 출금, 입금의 트랜잭션 분리 - Redis Hash를 이용해서 출금 기록을 저장 - 스케쥴러를 돌며 Redis Hash에서 출금 기록을 확인하며 알맞은 계좌에 입금 - 입금 시 멀티 스레드를 이용한 병렬 처리
- 출금, 입금의 트랜잭션 분리 - Redis Hash를 이용해서 출금 기록을 저장 - 스케쥴러를 돌며 Redis Hash에서 출금 기록을 확인하며 알맞은 계좌에 입금 - 입금 시 멀티 스레드를 이용한 병렬 처리
- 기존 Redis Hash -> Redis List로 변경 - 기존 방법은 개별 출금 기록을 추적할 수 없어 입금 취소를 할 수 없음 - 입금 실패 시 일정 횟수 재시도 후 횟수를 초과할 경우 출금 계좌에 다시 돈을 복구
- 다른 메인 계좌에서 메인 계좌로의 송금 기능으로 인해 충돌 가능성이 높아질 것으로 생각하여 비관적 락으로 변경
- 이벤트 발행으로 코드를 변경하여 테스트 코드도 변경
ZZAMBAs
left a comment
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.
저는 하나의 DB로만 처리하려 했었는데 레디스와 이벤트 처리를 통해 트랜잭션 처리를 할 수 있다는 인사이트를 얻어갑니다. 고생 많으셨어요!
| //기본값 -> Repeatable Read | ||
| @Transactional(isolation = Isolation.READ_COMMITTED) | ||
| public void chargeMoney(Long accountId, long money) { | ||
| Account account = accountRepository.findByIdWithLock(accountId) |
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.
비관적 락으로 바꾸신 것 같아요. 혹시 이유가 있으실까요?
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.
- 메인 계좌(A) -> 메인 계좌(B) 송금
- 메인 계좌(B) 충전
- 메인 계좌(B) -> 적금 계좌로 송금
이 처럼 동시에 B 계좌에 접근을 할 요청이 많아 충돌 가능성이 높아질 것이라고 생각해서 비관적 락으로 변경했습니다.
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.
👍
| long money = Long.parseLong(parts[3]); | ||
|
|
||
| try { | ||
| redisTemplate.opsForList().remove(PENDING_DEPOSIT, 1, deposit); |
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.
이 코드가 멀티 스레드 처리가 가능한 이유가 레디스는 연산을 싱글 스레드로 처리하기 때문이 맞을까요?
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.
레디스 연산이 원자적이라서 멀티 스레드 처리를 해도 동시성 문제가 없다고 생각했습니다.
| log.debug("입금 성공: transactionId={}, senderAccountId={}, receiverAccountId={}, money={}", | ||
| transactionId, senderAccountId, receiverAccountId, money); | ||
| } catch (Exception e) { | ||
| redisTemplate.opsForList().rightPush(FAILED_DEPOSIT, deposit); |
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.
위 redisTemplate.opsForList().remove(PENDING_DEPOSIT, 1, deposit)에서 에러가 나서 이 코드가 실행될 수도 있나요?
| try { | ||
| redisTemplate.opsForList().remove(PENDING_DEPOSIT, 1, deposit); | ||
|
|
||
| Account receiverAccount = accountRepository.findByIdWithLock(receiverAccountId) |
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.
트랜잭션이 없는 상태에서의 락은 바로 풀어지지 않나요? processDeposit()이 여러 동일 receiverAccountId에 대해 정상 작동하나요?
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.
그렇겠네요. 락이 바로 풀어지겠네요. 테스트 시 정상 동작하긴 했는데 한 번 더 확인해봐야겠습니다.
- 출금(재시도) 성공 시 커밋 완료 후 이벤트 리스너를 사용하여 Redis에 저장된 송금 내역 삭제 - 출금(재시도) 실패 시 예외를 감지하는 AOP를 통해 송금 기록이 PENDING_DEPOSIT에서 제거되고, FAILED_DEPOSIT에 추가된다.
|



메인 계좌(A) -> 메인 계좌(B) 송금
메인 계좌 A에서의 출금 시 걸리는 락을 메인 계좌 B에 입금 작업이 끝날 때까지 가지고 가기 때문에 락의 범위를 줄이기 위해 분리했습니다.
송금 요청 시 출금을 하고 커밋을 완료 후 Redis List에 출금 내용을 저장하는 이벤트를 발행합니다.
입금 트랜잭션의 경우 스케줄러를 돌면서 Redis List에 저장되어 있는 출금 기록을 통해 입금을 수행합니다.
입금 로직의 경우 멀티 스레드를 사용해 병렬 처리를 합니다.
입금 성공 시 Redis List에 출금 내용을 삭제하는 이벤트를 발행한다.
입금 실패 시 AOP를 통해 재시도를 위한 Redis List에 저장 한다.
송금 시 잔액 부족 시 10,000원 단위로 충전 후 송금
송금 시 과정
한도 유효기간 관리
입금 하는 과정에서 실패하는 경우 재시도를 시도하고 재시도도 실패하면 출금을 복구하는 과정으로 구현했는데, 출금을 복구하는 과정에 보상 트랜잭션에서 또 문제가 있을 경우 보상 트랜잭션에 대한 보상 트랜잭션을 구현해야 되는 것인지 고민이 됩니다. 이럴 경우 어떻게 하나요?