Skip to content

Commit 2aefb82

Browse files
committed
Write Kafka Post " [카프카 핵심 가이드] CHAPTER 4. 카프카 컨슈머: 개념, 처리 속도, 리밸런싱 전략 "
1 parent 6bfd91c commit 2aefb82

File tree

7 files changed

+223
-6
lines changed

7 files changed

+223
-6
lines changed

_posts/2025-01-01-2025-DevHistory.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,5 @@ author: devFancy
7272
* [[카프카 핵심 가이드] CHAPTER 2. 카프카 설치하기 (설치부터 파티션과 브로커 규모 산정까지)](https://devfancy.github.io/Kafka-Installation-Sizing-Guide/)
7373

7474
* [[카프카 핵심 가이드] CHAPTER 3. 카프카 프로듀서: 카프카에 메시지 쓰기](https://devfancy.github.io/Kafka-Producer/)
75+
76+
* [[카프카 핵심 가이드] CHAPTER 4. 카프카 컨슈머: 카프카 컨슈머: 개념, 처리 속도, 리밸런싱 전략](https://devfancy.github.io/Kafka-Consumer/)

_posts/2025-07-26-Kafka-Consumer.md

Lines changed: 221 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
layout: post
3-
title: " [카프카 핵심 가이드] CHAPTER 4. 카프카 컨슈머: 카프카에 데이터 읽기 "
4-
categories: Kafka
3+
title: " [카프카 핵심 가이드] CHAPTER 4. 카프카 컨슈머: 개념, 처리 속도, 리밸런싱 전략 "
4+
categories: [Kafka]
55
author: devFancy
66
---
77
* content
@@ -15,17 +15,232 @@ author: devFancy
1515
1616
## Prologue
1717

18-
"카프카 핵심 가이드" 책을 현재 두 번째 읽고 있지만, 생각보다 책의 난이도가 있고, 이해하기 어려운 점이 있어서 책에 있는 내용 중에 중요한 내용 위주로 정리하기 위해 + 간단한 실습 정리를 위해 이 글을 작성하게 되었다.
18+
"카프카 핵심 가이드" 4장 내용을 바탕으로 카프카 컨슈머의 개념, 처리 속도, 리밸런싱 전략에 대해 내용을 정리했다.
1919

20-
이 글은 책의 내용을 기반으로 하되, 앞으로 컨슈머와 관련하여 실무에서 중요하다고 생각되는 개념이 있다면 지속적으로 내용을 보완하고 업데이트할 예정이다.
20+
이론적인 개념 설명과 더불어, 실제 Spring Kafka 환경에서 어떻게 컨슈머를 구현하고 사용하는지 개인 프로젝트 코드를 통해 함께 살펴본다.
2121

22-
자세한 내용은 책의 4장을 참고하자.
2322

23+
## 카프카 컨슈머와 컨슈머 그룹의 개념
2424

25-
## 카프카 컨슈머(Consumer)
25+
`카프카 컨슈머(Consumer)`란 토픽(Topic)에 저장된 데이터를 읽어가는 애플리케이션이다.
2626

27+
* 단순히 데이터를 읽는 것을 넘어, 카프카의 성능과 확장성을 이해하려면 컨슈머 그룹 개념을 반드시 알아야 한다.
2728

29+
* 카프카 컨슈머는 보통 컨슈머 그룹의 일부로 동작한다.
2830

31+
`컨슈머 그룹`이란 같은 목적을 가진 컨슈머의 묶음이다.
32+
33+
* 하나의 토픽을 하나의 컨슈머 그룹이 구독할 때, 그룹 내 컨슈머들은 파티션을 나눠서 데이터를 가져간다.
34+
35+
여기서 중요한 점(핵심 규칙) 그룹 내에서 하나의 파티션은 오직 하나의 컨슈머만 점유할 수 있다는 점이다.
36+
37+
38+
## 컨슈머 처리 속도를 높이는 방법
39+
40+
애플리케이션이 처리하는 속도보다 프로듀서가 메시지를 쌓는 속도가 더 빠르면 데이터 처리가 계속 지연된다.
41+
42+
이때 컨슈머의 처리 속도를 높이는 가장 일반적이고 확실한 방법은 **컨슈머의 병렬 처리** 를 활용하는 것이다.
43+
44+
> 방법: 컨슈머 수를 늘려 파티션을 분담한다.
45+
46+
* 컨슈머 1개: 토픽의 모든 파티션에서 데이터를 혼자 처리한다. (아래 그림 4-1)
47+
48+
![](/assets/img/kafka/Kafka-Consumer-1.png)
49+
50+
* **컨슈머 추가**: 같은 그룹 내에 컨슈머를 추가하면, 카프카는 자동으로 파티션을 재분배(리밸런싱)하여 작업을 나눈다.
51+
특히 데이터베이스 쓰기와 같이 지연시간이 긴 작업을 수행할 때, 이런 방식으로 파티션과 메시지 처리를 분산시키는 것이 가장 일반적인 규모 확장 방식이다.
52+
53+
* 컨슈머가 4개이면 각각 1개의 파티션을 담당하게 되어 처리량이 늘어난다. (아래 그림 4-3)
54+
55+
![](/assets/img/kafka/Kafka-Consumer-2.png)
56+
57+
* 토픽의 파티션 개수를 선정하는 방법은 이전 글인 [[카프카 핵심 가이드] CHAPTER 2. 카프카 설치하기](https://devfancy.github.io/Kafka-Installation-Sizing-Guide/)의 "토픽의 파티션 수 결정 방법" 부분을 참고한다.
58+
59+
> 주의사항: 파티션 수만큼 확장 가능
60+
61+
* 처리량을 높이려고 컨슈머를 무작정 늘리는 것은 의미가 없다.
62+
63+
* 컨슈머 그룹 내 컨슈머의 수는 **토픽의 파티션 수를 초과할 수 없다**. (컨슈머 수 <= 토픽의 파티션 수)
64+
65+
* 만약 파티션이 4개인데, 컨슈머를 5개 투입하면, 1개의 컨슈머는 아무 파티션도 할당받지 못하고 유휴 상태(Idle)가 된다. (아래 그림 4-4)
66+
67+
![](/assets/img/kafka/Kafka-Consumer-3.png)
68+
69+
* **TIP**: 토픽을 처음 설계할 때 예상되는 **최대 처리량**을 고려하여 파티션 수를 설정하는 것이 좋다.
70+
71+
72+
## 안정성을 위한 리밸런싱 전략
73+
74+
`리밸런스(Rebalance)`는 컨슈머 그룹의 확장성과 가용성을 보장하는 핵심 기능이다.
75+
76+
컨슈머 그룹에 새로운 컨슈머가 참여하거나, 기존 컨슈머가 종료 또는 장애로 이탈할 때 **파티션의 소유권을 그룹 내 컨슈머들에게 `재분배`하는 과정**을 말한다.
77+
78+
하지만 리밸런싱 중에는 **데이터 처리가 일시적으로 중단**될 수 있어, 그 동장 방식을 이해하는 것이 매우 중요하다.
79+
80+
카프카는 크게 두 가지 리밸런스 전략을 제공한다.
81+
82+
### 조급한 리밸런스 (Eager Rebalance)
83+
84+
리밸런스가 시작되면 그룹 내 **모든 컨슈머**는 읽기 작업을 즉시 멈추고 자신이 담당하던 **모든 파티션의 `소유권`** 을 포기한다.
85+
86+
그 후 모든 컨슈머가 다시 그룹에 참여해 완전히 새로운 파티션을 할당을 받는다.
87+
88+
이러한 방식은 전체 컨슈머 그룹에 대해 짧은 시간 동안 작업을 멈추게 한다.
89+
90+
그래서 작업을 멈추는 시간의 길이는 컨슈머 그룹의 크기 뿐만 아니라 여러 설정 매개변수에 영향을 미친다.
91+
92+
아래 그림 4-6과 같이 두 단계에 걸쳐 일어나게 된다.
93+
94+
![](/assets/img/kafka/Kafka-Consumer-4.png)
95+
96+
1. 모든 컨슈머가 자신에게 할당된 파티션을 포기(해제)하고,
97+
98+
2. 파티션을 포기한 컨슈머 모두가 다시 그룹에 참여한 뒤 새로운 파티션을 할당받고 읽기 작업을 재개한다.
99+
100+
### 협력적 리밸런스 (Cooperative Rebalance)
101+
102+
점진적 리밸런스(incremental rebalance) 라고도 불리며, 리밸런스가 필요할 때 **변경이 필요한 파티션**만 재할당한다.
103+
104+
재할당 대상이 아닌 파티션을 담당하던 컨슈머들은 **작업 중단 없이 계속 데이터를 처리**할 수 있다. (<-> 조급한 리밸런스 방식 대비 장점)
105+
106+
이 경우 리밸런싱은 2개 이상의 단계에 걸쳐서 수행된다.
107+
108+
1. 컨슈머 그룹 리더가 다른 컨슈머들에게 각자에게 할당된 파티션 중 일부가 재할당될 것이라고 통보하면, 컨슈머들은 해당 파티션에서 데이터를 읽어오는 작업을 멈추고 해당 파티션에 대한 소유권을 포기한다.
109+
110+
2. 컨슈머 그룹 리더가 이 포기한 파티션들을 새로 할당한다.
111+
112+
113+
이러한 특징은 리밸런싱 작업에 **상당한 시간**이 걸릴 위험이 있는, 컨슈머 그룹에 속한 **컨슈머 수가 많은 경우**에 중요하다.
114+
115+
또한, 규모가 크고 민감한 서비스에 유리하다.
116+
117+
아래 그림 4-7은 어떻게 컨슈머와 파티션의 일부만을 재할당함으로써 점진적으로 리밸런싱이 수행되는지 보여준다.
118+
119+
![](/assets/img/kafka/Kafka-Consumer-5.png)
120+
121+
122+
### 리밸런스는 어떻게 감지되는가?
123+
124+
컨슈머 그룹의 리밸런싱은 그룹 코디네이터(Group Coordinator)와 하트비트(Heartbeat)라는 메커니즘을 통해 관리된다.
125+
126+
* `그룹 코디네이터`: 각 컨슈머 그룹을 담당하는 카프카 브로커. 그룹의 상태를 관리하고 리밸런스를 지시하는 총괄 책임자 역할을 한다.
127+
128+
* `하트비트`: 컨슈머가 그룹 코디네이터에게 주기적으로 보내는 "나 살아있어요!" 라는 생존 신호이다.
129+
130+
컨슈머는 백그라운드 스레드를 통해 하트비트를 계속 전송하여 그룹 멤버십을 유지한다.
131+
132+
만약 그룹 코디네이터가 일정 시간(`session.timeout.ms`) 동안 컨슈머의 하트비트를 받지 못하면,
133+
134+
해당 컨슈머에 장애가 발생했다고 판단하고 그룹에서 제외시킨 뒤 리밸런스를 시작한다.
135+
136+
이 때문에 컨슈머 장애 시에도 안정적인 데이터 처리가 가능하다.
137+
138+
139+
## 카프카 컨슈머의 동작과 실제 구현
140+
141+
이제 컨슈머의 핵심 개념을 바탕으로, 실제 코드에서 컨슈머가 어떻게 생성되고 동작하는지 알아보자.
142+
143+
책에서는 순수 `KafkaConsumer` 라이브러리를 사용하지만,
144+
145+
여기서는 Spring Boot 환경에서 `Spring Kafka`를 사용하는 방법을 함께 비교하여 설명한다
146+
147+
### 컨슈머 생성 및 필수 설정
148+
149+
카프카에서 데이터를 읽기 위한 첫 단계는 `KafkaConsumer` 인스턴스를 생성하는 것이다.
150+
151+
이때 반드시 지정해야 하는 3가지 필수 속성과 사실상 필수인 1가지 속성이 있다.
152+
153+
> 컨슈머 필수 설정
154+
155+
* `bootstrap.servers`: 카프카 클러스터에 처음 연결하기 위한 호스트와 포트 정보 목록
156+
157+
* `key.deserializer`: 메시지의 키를 역직렬화(byte[] -> Java Object)하는 클래스
158+
159+
* `value.deserializer`: 메시지의 값을 역직렬화하는 클래스
160+
161+
* `group.id`: 컨슈머가 속한 컨슈머 그룹을 식별하는 ID
162+
163+
### Spring Kafka 에서의 구현
164+
165+
Spring Boot 환경에서는 이러한 설정을 `ConsumerFactory` Bean을 통해 매우 편리하게 관리할 수 있다.
166+
167+
쿠폰 시스템 프로젝트의 `KafkaConsumerConfig` 코드를 통해 확인해 보자.
168+
169+
> KafkaConsumerConfig.java
170+
171+
```java
172+
// dev.be.coupon.kafka.consumer.config.KafkaConsumerConfig
173+
@Configuration
174+
public class KafkaConsumerConfig {
175+
176+
@Bean
177+
public ConsumerFactory<String, CouponIssueMessage> consumerFactory() {
178+
// ... JsonDeserializer 설정 생략 ...
179+
180+
Map<String, Object> config = new HashMap<>();
181+
// 1. bootstrap.servers
182+
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
183+
// 2. key.deserializer
184+
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
185+
// 3. value.deserializer (JSON 메시지를 CouponIssueMessage 객체로 변환)
186+
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, deserializer);
187+
// 4. group.id
188+
config.put(ConsumerConfig.GROUP_ID_CONFIG, "group_1");
189+
// 오프셋 커밋 방식 설정 (자동/수동)
190+
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
191+
192+
return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), deserializer);
193+
}
194+
// ...
195+
}
196+
```
197+
198+
책에서 설명한 필수 설정들이 `Map` 객체에 키-값 형태로 들어가는 것을 볼 수 있다.
199+
200+
`수동 커밋`은 개발자가 원하는 시점에 명시적으로 커밋을 할 수 있게 하여, **메시지 처리의 신뢰성**을 보장한다.
201+
202+
즉, 모든 비즈니스 로직이 성공적으로 끝났을 때만 커밋하여 데이터 유실이나 중복 처리 문제를 방지할 수 있다.
203+
204+
(오프셋 커밋 방식 설정에 대한 부분은 이후 컨슈머 설정과 관련된 포스팅에서 다룰 예정이다.)
205+
206+
### 토픽 구독 및 폴링: Spring Kafka의 추상화
207+
208+
순수 `KafkaConsumer``subscribe()`로 토픽을 구독한 뒤, `while(true)` 루프 안에서 `poll()`을 주기적으로 호출해야 한다.
209+
210+
`poll()` 메서드는 단순히 데이터를 가져오는 것을 넘어, **리밸런스에 참여하고 브로커에 생존 신호(하트비트)를 보내는** 매우 중요한 역할을 한다.
211+
212+
(`poll()` 메서드를 포함한 폴링 루프의 상세한 동작 과정은 "카프카 핵심 가이드" **4.4절**을 참고한다.)
213+
214+
`Spring Kafka`는 이 복잡한 구독 및 폴링 루프 전체를 `@KafkaListener` 애노테이션 하나로 추상화하여 개발자가 비즈니스 로직에만 집중할 수 있게 해준다.
215+
216+
> CouponIssueConsumer.java
217+
218+
```java
219+
// dev.be.coupon.kafka.consumer.application.CouponIssueConsumer
220+
@Component
221+
public class CouponIssueConsumer {
222+
// ...
223+
224+
@KafkaListener(topics = "coupon_issue", groupId = "group_1")
225+
public void listener(final CouponIssueMessage message, final Acknowledgment ack) {
226+
// Spring이 백그라운드에서 poll()로 가져온 '하나의 레코드'가 'message' 파라미터로 전달된다.
227+
log.info("발급 처리 메시지 수신: {}", message);
228+
229+
try {
230+
// 비즈니스 로직 수행
231+
couponIssueService.issue(message);
232+
} finally {
233+
// 로직이 성공적으로 끝나면 수동으로 오프셋을 커밋한다.
234+
ack.acknowledge();
235+
// ...
236+
}
237+
}
238+
}
239+
```
240+
241+
이처럼 `@KafkaListener`를 사용하면 컨슈머의 복잡한 생명주기 관리를 프레임워크에 위임하고,
242+
243+
개발자는 메시지를 받아 처리하는 핵심 로직에만 집중할 수 있다.
29244

30245
## References
31246

377 KB
Loading
398 KB
Loading
447 KB
Loading
334 KB
Loading
426 KB
Loading

0 commit comments

Comments
 (0)