- Kafka κ°μ
- μ μ© λ°°κ²½ λ° μ΄μ
- μμ€ν ꡬμ±
- μ£Όμ κ°μ μ¬ν
- κΈ°μ μ μΈλΆμ¬ν
- λͺ¨λν°λ§ λ° μ΄μ
- ν₯ν νμ₯ κ°λ₯μ±
Apache Kafkaλ λΆμ° μ΄λ²€νΈ μ€νΈλ¦¬λ° νλ«νΌμΌλ‘, λμ©λμ μ€μκ° λ°μ΄ν°λ₯Ό μμ μ μΌλ‘ μ²λ¦¬ν μ μλ λ©μμ§ λΈλ‘컀μ λλ€.
- λμ μ²λ¦¬λ: μ΄λΉ μλ°±λ§ κ°μ λ©μμ§ μ²λ¦¬ κ°λ₯
- νμ₯μ±: μν νμ₯μ ν΅ν ν΄λ¬μ€ν° κ΅¬μ± μ§μ
- λ΄κ΅¬μ±: λμ€ν¬ κΈ°λ° λ°μ΄ν° μμμ±μΌλ‘ λ©μμ§ μ μ€ λ°©μ§
- λΆμ° μ²λ¦¬: νν°μ λκ³Ό 볡μ λ₯Ό ν΅ν κ³ κ°μ©μ± 보μ₯
| κ°λ | μ€λͺ |
|---|---|
| Producer | λ©μμ§λ₯Ό Kafka ν ν½μ λ°ννλ 주체 |
| Consumer | Kafka ν ν½μ λ©μμ§λ₯Ό ꡬλ νμ¬ μλΉνλ 주체 |
| Topic | λ©μμ§λ₯Ό λΆλ₯νλ λ Όλ¦¬μ μ±λ |
| Partition | ν ν½μ λλ 물리μ λ¨μ, λ³λ ¬ μ²λ¦¬ κ°λ₯ |
| Broker | Kafka μλ² μΈμ€ν΄μ€ |
| Consumer Group | νλ ₯νμ¬ λ©μμ§λ₯Ό μλΉνλ Consumer κ·Έλ£Ή |
κΈ°μ‘΄ μμ€ν μμλ μ£Όλ¬Έ κ²°μ μλ£ μ Spring Event κΈ°λ°μΌλ‘ μ΄λ²€νΈ 리μ€λκ° μΈλΆ λ‘κΉ μμ€ν μ μ§μ νΈμΆνμ¬ μ΄λ²€νΈλ₯Ό μ μ‘νμ΅λλ€.
// κΈ°μ‘΄ λ°©μ (Spring Event + μ§μ HTTP νΈμΆ)
OrderService.processPayment()
ββ> Spring Event λ°ν (OrderPaidEvent)
ββ> OrderEventListener (@Async)
ββ> ExternalLoggingClient.sendLog() // μΈλΆ μμ€ν
μ§μ HTTP νΈμΆ
ββ> μ±κ³΅: μ μ μ²λ¦¬
ββ> μ€ν¨: μ΄λ²€νΈ μ μ€ (λ‘κ·Έλ§ κΈ°λ‘)- μ ν리μΌμ΄μ λ΄λΆ κ²°ν©: λμΌ JVM λ΄λΆμμλ§ λμ, λ€λ₯Έ μλΉμ€λ‘ νμ₯ λΆκ°
- κ°ν κ²°ν©: μ΄λ²€νΈ 리μ€λκ° μΈλΆ λ‘κΉ μμ€ν κ³Ό μ§μ μ°κ²°
- λ©μμ§ μ μ€ μν: μΈλΆ μμ€ν μ₯μ μ μ΄λ²€νΈ μμ€ (μ¬μλ μμ)
- μ₯μ μΆμ μ΄λ €μ: μ€ν¨ν μ΄λ²€νΈμ λν μΆμ λ° μ¬μ²λ¦¬ λΆκ°
Kafkaλ₯Ό λ©μμ§ λΈλ‘μ»€λ‘ λμ νμ¬ μ΄λ²€νΈ κΈ°λ° μν€ν μ²λ‘ μ ννμ΅λλ€.
| ν¨κ³Ό | μ€λͺ |
|---|---|
| λΉλκΈ° μ²λ¦¬ | μ£Όλ¬Έ μ²λ¦¬μ λ‘κΉ μ΄ λΆλ¦¬λμ΄ μλ΅ μκ° λ¨μΆ |
| μ₯μ 격리 | μΈλΆ μμ€ν μ₯μ κ° λΉμ¦λμ€ λ‘μ§μ μν₯ μμ |
| νμ₯μ± | μλ‘μ΄ Consumer μΆκ°λ§μΌλ‘ κΈ°λ₯ νμ₯ (μλ¦Ό, λΆμ λ±) |
| μ λ’°μ± | λ©μμ§ μμμ±κ³Ό μ¬μλλ‘ λ°μ΄ν° μ μ€ λ°©μ§ |
μ°Έκ³ : μΏ ν° λ°κΈμλ kafka μ μ©νμ§ μκ³ Redis Streams μ¬μ© μ μ§
- μΏ ν° λ°κΈμ μ ν©μ±κ³Ό μ¦κ°μ μΈ μ²λ¦¬ μλκ° λ μ€μνλ€. λ©λͺ¨λ¦¬ κΈ°λ°μ Redis Streams μ¬μ©μ μ μ§νλ€.
- μ¬μ²λ¦¬ ꡬνλ νμ§ μμλ€. λ¨λ°μ μΈ κ²½μ μ²λ¦¬μ΄κΈ° λλ¬Έμ μ¬μλλ μλ―Έμλ€κ³ νλ¨νλ€.
OrderService
ββ> κ²°μ μλ£ μ Spring Event λ°ν (νΈλμμ
μ»€λ° μ΄ν μ€ν)
ββ> OrderEventPublisher
ββ> OrderEventListener (λΉλκΈ°)
ββ> OrderKafkaProducer
ββ> Kafka ν ν½ "order-paid-events"λ‘ λ°ν
μ£Όμ ν΄λμ€:
OrderService: μ£Όλ¬Έ λΉμ¦λμ€ λ‘μ§ μ²λ¦¬OrderEventPublisher: Spring ApplicationEventPublisher νμ©OrderEventListener:@TransactionalEventListenerνμ©νμ¬ νΈλμμ μ»€λ° μ΄ν μ€νOrderKafkaProducer: Kafka Producer ꡬνOrderKafkaMessage: λ©μμ§ DTO
λΈλ‘컀 ꡬμ±:
- localhost:19092 (broker-1)
- localhost:29092 (broker-2)
- localhost:39092 (broker-3)
ν ν½ μ€μ :
- order-paid-events: νν°μ
3κ°, 볡μ λ³Έ 3κ°
- order-paid-events.DLQ: Dead Letter QueueKafka ν ν½ "order-paid-events"
ββ> LoggingKafkaConsumer
ββ> λ©μμ§ μμ λ° μμ§λ ¬ν
ββ> ExternalLoggingClient
ββ> μΈλΆ λ‘κΉ
μμ€ν
(http://localhost:3000/logs)
μ£Όμ ν΄λμ€:
LoggingKafkaConsumer:@KafkaListenerνμ©ExternalLoggingClient: RestTemplate κΈ°λ° HTTP ν΄λΌμ΄μΈνΈ
{
"eventType": "ORDER_PAID",
"orderId": 123,
"userId": 456,
"paidAt": "2025-12-19T10:30:00",
"orderItems": [
{
"productId": 1,
"orderQuantity": 2
}
]
}μ€μ (application.properties):
spring.kafka.producer.acks=all # λͺ¨λ replica ACK λκΈ°
spring.kafka.producer.properties.enable.idempotence=true # λ©±λ±μ± 보μ₯
spring.kafka.producer.retries=3 # μ΅λ 3ν μ¬μλ
spring.kafka.producer.properties.retry.backoff.ms=1000 # μ¬μλ κ°κ²© 1μ΄ν¨κ³Ό:
- μΌμμ λ€νΈμν¬ μ€λ₯ μλ 볡ꡬ
- λ©μμ§ μ€λ³΅ λ°©μ§ (λ©±λ±μ±)
- λ©μμ§ μμ 보μ₯
Kafka λ°ν μ€ν¨ μ λ©μμ§λ₯Ό λ°μ΄ν°λ² μ΄μ€μ μ μ₯νκ³ , μ£ΌκΈ°μ μΌλ‘ μ¬λ°νμ μλν©λλ€.
OrderKafkaProducer
ββ> Kafka λ°ν μλ
ββ> [μ±κ³΅] λ‘κ·Έ κΈ°λ‘
ββ> [μ€ν¨] KafkaFallbackService
ββ> kafka_fallback_message ν
μ΄λΈμ μ μ₯
ββ> KafkaFallbackScheduler (1λΆλ§λ€ μ€ν)
ββ> μ¬λ°ν μλ
ββ> μ±κ³΅: status = PUBLISHED
ββ> μ€ν¨: μ§μ λ°±μ€ν ν μ¬μλ
KafkaFallbackMessage μν°ν°:
| νλ | μ€λͺ |
|---|---|
topic |
Kafka ν ν½λͺ |
messageKey |
λ©μμ§ ν€ |
payload |
JSON λ©μμ§ λ³Έλ¬Έ |
retryCount |
νμ¬ μ¬μλ νμ |
maxRetry |
μ΅λ μ¬μλ νμ (κΈ°λ³Έ: 3) |
status |
PENDING / PUBLISHED / FAILED |
nextRetryAt |
λ€μ μ¬μλ μκ° |
errorMessage |
μ€ν¨ μ¬μ |
KafkaFallbackService:
saveFallbackMessage(): Fallback λ©μμ§ DB μ μ₯retryPendingMessages(): μ¬μλ λμ μ‘°ν λ° μ¬λ°νgetStats(): ν΅κ³ μ‘°ν (PENDING, PUBLISHED, FAILED 건μ)
KafkaFallbackScheduler:
- 1λΆλ§λ€ λκΈ° μ€μΈ λ©μμ§ μ¬λ°ν μλ
- 1μκ°λ§λ€ ν΅κ³ λ‘κΉ
- μ§μ λ°±μ€ν: 1λΆ, 2λΆ, 4λΆ, 8λΆ (2^n λΆ)
- μ΅λ μ¬μλ: 3ν
- μ΅μ’ μν: PUBLISHED (μ±κ³΅) / FAILED (μ΅λ μ¬μλ μ΄κ³Ό μ μλ μ²λ¦¬ νμ)
Consumer μ²λ¦¬ μ€ν¨ μ μλ μ¬μλ ν, μ΅μ’ μ€ν¨ν λ©μμ§λ DLQλ‘ μ μ‘ν©λλ€.
@Bean
public CommonErrorHandler errorHandler(KafkaTemplate kafkaTemplate) {
// μ§μ λ°±μ€ν: 1μ΄, 2μ΄, 4μ΄ (μ΅λ 3ν)
ExponentialBackOffWithMaxRetries backOff = new ExponentialBackOffWithMaxRetries(3);
backOff.setInitialInterval(1000);
backOff.setMultiplier(2.0);
backOff.setMaxInterval(10000);
// DLQ Recoverer: μ€ν¨ λ©μμ§λ₯Ό [μλ³Έν ν½].DLQλ‘ μ μ‘
DeadLetterPublishingRecoverer recoverer =
new DeadLetterPublishingRecoverer(kafkaTemplate, ...);
return new DefaultErrorHandler(recoverer, backOff);
}LoggingKafkaConsumer
ββ> externalLoggingClient.sendLog()
ββ> [μ±κ³΅] ACK
ββ> [μ€ν¨] DefaultErrorHandler
ββ> μ¬μλ 1 (1μ΄ ν)
ββ> μ¬μλ 2 (2μ΄ ν)
ββ> μ¬μλ 3 (4μ΄ ν)
ββ> [μ΅μ’
μ€ν¨]
ββ> DeadLetterPublishingRecoverer
ββ> order-paid-events.DLQ ν ν½μΌλ‘ μ μ‘
Before:
catch (Exception e) {
// λ‘κ·Έλ§ λ¨κΈ°κ³ λ (λ©μμ§ μ μ€ κ°λ₯)
log.error("Failed to process...", e);
}After:
// try-catch μ κ±° β ErrorHandlerκ° μλ μ²λ¦¬
externalLoggingClient.sendLog(message);| κ΅¬λΆ | κΈ°μ‘΄ | κ°μ ν |
|---|---|---|
| λ©μμ§ μ μ€ | λ°ν μ€ν¨ μ μ μ€ | Fallback DB + μ¬λ°νμΌλ‘ λ°©μ§ |
| Producer μ¬μλ | μμ | μλ μ¬μλ 3ν + λ©±λ±μ± 보μ₯ |
| Consumer μ¬μλ | λ‘κ·Έλ§ κΈ°λ‘ | μλ μ¬μλ 3ν + DLQ μ μ‘ |
| μ₯μ 격리 | μΈλΆ μμ€ν μ₯μ κ° μ£Όλ¬Έμ μν₯ | Kafkaκ° λ²νΌ μν , μμ λΆλ¦¬ |
| λͺ¨λν°λ§ | μ΄λ €μ | Kafka UI + Fallback ν΅κ³ + DLQ |
# μ λ’°μ± μ€μ
spring.kafka.producer.acks=all # λͺ¨λ replica νμΈ
spring.kafka.producer.properties.enable.idempotence=true # μ€λ³΅ λ°©μ§
spring.kafka.producer.retries=3 # μ¬μλ 3ν
spring.kafka.producer.properties.retry.backoff.ms=1000 # μ¬μλ κ°κ²©
spring.kafka.producer.properties.request.timeout.ms=30000 # μμ² νμμμ
spring.kafka.producer.properties.delivery.timeout.ms=120000 # μ λ¬ νμμμ
spring.kafka.producer.properties.max.in.flight.requests.per.connection=5# κΈ°λ³Έ μ€μ
spring.kafka.consumer.group-id=logging-consumer-group
spring.kafka.listener.concurrency=3 # λμ μ²λ¦¬ μ€λ λ 3κ°
# μ¬μλ μ€μ
kafka.consumer.retry.max-attempts=3
kafka.consumer.retry.backoff.initial-interval=1000
kafka.consumer.retry.backoff.multiplier=2.0kafka.topic.common.partitions=3 # νν°μ
3κ°
kafka.topic.common.replications=3 # 볡μ λ³Έ 3κ°
kafka.topic.order-paid=order-paid-eventssrc/main/java/com/example/ecommerceapi/
βββ order/
β βββ application/
β β βββ service/OrderService.java
β β βββ event/OrderEventPublisher.java
β βββ domain/
β β βββ event/OrderPaidEvent.java
β βββ infrastructure/
β βββ kafka/
β βββ producer/OrderKafkaProducer.java
β βββ dto/OrderKafkaMessage.java
βββ common/infrastructure/
βββ kafka/
β βββ config/
β β βββ KafkaTopicConfig.java
β β βββ KafkaConsumerConfig.java
β βββ consumer/LoggingKafkaConsumer.java
β βββ entity/KafkaFallbackMessage.java
β βββ repository/KafkaFallbackMessageRepository.java
β βββ service/KafkaFallbackService.java
β βββ scheduler/KafkaFallbackScheduler.java
βββ external/
βββ client/ExternalLoggingClient.java
βββ listener/ExternalLoggingEventListener.java
1. Kafka UI (http://localhost:8081)
νμΈ νλͺ©:
- Topics:
order-paid-events,order-paid-events.DLQλ©μμ§ νμΈ - Consumer Groups:
logging-consumer-groupμν λ° Lag νμΈ - Brokers: ν΄λ¬μ€ν° μν νμΈ
-- λκΈ° μ€μΈ λ©μμ§ (μ¬λ°ν μμ )
SELECT * FROM kafka_fallback_message WHERE status = 'PENDING';
-- μ€ν¨ν λ©μμ§ (μλ μ²λ¦¬ νμ)
SELECT * FROM kafka_fallback_message WHERE status = 'FAILED';
-- ν΅κ³
SELECT status, COUNT(*)
FROM kafka_fallback_message
GROUP BY status;# Fallback μ μ₯ λ‘κ·Έ
grep "Saved fallback message" logs/application.log
# Fallback μ¬λ°ν μ±κ³΅
grep "Fallback message published successfully" logs/application.log
# DLQ μ μ‘ λ‘κ·Έ
grep "Publishing failed message to DLQ" logs/application.log
# Consumer μ¬μλ λ‘κ·Έ
grep "Retrying message" logs/application.log[2025-12-19 10:00:00] Fallback message stats - pending: 2, published: 45, failed: 1
Kafka λμ μ ν΅ν΄ μ΄λ²€νΈ κΈ°λ° μν€ν μ²λ‘ μ ννκ³ , Fallback DB + DLQ λ©μ»€λμ¦μ ꡬννμ¬ λ©μμ§ μ μ€μ λ°©μ§νμ΅λλ€.
- λΉμ¦λμ€ λ‘μ§κ³Ό μΈλΆ μμ€ν μμ λΆλ¦¬ β μ₯μ 격리
- 3λ¨κ³ μμ μ₯μΉ β Producer μ¬μλ + Fallback DB + Consumer μ¬μλ
- λ©μμ§ μ μ€ μ λ‘ β λͺ¨λ μ€ν¨ μΌμ΄μ€μ λν λμ μλ£
- νμ₯ κ°λ₯ν μν€ν μ² β μλ‘μ΄ Consumer μΆκ°λ§μΌλ‘ κΈ°λ₯ νμ₯
μ΄λ₯Ό ν΅ν΄ μμ μ μ΄κ³ νμ₯ κ°λ₯ν μ΄λ²€νΈ κΈ°λ° μμ€ν μ ꡬμΆνμ΅λλ€.
