기나긴 핫딜 구현이 끝났다.. 정리할 내용이 많은것..
아마 동시성 처리기 2 포스트에서 언급했던 내용으로 쓰레드가 구매 요청을 시도한 순서대로 구매가 이루어지지 않고 있는 것을 제어하고자 시작했던 여정이였다
이를 해결하기 위해 도입한 것이 바로 Redis SortedSet 핫딜이라는 event Key에 유저이메일을 value : 요청 시간의 System.currentTimeMillis을 score값으로 순서대로 정렬한다
@Override
@Transactional
public HotdealWaitResponse waitHotdeal(String hotdealId, Member member) {
Hotdeal hotdeal = findHotdeal(Long.parseLong(hotdealId));
int waitCount = redisUtil.getQueueSize(hotdealId);
if (hotdeal.getDealQuantity() < waitCount * 10) { // 발급 전에 재고 확인후 대기열 추가
return new HotdealWaitResponse("현재 구매 요청이 많아 대기열에 추가할 수 없습니다", waitCount);
}
redisUtil.addPurchaseHotdealMemberToQueueString(hotdealId, member.getEmail(),
System.currentTimeMillis());
return new HotdealWaitResponse("success", waitCount + 1);
}
public void addPurchaseHotdealMemberToQueueString(String queueName, String memberName, double score) {
ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
zSetOperations.add(queueName, memberName, score);
}
처음에는 purchase 로직에 다 우겨 넣으려고 했으나 메서드를 분리하여 대기열에 담는 메서드, 바로 밑에 언급할 대기열에서 확인하는 메서드, 그리고 핫딜 구매 메서드를 떼어내어 역할을 명확히 하고자 했다.
이제 score를 기준으로 정렬되어 지금은 10개 씩 꺼내서 지금 내 차례가 왔는지 확인 하고 구매 로직을 타고 갈 것이다
@GetMapping("/hotdeals/{hotdealId}/isMyTurn")
public ResponseEntity<?> isMyTurn(
@AuthenticationPrincipal MemberDetailsImpl memberDetails,
@PathVariable Long hotdealId
) {
boolean isTurn = hotdealService.isMyHotdealTurn(memberDetails.getMember(),
hotdealId.toString());
if (isTurn) {
return ResponseEntity.ok(true);
} else {
// 상태 코드 202과 함께 false 반환. 다른 상태 코드를 선택할 수도 있음.
return ResponseEntity.status(HttpStatus.ACCEPTED).body(false);
}
}
차례가 왔는지 확인은 client단에서 구매 대기 유저의 차례가 왔는지 polling 방식으로 지속적으로 요청을 날려 statusCode가 200에 true가 왔을때 처리하여 최종적으로 핫딜 구매가 이루어 지는 것 이다.
왜 굳이 polling 방식으로 계속 요청을 해서 부담을 주는가? 라는 의문점 또한 들 수 있다. 따라서 V3에서는 대기열에 담고 eventListener를 통해 n초마다 스케쥴러를 돌려 set안에 구매자들의 구매 대기를 허가해주는 방식으로 추가 구현하였다