안녕하세요? 재영입니따
프로젝트 개발을 끝내고 오랜만에 블로그에 왔습니다
오늘 정리해볼건 여태 안 듣고 미뤄놨던 Redis 랑 Kafka 찍먹 강의를 듣고
학습한 걸 끄적여보려고 합니다

이거 진짜 찍먹 수준이라 Redis 랑 Kafka 써봤다고 하면 안 될 정도고
저도 처음 써보는 거라 얘네들이 뭘 도와주는 친구들인지 알아보는 것만으로도 머리가 터질 것 같습니다 하하하...
그럼~ 레 쭈 고
1. 개요
이번 글에서는 선착순 쿠폰 발급 시스템을 개발하면서 겪었던 문제점과 이를 해결하기 위해 사용한 Redis와 Kafka 가 어떻게 선착순 쿠폰 발급 시스템에서 작동하는지에 대해 설명하겠습니다. 이 과정을 통해 저는 많은 사용자가 몰려서 발생할 수 있는 동시성 문제를 해결하고, 데이터베이스 부하 분산 방법, 그리고 쿠폰 발급 로직을 효율적으로 관리하는 방법을 배울 수 있었습니다.
강의 정보 : 인프런 - 실습으로 배우는 선착순 이벤트 시스템 (최상용)
실습으로 배우는 선착순 이벤트 시스템 강의 | 최상용 - 인프런
최상용 | 선착순 이벤트 시스템을 구현할 때 어떤 문제가 발생할 수 있고 어떻게 해결할 수 있는지 배워봅니다., 선착순 이벤트 시스템도 자신있게! 예제를 통해 실전 감각을 잡아보세요. [임
www.inflearn.com
2. MySQL을 사용한 쿠폰 발급 로직과 문제점
초기에는 MySQL 을 사용하여 쿠폰 발급 로직을 작성했습니다. 로직 작성 후, 간단한 테스트를 통해 한 명이 한 장의 쿠폰을 발급하는 경우에는 문제가 없음을 확인했습니다. 아래는 쿠폰을 발급하는 로직을 작성한 ApplyService의 코드입니다.
@Service
public class ApplyService {
private final CouponRepository couponRepository;
public ApplyService(CouponRepository couponRepository) {
this.couponRepository = couponRepository;
}
public void apply(Long userId) {
long count = couponRepository.count(); // mysql 에서 쿠폰 카운트를 가지고 오는 로직
if (count > 100) {
return;
}
couponRepository.save(new Coupon(userId)); // 직접 쿠폰을 생성하는 로직
}
}
그리고, ApplyService 의 쿠폰 발급 로직을 테스트하는 ApplyServiceTest 코드입니다.
여기서는 쿠폰 한 번만 발급하는 테스트를 진행했고, 이 테스트는 문제없이 통과되었습니다.
@SpringBootTest
class ApplyServiceTest {
@Autowired
private ApplyService applyService;
@Autowired
private CouponRepository couponRepository;
@Test
public void 한번만응모() {
applyService.apply(1L);
long count = couponRepository.count();
assertThat(count).isEqualTo(1);
}
}
한 번의 쿠폰 발급 말고, 여러 개의 쿠폰이 동시에 발급되는 경우를 테스트해보겠습니다.
동시에 여러 개의 요청을 보내야 해서 멀티 스레드를 사용했고, 천 개의 요청을 보낼 거라 threadCount를 1000으로 설정해 줬습니다.
그리고 ExecutorService 를 활용해서 병렬 작업을 할 수 있도록 설정해 줍니다.
@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(userId);
} finally {
latch.countDown();
}
});
}
latch.await();
long count = couponRepository.count();
assertThat(count).isEqualTo(100);
}
이 테스트의 결과 기댓값은 100장의 쿠폰이 발급되는 것인데, 여러 사용자가 동시에 쿠폰을 발급하려고 시도할 경우, 동시성 문제가 발생해서 100장 이상 쿠폰이 발급되는 race condition 문제가 발생했습니다. 테스트 완료창을 캡처하진 못했는데, 저는 120장이 발급되는 문제가 발생하여 테스트가 통과되지 못했습니다.
왜 100개의 쿠폰을 발급하도록 서비스 로직을 작성했는데 120개의 쿠폰이 발급되었는가? 는
지금 테스트코드에서 2개 이상의 스레드가 공유자원(쿠폰)에 액세스를 하고, 작업을 하려고 할 때 발생되는 문제가 발생했기 때문입니다. 이걸 race condition이라고 합니다.
스레드가 2개라고 가정하고, 한 개의 스레드에서 쿠폰 발급을 시도할 때, 쿠폰 발급을 완료하지 않은 상태에서 다른 스레드에서 쿠폰 발급을 시도할 경우 쿠폰 카운트가 소모되지 않았기 때문에 우리가 예상했던 쿠폰 발급 개수보다 많은 개수의 쿠폰이 발급되게 되는 것입니다.
race condition을 해결하기 위해 Redis를 활용해서 쿠폰이 동시에 발급될 때 동시성 문제가 발생하는 것을 해결해 보겠습니다.
3. Redis를 이용한 동시성 문제 해결
Redis의 싱글스레드 특성을 활용해서 쿠폰 발급 시 발생할 수 있는 동시성 문제를 해결해보겠습니다.
문제를 해결하기 전에, Redis의 개요와 주요 특징, 사용 사례와 장점을 정리해 보겠습니다!
3-1. Redis 란?
Redis(레디스)는 Remote Dictionary Server의 약자로, 오픈 소스 인메모리 데이터 구조 저장소입니다. Redis는 키-값(key-value) 방식의 데이터 저장소로, 데이터를 메모리에 저장하여 매우 빠른 읽기 및 쓰기 성능을 제공합니다. 다양한 데이터 구조(문자열, 해시, 리스트, 셋, 정렬된 셋)를 지원하여 여러 가지 방식으로 데이터를 관리할 수 있습니다.
Redis의 주요 특징을 정리해보겠습니다.
- 인메모리 저장소 : 모든 데이터를 메모리에 저장하여 매우 빠른 성능을 자랑합니다.
- 다양한 데이터 구조 : 문자열, 해시, 리스트, 셋, 정렬된 셋 등 다양한 데이터 구조를 지원합니다.
- 지속성 : 데이터를 디스크에 저장하여 서버 재시작 시에도 데이터를 복구할 수 있습니다. RDB(snapshotting)와 AOF(Append-Only File) 방식의 지속성을 지원합니다.
- 복제 : 마스터-슬레이브 복제를 통해 데이터를 복제하여 높은 가용성과 데이터 분산을 제공합니다.
- 고급 기능 : Pub/Sub, Lua 스크립트, 트랜잭션, Geospatial 인덱스 등 다양한 고급 기능을 제공합니다.
- 단순한 설치 및 사용 : 설정이 간단하고 사용하기 쉬워 빠르게 학습하고 적용할 수 있습니다.
그리고, Redis 의 주요 사용 사례입니다.
- 캐싱 : 자주 조회되는 데이터를 캐싱하여 데이터베이스 부하를 줄이고 응답 속도를 높입니다.
- 세션 관리 : 사용자 세션 정보를 관리하여 빠르게 세션 데이터를 읽고 쓸 수 있습니다.
- 실시간 분석 : 실시간으로 데이터를 수집하고 분석하는 데 사용됩니다.
- 메시지 큐 : Pub/Sub 기능을 이용하여 메시지 큐로 사용할 수 있습니다.
- 분산 잠금 : 분산 시스템에서 분산 잠금을 구현하여 동시성 문제를 해결할 수 있습니다.
3-2. Redis 사용 시 장단점, 사용 시 주의사항
Redis 사용 시 장점을 간단하게 정리하면, 4가지로 정리할 수 있을 것 같습니다.
- 빠른 성능 : Redis는 메모리 기반의 데이터 저장소로 매우 빠른 읽기/쓰기 속도를 자랑합니다. 이는 실시간 처리 요구사항이 있는 시스템에 매우 유리합니다.
- 다양한 데이터 구조 : Redis는 다양한 데이터 구조(문자열, 해시, 리스트, 셋 등)를 지원하여 다양한 방식으로 데이터를 저장하고 관리할 수 있습니다.
- 간편한 확장성 : Redis는 클러스터링을 통해 쉽게 확장할 수 있어 대규모 트래픽을 처리하는 데 유리합니다.
- 다양한 사용 사례 : 캐싱, 세션 관리, 실시간 분석, 분산 잠금 등 다양한 용도로 활용할 수 있습니다. 예를 들어, 분산 잠금의 경우, Redis를 이용하면 분산 환경에서의 잠금 메커니즘을 쉽게 구현할 수 있습니다. 이는 쿠폰 발급과 같은 동시성 처리가 중요한 시스템에 유용하게 사용할 수 있습니다.
우리가 위에서 쿠폰 발급 시 발생하는 race condition 문제를 해결하기 위해서 Redis를 사용하는 건데, Redis는 다양한 race 상황을 방지하고자 싱글스레드로 동작합니다. 여기에서 발생하는 단점과, 사용 시 주의사항을 정리해 보겠습니다.
1. 단점
1.1 싱글스레드 기반의 한계
- CPU 활용의 한계 : Redis는 싱글스레드로 동작하기 때문에, 단일 인스턴스에서는 멀티코어 CPU를 충분히 활용하지 못합니다. 이는 CPU 집약적인 작업을 수행할 때 병목 현상이 발생할 수 있습니다.
- 블로킹 연산 : 일부 연산이 시간이 오래 걸리면, 다른 클라이언트의 요청도 지연될 수 있습니다. 예를 들어, 대용량 데이터에 대한 복잡한 연산이나 Lua 스크립트 실행 시 블로킹이 발생할 수 있습니다.
1.2 메모리 기반의 한계
- 메모리 제한 : Redis는 인메모리 데이터베이스이므로, 저장할 수 있는 데이터 양이 서버의 물리적 메모리에 의해 제한됩니다. 대량의 데이터를 저장해야 하는 경우 메모리 부족 문제가 발생할 수 있습니다.
- 비용 문제 : 메모리를 많이 사용하는 시스템의 경우, 메모리 비용이 높아질 수 있습니다. 이는 특히 클라우드 환경에서 비용 증가로 이어질 수 있습니다.
1.3 데이터 일관성 문제
- 비정상 종료 시 데이터 손실 : Redis는 메모리에 데이터를 저장하므로, 비정상 종료 시 데이터가 손실될 수 있습니다. RDB 스냅샷이나 AOF를 통해 데이터를 디스크에 저장하지만, 이 과정에서 약간의 데이터 손실이 발생할 수 있습니다.
2. Redis 사용 시 주의사항
2.1 데이터 지속성 설정
- RDB와 AOF 설정 : Redis의 RDB 스냅샷과 AOF(Append-Only File) 설정을 통해 데이터를 디스크에 주기적으로 저장하여 비정상 종료 시 데이터 손실을 최소화해야 합니다. AOF는 지속적으로 로그를 기록하므로, 더 높은 데이터 보존성을 제공합니다.
2.2 적절한 데이터 구조 선택
- 효율적인 데이터 구조 사용 : Redis는 다양한 데이터 구조를 지원하므로, 사용 사례에 맞는 적절한 데이터 구조를 선택해야 합니다. 예를 들어, 리스트 대신 셋을 사용하여 중복을 방지하거나, 해시를 사용하여 필드-값 쌍을 효율적으로 관리할 수 있습니다.
2.3 메모리 사용 관리
- 메모리 용량 모니터링 : Redis의 메모리 사용량을 주기적으로 모니터링하고, 설정한 메모리 한계를 초과하지 않도록 관리해야 합니다. maxmemory 설정을 통해 메모리 사용량을 제한하고, 초과 시 LRU(Least Recently Used) 등 적절한 메모리 정리 정책을 설정할 수 있습니다.
2.4 블로킹 연산 피하기
- 복잡한 연산 분산 : Redis에서 시간이 오래 걸리는 복잡한 연산은 피해야 합니다. 필요시, 해당 연산을 애플리케이션 레벨에서 처리하거나 분산 처리하여 Redis 서버의 응답 속도를 유지해야 합니다.
- Lua 스크립트 주의 : Lua 스크립트를 사용할 때, 스크립트가 너무 오래 실행되지 않도록 주의해야 합니다. 복잡한 스크립트는 여러 개의 작은 스크립트로 나누어 실행하는 것이 좋습니다.
2.5 보안 설정
- 접근 제어 : Redis 서버에 대한 접근을 제한하고, 인증을 설정하여 비인가 사용자가 접근하지 못하도록 해야 합니다. requirepass 설정을 통해 비밀번호 인증을 추가할 수 있습니다.
- 네트워크 보안 : Redis는 기본적으로 암호화되지 않은 평문으로 데이터를 주고받으므로, TLS/SSL을 설정하여 네트워크 통신을 암호화하는 것이 좋습니다.
2.6 클러스터링 및 샤딩
- 클러스터링 사용 : Redis 클러스터를 구성하여 데이터를 여러 노드에 분산 저장하고, 확장성과 가용성을 높일 수 있습니다. 클러스터링을 통해 싱글스레드의 한계를 극복할 수 있습니다.
- 샤딩 전략 : 샤딩을 통해 데이터를 여러 Redis 인스턴스에 분산하여 저장하고, 개별 인스턴스의 부하를 분산시킬 수 있습니다.
이렇게 Redis의 특징을 알고 사용한다면 Redis의 장점을 최대한 활용하고, 안정적이고 효율적인 시스템을 구축할 수 있을 것입니다.
자 이제 다시 프로젝트로 넘어가서, Redis를 사용해서 race condition을 해결하는 방법을 설명하겠습니다!
3-3. Redis를 쿠폰 발급 시스템에 적용하기
Redis 에는 incr이라는 명령어가 있는데, 이 명령어는 key에 대한 value를 1씩 증가시키는 명령어입니다.
이 명령어를 사용해서 쿠폰 발급 시 발생하는 race condition을 해결하겠습니다. 먼저, Redis의 명령어를 사용하기 위해 Repository를 하나 만들어줘야 합니다.
@Repository
public class CouponCountRepository {
private final RedisTemplate<String, String> redisTemplate;
public CouponCountRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// redis 에서 incr 명령어를 사용하기위한 메소드
public Long increment() {
return redisTemplate
.opsForValue()
.increment("coupon_count");
}
}
요로코롬 만들어주면 Redis에서 사용하는 incr 명령어를 메소드로 사용할 수 있습니다. 이걸 ApplyService에 적용하면 됩니다.
public void apply(Long userId) {
Long count = couponCountRepository.increment(); // redis 에서 쿠폰 카운트를 가지고 오는 로직
if (count > 100) {
return;
}
couponRepository.save(new Coupon(userId)); // 직접 쿠폰을 생성하는 로직
}
이렇게 로직을 변경해서 아까 실패했던 테스트를 다시 시도해 보면, 테스트가 정상적으로 통과되는 것을 확인할 수 있습니다.
물론 저는 확인했는데 캡쳐하는 걸 까먹어서... 네... 통과했습니다... 😅
3-4. Redis를 활용한 쿠폰 발급 로직의 문제점
겉보기에는 테스트도 잘 통과되었고 Redis를 활용해서 race condition 도 잡았으니 문제없는 것 아닌가?라고 생각이 들 수 있겠지만, 발급하는 쿠폰이 만약에 100만 장 막 이렇다면? 발급하는 쿠폰의 개수가 많아지면 많아질수록, 쿠폰을 저장하는 RDB에 부하가 올 것입니다.
지금은 단순히 100장만 발급하는 거라 큰 부하가 생기진 않겠지만, 만약 실제 서비스에서 사용하는 RDB 가 쿠폰 전용 DB가 아니라, 다양한 곳에서 사용하는 DB라면, 다른 기능의 성능에도 영향을 줄 수 있습니다.
인강에서 쓰앵님이 nGrinder를 사용해서 DB에 부하를 주는 걸 확인했는데, 갑자기 DB에 부하가 오니까 발생하지 않은 에러가 터지는 걸 확인했습니다... 물론 이것도 그냥 제 눈으로 확인한 거라 그냥 그렇다고 믿어주시면 감사하겠습니다 하하하
그래서 이번에는 Kafka를 쿠폰 발급 시스템에 도입해서 데이터를 효율적으로 처리해 보겠습니다.
4. Kafka를 이용해서 DB 부하를 방지하기
4-1. Kakfa 란?
Kafka를 프로젝트에 도입하기 전에, 먼저 Kafka에 대해 정리하고 넘어가겠습니다.
Apache Kafka(아파치 카프카)는 분산 스트리밍 플랫폼으로, 실시간 데이터 스트리밍과 로그 수집, 이벤트 소싱 등을 위한 고성능 메시지 큐 시스템입니다. Kafka는 대규모의 데이터를 효율적으로 처리하고, 높은 내구성과 확장성을 제공합니다.
아래는 Kafka의 구성 요소와 데이터의 흐름을 그림으로 그린 것입니다!

