2025. 5. 24. 15:20ㆍ향해99 8기
★★★ 정확히 이해가 안되어서 실제 내 코드를 빗대어서 정리
1. 주문-결제 핵심 로직 완료
- PointPaymentHandler.payWithPoints(order, user) 내부에서
- 포인트 차감
- 주문 상태 → PAID
- (트랜잭션 커밋)
2. 이벤트 발행 (Producer)
- 기존 : 트랜잭션 커밋 직후(afterCommit) “커밋 직후 비동기 이벤트 발행”
파사드에 트랜잭션을 걸지 않았으므로, 핵심 로직 트랜잭션 커밋 시점을 기준으로 이벤트를 발행
paymentEventPublisher.publish(new PointPaymentCompletedEvent(orderId, userId))
@TransactionalEventListener(phase = AFTER_COMMIT) 로 “커밋 후”를 보장하고
@Async 로 “비동기” 처리하도록 설정하여
결제 로직과 완전히 분리된 상태에서 외부 플랫폼에 메시지를 전송
- 이 호출이 Kafka Producer 역할을 해서
- 토픽: order-paid
- 키: orderId (파티션 결정용)
- 페이로드: PointPaymentCompletedEvent(orderId, userId) 객체
★★★★★ 의문점
order-paid 토픽
키 = orderId → 각 주문 이벤트가 고유 파티션에 들어감
파티션 키 = orderId (or couponTemplateId)
같은 키의 메시지는 같은 파티션으로 가서 순차 처리 보장
(파티션)주문 1번 -
(파티션)주문 2번 -
동시성 이슈는 거의 없음 주문-결제에는 카프카를 사용할 필요가 없지 않나???
coupon-request 토픽
사용해야 한다면 선착순 쿠폰 예를 들면
(파티션)쿠폰 1 - 선착순 100명
(파티션)쿠폰 2 - 선착순 200명
키 = couponTemplateId → 같은 쿠폰 요청만 같은 파티션에 몰아 순차 발급
재고 초과 없이 선착순 처리 가능
동시성 제어 관점만 보면 주문-결제 이벤트에 KafKa가 필수는 아니다
★★★ 그래도 쓰는 이유
1. 동시성 제어가 주목적은 아니지만, 비동기·분리·내구성·확장성 때문에 Kafka를 쓸 수 있음
2. 선착순 쿠폰: 동시성 제어(순차 발급) 가 주목적이므로, Kafka 파티션 큐가 아주 잘 맞음
9주차 과제(카프카)는 선착순 쿠폰으로 구현해보기
★★★★★ 선착순 쿠폰 - 카프카 도입
1. 그림 설명
- 토픽: coupon-publish-request
- 파티션: N개 (예: 3개)
- 키 = couponId 로 메시지 발행
- 같은 couponId 요청은 동일 파티션 → 순차 처리 보장
- 다른 couponId는 서로 다른 파티션 → 병렬 처리 가능

