
안녕하데요? 재영입니따
오늘은 저번 글에 이어서, 선착순 쿠폰 발급 시스템의 로직을 살짝 변경해서
Redis의 Set 자료구조를 찍먹해보는 시간을 가져보겠습니다.
저번 글은 여기서 확인해주세요이~~~
선착순 쿠폰 발급 시스템 개발을 통해 Redis 랑 Kafka 찍먹해보기
안녕하세요? 재영입니따 프로젝트 개발을 끝내고 오랜만에 블로그에 왔습니다오늘 정리해볼건 여태 안 듣고 미뤄놨던 Redis 랑 Kafka 찍먹 강의를 듣고학습한걸 끄적여보려고 합니다 이거 진
chuuuu1224.tistory.com
그럼 레쭈고기리기릿
1. 개요
저번 글에서까지 정리한 선착순 쿠폰 발급 시스템의 로직은 한 명의 사용자가 여러 개의 쿠폰을 중복해서 발급받을 수 있습니다.
근데 다른 서비스에서 쿠폰 받는 걸 생각해 보면... 보통 선착순으로 받는 쿠폰은 사용자 한 명이 한 장의 쿠폰만 발급받도록 제한해 둔 경우가 많습니다.
그래서 저도 쿠폰 발급 시스템 로직을 변경해서 1인당 발급 가능한 쿠폰 개수를 1개로 제한해보려고 합니다.
1인당 쿠폰 개수를 제한하는 방법은 여러 가지 방법이 있는데, 그중 하나는 데이터베이스의 유니크키를 사용하는 방법을 사용할 수 있습니다. 하지만, 이 방법은 다른 쿠폰을 발급받을 때 제약이 생길 수 있어서 실용적인 방법은 아닙니다.
그래서 Set 자료구조를 찍먹 해볼 겸사겸사 Redis에서 Set 자료구조를 활용해서 쿠폰 개수를 1개로 제한해 보겠습니다~
2. Redis의 Set 자료구조를 쿠폰 발급 로직에 적용하기
Redis 의 Set 자료구조를 사용하려면 sadd 명령어를 수행해 줄 리포지토리를 만들어줘야 합니다.
@Repository
public class AppliedUserRepository {
private final RedisTemplate<String, String> redisTemplate;
public AppliedUserRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public Long add(Long userId) {
return redisTemplate
.opsForSet()
.add("applied_user", userId.toString());
}
}
AppliedUserRepository에 RedisTemplate를 추가해 주고, sadd 명령어를 수행해 줄 add 메소드를 만들어줍니다.
그다음, ApplyService로 가서 로직을 수정해 주면 됩니다.
public void apply(Long userId) {
Long apply = appliedUserRepository.add(userId);
if (apply != 1) {
// apply 가 1이 아니라면, 이 유저는 쿠폰을 이미 발급받은 것임, 쿠폰을 발급하지 않고 return
return;
}
Long count = couponCountRepository.increment(); // redis 에서 쿠폰 카운트를 가지고 오는 로직
if (count > 100) {
return;
}
couponCreateProducer.create(userId); // CouponCreateProducer 를 사용해서 Topic 에 userId 를 전송하는 로직
}
쿠폰 카운트를 가지고 오기 전에, apply 변수가 1인지 아닌지 확인해 주면 1인당 쿠폰 개수를 1개로 제한할 수 있습니다.
진짜로 1인당 1개의 쿠폰만 발급되는지 확인하는 테스트케이스를 작성해 보겠습니다!
@Test
public void 한명당_한개의_쿠폰만_발급() throws InterruptedException {
int threadCount = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
long userId = i;
executorService.submit(() -> {
try {
applyService.apply(1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Thread.sleep(10000);
long count = couponRepository.count();
assertThat(count).isEqualTo(1);
}
1명의 유저가 1000번의 쿠폰 발급 요청을 해도, 결괏값이 1인지 확인해 보면 되는데
결과는 1이 나와 테스트가 정상적으로 통과되었습니다! (오늘도 캡쳐본은 없습니다 우하하... 캡처해 두는 거 깜빡했어요 😅
3. 쿠폰 발급 실패를 기록하는 로직을 추가하기
만약 Kafka의 Consumer에서 쿠폰 발급 시 에러가 발생해서 쿠폰 발급이 안 될 경우에는 어떡하면 좋을까요...?
현재는 컨슈머에서 토픽에 있는 데이커를 가져간 후에, 쿠폰을 발급하는 과정에서 에러가 발생한다면,
쿠폰은 발급되지 않았는데, 발급된 쿠폰 개수만 올라가는 문제가 발생할 수 있습니다. (omg 내 쿠폰 내놔요
이러면, 설정해 둔 100개의 쿠폰보다 적은 개수가 발급될 수 있다는 얘기입니다. (omg 222
이 문제를 해결하는 방법은 여러 가지가 있지만,
여기서는 쿠폰 발급 시 오류가 발생하면 백업 데이터와 로그를 남겨두도록 설정하려고 합니다!
먼저, 쿠폰 발급 실패 데이터를 담기 위한 FaildEvent라는 엔티티와 리포지토리를 만들어주겠습니다.
(FaildEventRepository 만드는 건 여기서는 생략하겠습니따~)
@Entity
public class FailedEvent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
public FailedEvent() {
}
public FailedEvent(Long userId) {
this.userId = userId;
}
}
그리고, 이전에 만들어뒀던 CouponCreatedConsumer의 로직을 수정해 주면 됩니다.
@Component
public class CouponCreatedConsumer {
private final CouponRepository couponRepository;
private final FailedEventRepository failedEventRepository;
private final Logger logger = LoggerFactory.getLogger(CouponCreatedConsumer.class);
public CouponCreatedConsumer(CouponRepository couponRepository,
FailedEventRepository failedEventRepository) {
this.couponRepository = couponRepository;
this.failedEventRepository = failedEventRepository;
}
@KafkaListener(topics = "coupon_create", groupId = "group_1")
public void listener(Long userId) {
try {
couponRepository.save(new Coupon(userId));
} catch (Exception e) {
logger.error("failed to create coupon::" + userId);
failedEventRepository.save(new FailedEvent(userId));
}
}
}
쿠폰이 발급되지 않는다면, ERROR 레벨의 로그를 남기게 되고, FaildEvent에 userId를 저장하게 됩니다.
이후에 배치 프로그램에서 주기적으로 FaildEvent에 쌓인 데이터를 읽어서, 쿠폰을 발급을 처리해 준다면 100개의 쿠폰을 모두 발급할 수 있게 됩니다.
이렇게 쿠폰을 발급하고, 에러를 처리하는 방법까지 쿠폰 발급 시스템을 만들면서
Redis와 Kafka를 찍먹해보는 과정을 마무리하려고 합니다!
좋은 찍먹 시간이었습니다... 우하하 👍
이번에 배운 걸 통해서 나중에 프로젝트를 진행할 때 동시성을 문제를 효율적으로 해결할 수 있을 것 같습니다.
오늘 글은 여기서 마무리하겠습니다~ 분량 조절 실패인 것 같은데 뭐 어쩔 수 없지 홍홍홍
그럼 20000~
🍀
좋아하는 것을 계속 좋아하세요!
반드시 행복해집니다
[Github] https://github.com/chujaeyeong
[E-mail] chujy1224@gmail.com
'Study > Spring' 카테고리의 다른 글
| Redis 라이브러리를 활용해서 동시성 제어하기 (Lettuce, Redisson) (2) | 2024.08.17 |
|---|---|
| MySQL에서 제공하는 Lock을 이용해서 동시성 제어하기 (Pessimistic Lock, Optimistic Lock, Named Lock) (0) | 2024.08.17 |
| 선착순 쿠폰 발급 시스템 개발을 통해 Redis 랑 Kafka 찍먹해보기 (2) | 2024.07.01 |
| [Spring] Validation (검증) 처리 방법 및 properties 파일의 한글이 출력되지 않는 문제 해결 방법 (2) | 2023.10.12 |
| [Spring] Autowire 불가 에러 해결 방법 (Could not autowire, 스프링 빈 등록 불가 에러) (0) | 2023.09.26 |