그럼 이제 Kafka의 주요 특징을 정리해 보겠습니다.
- 분산 시스템 : 여러 노드로 구성된 분산 시스템으로 높은 내구성과 가용성을 제공합니다.
- 토픽 기반 메시징 : 데이터를 토픽(topic)으로 분류하여 생산자(producer)와 소비자(consumer)가 주고받을 수 있습니다.
- 파티셔닝 : 데이터는 파티션(partition)으로 나뉘어 저장되며, 각 파티션은 여러 브로커에 분산 저장됩니다.
- 내구성 : 데이터를 디스크에 저장하여 내구성을 보장합니다. 데이터 복제(replication)를 통해 데이터 손실을 방지합니다.
- 확장성 : 노드 추가를 통해 클러스터를 쉽게 확장할 수 있습니다.
- 고성능 : 높은 처리량을 자랑하며, 초당 수백만 개의 메시지를 처리할 수 있습니다.
그리고, Kafka의 주요 사용 사례는 다음과 같습니다.
- 실시간 데이터 스트리밍: 실시간 로그 수집, 분석, 모니터링 시스템 구축에 사용됩니다.
- 이벤트 소싱: 이벤트 기반 시스템에서 이벤트를 저장하고 처리하는 데 사용됩니다.
- 메시지 큐: 대규모의 메시지를 처리하고, 여러 소비자에게 분산 처리할 수 있습니다.
- 로그 수집 및 처리: 분산 환경에서 로그 데이터를 수집하고 처리하는 데 사용됩니다.
정리하자면, Kafka는 초당 수백만 개의 메시지를 처리할 수 있는 고성능 시스템이라, 지금같이 DB 부하가 발생할 수 있는 곳에 도입해서 많은 양의 이벤트를 바로 처리하지 않고 큐에 보관했다가, 시스템 부하가 적을 때 일괄적으로 처리할 수 있도록 도와줍니다. 여기서 큐는 Kafka의 Topic이라고 생각하면 됩니다. Kafka를 사용하는 이유를 아래에 자세하게 정리해보겠습니따!
4-2. Kafka를 사용하는 이유
1. DB 부하 분산
- Kafka를 도입하면 데이터베이스에 직접적인 부하를 주지 않고, 많은 양의 이벤트를 Kafka의 Topic에 저장합니다.
- 메시지는 Topic에 큐 형태로 저장되며, 시스템 부하가 적을 때 Consumer가 이를 일괄적으로 처리할 수 있습니다.
2. 실시간 데이터 스트리밍
- Kafka는 실시간으로 데이터를 스트리밍 처리할 수 있습니다. Producer가 생성한 메시지를 실시간으로 Topic에 전송하고, Consumer는 이를 실시간으로 처리할 수 있습니다.
3. 높은 처리량 및 확장성
- Kafka는 높은 처리량을 자랑하며, 초당 수백만 개의 메시지를 처리할 수 있습니다.
- Kafka는 분산 시스템으로, 노드를 추가하여 클러스터를 쉽게 확장할 수 있습니다.
4. 내구성 및 신뢰성
- Kafka는 메시지를 디스크에 저장하여 내구성을 보장합니다. 또한, 데이터 복제를 통해 데이터 손실을 방지합니다.
- 각 메시지는 오프셋으로 식별되며, 메시지의 순서와 일관성을 유지할 수 있습니다.
5. 다양한 사용 사례
- 로그 수집 및 처리: 분산 환경에서 로그 데이터를 수집하고 처리하는 데 사용됩니다.
- 이벤트 소싱: 이벤트 기반 시스템에서 이벤트를 저장하고 처리하는 데 사용됩니다.
- 데이터 통합: 다양한 시스템 간의 데이터 통합을 용이하게 합니다.
여기까지 정리하고, 프로젝트에 Kafka를 도입해서 DB 부하를 방지해 보겠습니다~
4-3. Kafka를 쿠폰 발급 시스템에 도입해서 DB 부하를 방지하기
Kafka 를 사용하기 위해서 Kafka 의존성을 추가하고, Config 클래스를 통해 Producer 인스턴스를 생성하는 데 필요한 설정 값들을 세팅해 주겠습니다.
Producer 설정과 Kafka Topic에 데이터를 전송하기 위해 사용할 카프카 템플릿을 Bean으로 등록해 주면 됩니다.
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, Long> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); // 서버 정보 추가
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); // 키 시리얼라이저 클래스 정보 추가
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, LongSerializer.class); // 벨류 시리얼라이즈 클래스 정보 추가
return new DefaultKafkaProducerFactory<>(config);
}
@Bean
public KafkaTemplate<String, Long> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}
그다음으로는, 카프카 템플릿을 사용해서 토픽의 데이터를 전송할 프로듀서를 만들어줍니다.
토픽에 유저 아이디를 전달할 것이라, 유저의 아이디를 매개변수로 가지는 create라는 메소드를 생성해 줬습니다.
@Component
public class CouponCreateProducer {
private final KafkaTemplate<String, Long> kafkaTemplate;
public CouponCreateProducer(KafkaTemplate<String, Long> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void create(Long userId) {
kafkaTemplate.send("coupon_create", userId);
}
}
그다음으로는, Consumer 작업을 위한 설정을 진행하겠습니다. 프로듀서를 설정했던 방식과 비슷하게 컨슈머 설정도 하면 되고,
토픽으로부터 메시지를 전달받기 위해서 ConcurrentKafkaListenerContainerFactory를 사용해서 Bean으로 추가해 줍니다.
@Configuration
public class KafkaConsumerConfig {
@Bean
public ConsumerFactory<String, Long> consumerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ConsumerConfig.GROUP_ID_CONFIG, "group_1");
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class);
return new DefaultKafkaConsumerFactory<>(config);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, Long> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Long> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
그다음, 쿠폰을 생성해 주는 CouponCreateConsumer 클래스를 만들어서 쿠폰을 생성하도록 하겠습니다.
@Component
public class CouponCreatedConsumer {
private final CouponRepository couponRepository;
public CouponCreatedConsumer(CouponRepository couponRepository) {
this.couponRepository = couponRepository;
}
@KafkaListener(topics = "coupon_create", groupId = "group_1")
public void listener(Long userId) {
couponRepository.save(new Coupon(userId));
}
}
그리고, ApplyService에 쿠폰 발급 로직을 수정하겠습니다.
직접 쿠폰을 생성하던 로직을 삭제하고, CouponCreateProducer를 사용해서 토픽에 userId를 전송하는 로직으로 변경해 줍니다.
public void apply(Long userId) {
Long count = couponCountRepository.increment(); // redis 에서 쿠폰 카운트를 가지고 오는 로직
if (count > 100) {
return;
}
couponCreateProducer.create(userId); // CouponCreateProducer 를 사용해서 Topic 에 userId 를 전송하는 로직
}
자 이렇게 하면 Kafka를 통해 DB 부하 이슈를 방지하면서 쿠폰 발급을 할 수 있게 되었습니다.
다음으로는 발급 가능한 쿠폰 개수를 1인당 1개로 제한하고, 쿠폰 발급 실패 시 로그를 남기는 단계가 남았는데, 지금 중간중간 Redis와 Kafka를 설명하면서 적다 보니 글의 길이가 너무 길어져서 그건 다음 글에 이어서 작성해보겠습니따!!!
그럼 20000~
🍀
좋아하는 것을 계속 좋아하세요!
반드시 행복해집니다
[Github] https://github.com/chujaeyeong
[E-mail] chujy1224@gmail.com