이 글에서 얻는 것#
- Kafka, RabbitMQ, Redis Pub/Sub의 핵심 차이를 이해합니다
- 각 시스템의 적합한 사용 사례를 알아봅니다
- 프로젝트에 맞는 메시지 시스템 선택 기준을 정립합니다
아키텍처 비교#
전체 구조#
flowchart TB
subgraph Kafka["Apache Kafka"]
direction TB
KP[Producer] --> KB[Broker Cluster]
KB --> KT[Topic/Partition]
KT --> KC[Consumer Group]
KT --> KS[(Log Storage)]
end
subgraph RabbitMQ["RabbitMQ"]
direction TB
RP[Producer] --> RE[Exchange]
RE --> RQ[Queue]
RQ --> RC[Consumer]
end
subgraph Redis["Redis Pub/Sub"]
direction TB
RedP[Publisher] --> RedC[Channel]
RedC --> RedS1[Subscriber 1]
RedC --> RedS2[Subscriber 2]
end
style Kafka fill:#e8f5e9,stroke:#2e7d32
style RabbitMQ fill:#fff3e0,stroke:#ef6c00
style Redis fill:#ffebee,stroke:#c62828
Kafka#
핵심 특징#
flowchart LR
subgraph Topic["Topic: orders"]
P0["Partition 0\n[0,1,2,3,4...]"]
P1["Partition 1\n[0,1,2,3,4...]"]
P2["Partition 2\n[0,1,2,3,4...]"]
end
subgraph CG["Consumer Group"]
C1[Consumer 1]
C2[Consumer 2]
C3[Consumer 3]
end
P0 --> C1
P1 --> C2
P2 --> C3
style Topic fill:#e3f2fd,stroke:#1565c0
특징:
- 분산 로그 스토리지: 메시지를 디스크에 영구 저장
- Consumer Group: 여러 Consumer가 병렬 처리
- Offset 기반: Consumer가 읽은 위치 직접 관리
- Replay 가능: 과거 메시지 재처리 가능
// Kafka Producer
@Service
public class OrderProducer {
@Autowired
private KafkaTemplate<String, OrderEvent> kafkaTemplate;
public void sendOrder(OrderEvent order) {
kafkaTemplate.send("orders", order.getOrderId(), order);
}
}
// Kafka Consumer
@Service
public class OrderConsumer {
@KafkaListener(
topics = "orders",
groupId = "order-processor"
)
public void processOrder(OrderEvent order) {
// 병렬 처리 (Partition 수만큼 Consumer 확장 가능)
orderService.process(order);
}
}
장점 / 단점#
| 장점 | 단점 |
|---|
| 초고처리량 (100만 TPS+) | 운영 복잡도 높음 |
| 메시지 영구 보관 | 단순 큐로는 과함 |
| Replay 가능 | 실시간 라우팅 제한적 |
| 수평 확장 용이 | 메시지 순서 (파티션 단위) |
RabbitMQ#
핵심 특징#
flowchart LR
P[Producer] --> E{Exchange}
E -->|routing_key: order.*| Q1[orders.created]
E -->|routing_key: order.*| Q2[orders.updated]
E -->|routing_key: payment.*| Q3[payments]
Q1 --> C1[Consumer 1]
Q2 --> C2[Consumer 2]
Q3 --> C3[Consumer 3]
style E fill:#fff3e0,stroke:#ef6c00
Exchange 타입:
| Exchange | 라우팅 방식 | 사용 예 |
|---|
| Direct | Exact match | 특정 큐에 전달 |
| Topic | Pattern match | order.*, #.error |
| Fanout | 모든 큐에 복제 | 브로드캐스트 |
| Headers | Header 기반 | 복잡한 라우팅 |
// RabbitMQ Publisher
@Service
public class NotificationPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendNotification(Notification notification) {
rabbitTemplate.convertAndSend(
"notifications.exchange", // Exchange
"user." + notification.getUserId(), // Routing Key
notification
);
}
}
// RabbitMQ Consumer
@Service
public class NotificationConsumer {
@RabbitListener(queues = "user.notifications")
public void handleNotification(Notification notification) {
// 메시지 처리
pushService.send(notification);
}
}
장점 / 단점#
| 장점 | 단점 |
|---|
| 유연한 라우팅 | Kafka 대비 처리량 낮음 |
| 메시지 우선순위 | 메시지 영구 보관 X (기본) |
| ACK 기반 신뢰성 | 수평 확장 제한 |
| 플러그인 생태계 | 대용량에 부적합 |
Redis Pub/Sub#
핵심 특징#
flowchart TB
P1[Publisher 1] --> Ch[Channel: news]
P2[Publisher 2] --> Ch
Ch --> S1[Subscriber 1]
Ch --> S2[Subscriber 2]
Ch --> S3[Subscriber 3]
Note1[⚠️ 구독자 없으면\n메시지 유실]
style Ch fill:#ffebee,stroke:#c62828
style Note1 fill:#fff9c4,stroke:#f9a825
특징:
- Fire-and-Forget: 메시지 저장 없음
- 실시간 전달: 구독자에게 즉시 푸시
- 메시지 유실 가능: 구독자 없으면 버려짐
- 초저지연: 메모리 기반
// Redis Pub/Sub Publisher
@Service
public class CacheInvalidator {
@Autowired
private StringRedisTemplate redisTemplate;
public void invalidateCache(String key) {
// 모든 서버에 캐시 무효화 알림
redisTemplate.convertAndSend("cache:invalidate", key);
}
}
// Redis Pub/Sub Subscriber
@Service
public class CacheListener {
@Autowired
private LocalCache localCache;
@PostConstruct
public void subscribe() {
redisTemplate.getConnectionFactory()
.getConnection()
.subscribe((message, pattern) -> {
String key = new String(message.getBody());
localCache.evict(key);
}, "cache:invalidate".getBytes());
}
}
Redis Streams (대안)#
Redis 5.0+에서 Kafka 유사 기능 제공:
// Redis Streams (영구 저장 + Consumer Group)
@Service
public class OrderStreamProducer {
@Autowired
private StringRedisTemplate redisTemplate;
public void addOrder(Order order) {
Map<String, String> message = Map.of(
"orderId", order.getId(),
"status", order.getStatus()
);
redisTemplate.opsForStream().add("orders", message);
}
}
장점 / 단점#
| 장점 | 단점 |
|---|
| 초저지연 | 메시지 유실 가능 |
| 설정 간단 | 영구 저장 없음 |
| 기존 Redis 활용 | Consumer Group 없음 |
| 실시간 이벤트 | 대용량 부적합 |
선택 가이드#
비교표#
| 기준 | Kafka | RabbitMQ | Redis Pub/Sub |
|---|
| 처리량 | 100만+ TPS | 10만 TPS | 100만+ TPS |
| 지연시간 | 5~50ms | 1~10ms | <1ms |
| 메시지 보관 | ✅ 영구 | ⚠️ 선택적 | ❌ 없음 |
| Replay | ✅ 가능 | ❌ 불가 | ❌ 불가 |
| 라우팅 | ⚠️ 제한적 | ✅ 유연 | ⚠️ 패턴만 |
| 운영 복잡도 | 높음 | 중간 | 낮음 |
| 확장성 | ✅ 수평 | ⚠️ 제한 | ✅ 수평 |
사용 사례별 선택#
flowchart TD
Start["메시지 시스템 선택"] --> Q1{"대용량 로그/이벤트?"}
Q1 -->|Yes| Kafka["Apache Kafka"]
Q1 -->|No| Q2{"유연한 라우팅 필요?"}
Q2 -->|Yes| RabbitMQ["RabbitMQ (Exchange)"]
Q2 -->|No| Q3{"메시지 유실 허용?"}
Q3 -->|Yes| Redis["Redis Pub/Sub"]
Q3 -->|No| Q4{"간단한 큐만 필요?"}
Q4 -->|Yes| RabbitMQ2[RabbitMQ / Redis Streams]
Q4 -->|No| Kafka2[Apache Kafka]
style Kafka fill:#e8f5e9,stroke:#2e7d32
style Kafka2 fill:#e8f5e9,stroke:#2e7d32
style RabbitMQ fill:#fff3e0,stroke:#ef6c00
style RabbitMQ2 fill:#fff3e0,stroke:#ef6c00
style Redis fill:#ffebee,stroke:#c62828
구체적 사용 사례#
| 시스템 | 적합한 사용 사례 |
|---|
| Kafka | 로그 집계, 이벤트 소싱, 스트림 처리, 데이터 파이프라인 |
| RabbitMQ | 작업 큐, 마이크로서비스 통신, 복잡한 라우팅, RPC |
| Redis Pub/Sub | 캐시 무효화, 실시간 알림, 임시 이벤트, 세션 동기화 |
실무 조합 패턴#
하이브리드 아키텍처#
flowchart TB
subgraph Services
API[API Server]
Worker[Worker]
end
subgraph MessageSystems
Redis[(Redis)]
Kafka[(Kafka)]
RabbitMQ[(RabbitMQ)]
end
API -->|실시간 알림| Redis
API -->|주문 이벤트| Kafka
Worker -->|작업 큐| RabbitMQ
Kafka -->|로그 분석| Analytics[Analytics]
RabbitMQ -->|Email 발송| EmailWorker[Email Worker]
style Redis fill:#ffebee,stroke:#c62828
style Kafka fill:#e8f5e9,stroke:#2e7d32
style RabbitMQ fill:#fff3e0,stroke:#ef6c00
실무 조합 예시:
- Redis: 캐시 무효화, 실시간 채팅
- Kafka: 주문/결제 이벤트, 로그 수집
- RabbitMQ: 이메일 발송, 백그라운드 작업
핵심 선택 기준#
| 이걸 원하면 | 이걸 선택 |
|---|
| 대용량 + 재처리 | Kafka |
| 유연한 라우팅 | RabbitMQ |
| 초저지연 + 단순 | Redis Pub/Sub |
| 작업 큐 + 신뢰성 | RabbitMQ |
| 이벤트 소싱 | Kafka |
| 캐시 동기화 | Redis |
💬 댓글