2. 카프카 설정
- Kafka 브로커 띄우기
- Spring Boot 설정
- 필요 빈(Bean) 설정
- 테스트 전용 Kafka(TestContainers)
@EnableKafka 한 줄로
- @KafkaListener 를 감지할 수 있게 해 주고,
- spring-kafka 의존성과 spring.kafka.* 설정을 기반으로
- ProducerFactory<String, ?>
- KafkaTemplate<String, ?>
- ConsumerFactory<String, ?>
- ConcurrentKafkaListenerContainerFactory<String, ?>
카프카 돌아가는지 확인
docker-compose ps
docker logs -f server-java-kafka-1
3. 선착순 쿠폰 수정
1. 기존에 파사드에서 락획득, 중복-재고 검사, 발급 처리를 하였는데 전부 삭제하고
2. 발급 요청 이벤트만 발행
3. 결론적으로 어떻게 구현하고 싶냐면
Kafka를 잘 써서 락 없이도 동시성 문제를 해결할 수 있고,
DB 커밋 성공 이후에만 다음 이벤트를 발행
★★★★★ 4. 선착순 쿠폰 구현 순서 (Kafka 2회 전송)
1. 요청 발행 (CouponRequestEvent)
- 위치: CouponFacadeImpl.issueCoupon()
kafkaTemplate.send(
"coupon-publish-request", // 토픽
couponId.toString(), // 메시지 키
new CouponRequestEvent(userId, couponId) // 페이로드
);
- 목적: 요청을 분산·순차 처리 큐에 올리기
2. 파티션 순차 처리
- Kafka 브로커가 couponId 키로 같은 쿠폰 요청을 한 파티션에 모아 순차 처리
3. 비즈니스 로직 & 완료 이벤트 준비
- 위치: @KafkaListener가 붙은 Consumer
- 동작:
- couponService.issueCoupon(userId, couponId) 호출
- 내부에서 중복·재고·유효기간 검증 + DB 발급 처리
- 커밋 직후 afterCommit() 에서 오프셋 커밋(ack.acknowledge()) 준비
4. 완료 통보 (CouponIssuedEvent)
- 위치: Consumer afterCommit() 콜백
kafkaTemplate.send(
"coupon-issued", // 토픽
couponId.toString(), // 메시지 키
new CouponIssuedEvent(userId, couponId) // 페이로드
);
ack.acknowledge(); // 오프셋 커밋
- 목적: 발급 완료를 downstream(알림·통계·데이터 플랫폼 등)에 통보
첫 전송 (coupon-publish-request): 요청을 분산·순차 처리
두 전송 (coupon-issued): 성공 여부를 비즈니스 후속 시스템에 알림
★ 발제 정리
① 메시지 흐름
- Producer: 메시지<key, message> 를 “토픽”에 발행
- KAFKA(Broker): 받은 메시지 저장·관리 (클러스터로 고가용성·확장성)
- Consumer: 저장된 메시지 읽어 처리
- Offset 관리: Consumer가 처리 위치를 직접 추적·커밋 (중복/누락 방지)

② 토픽 & 파티션
- Topic: 메시지 분류 단위
- Partition: 토픽을 쪼갠 조각
- 같은 파티션 안 메시지는 순차 보장
- 파티션 수만큼 병렬 처리 가능 → 처리량↑
- Partitioner:
- 키가 있으면 hash(key) % 파티션수 → 키별 순서 유지
- 키 없으면 Round-Robin 분배

③ 컨슈머 그룹 & 리밸런싱
- Consumer Group:
- 하나의 토픽을 그룹 단위로 구독
- 그룹 내 파티션을 멤버에게 1:1 분배해 병렬 소비
- Rebalancing:
- 컨슈머 추가/제거, 파티션 변경 시 파티션 소유권 재조정
- 중단 없이 소비자 수·파티션 수 최적화

④ 파티션↔컨슈머 매핑 & 복제
- 매핑 시나리오:
- 멤버 < 파티션 → 일부 파티션 미소비
- 멤버 = 파티션 → 최적
- 멤버 > 파티션 → 일부 멤버 유휴
- Replication:
- 각 파티션에 Leader/Follower 복제본 생성
- Leader 장애 시 Follower 승격 → 데이터 가용성 확보

'향해99 8기' 카테고리의 다른 글
| [ 10주차 과제 ] 장애대응 (0) | 2025.06.04 |
|---|---|
| [ 8주차 과제 ] 대용량 트래픽&데이터 처리 (0) | 2025.05.19 |
| [ 7주차 과제 ] 대용량 트래픽&데이터 처리 (0) | 2025.05.19 |
| [ 6주차 과제 ] 대용량 트래픽&데이터 처리 - 피드백 (0) | 2025.05.12 |
| [ 6주차 과제 ] 대용량 트래픽&데이터 처리 - 정리 (0) | 2025.04.29 |