Apache Kafka용 Pulsar 어댑터
Apache Kafka용 Pulsar 어댑터
Pulsar는 현재 Apache Kafka Java 클라이언트 API로 작성된 애플리케이션을 위한 쉬운 선택지를 제공해요. 기존 Kafka 코드를 거의 그대로 두고 Pulsar로 옮길 수 있도록 Pulsar Kafka 래퍼(wrapper)를 제공하는데요. 다만 이 래퍼를 사용하기 전에 몇 가지 버전 규칙을 알아두는 게 좋아요.
출처: 문서
본문
note
Pulsar Kafka 래퍼 아티팩트(
pulsar-client-kafka와pulsar-client-kafka-original)는 별도의 apache/pulsar-adapters 리포지토리에 있으며, Maven Central에 게시된 마지막 릴리스 버전은 2.11.0이에요. 더 새로운 Pulsar 브로커(3.x / 4.x)를 실행하더라도 의존성은 2.11.0에 고정해 두세요 — 더 새로운 대응 아티팩트는 게시되지 않으니까요.
Pulsar Kafka 호환 래퍼 사용하기 (Use the Pulsar Kafka compatibility wrapper)
Pulsar Kafka 호환 래퍼를 사용하려면 다음 단계를 완료해요.
Step 1: 기존 애플리케이션에서 일반 Kafka 클라이언트 의존성을 Pulsar Kafka 래퍼로 교체해요. pom.xml의 다음 의존성을 제거하세요.
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.2.1</version>
</dependency>
Step 2: 그다음 Pulsar Kafka 래퍼에 대한 다음 의존성을 포함시키세요.
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-kafka</artifactId>
<version>2.11.0</version>
</dependency>
새 의존성을 사용하면 기존 코드가 변경 없이 동작해요. 설정을 조정해서 프로듀서와 컨슈머가 Kafka 대신 Pulsar 서비스를 가리키게 하고, 특정 Pulsar 토픽을 사용하도록 해야 해요.
기존 Kafka 클라이언트와 함께 Pulsar Kafka 래퍼 사용하기
Kafka에서 Pulsar로 마이그레이션할 때, 애플리케이션이 마이그레이션 기간 동안 원래 Kafka 클라이언트와 Pulsar Kafka 래퍼를 함께 사용할 수도 있어요. 이 경우 unshaded(언섀도) Pulsar Kafka 클라이언트 래퍼를 사용하는 것을 고려해야 해요.
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-kafka-original</artifactId>
<version>2.11.0</version>
</dependency>
이 의존성을 사용할 때는 프로듀서를 org.apache.kafka.clients.producer.KafkaProducer 대신 org.apache.kafka.clients.producer.PulsarKafkaProducer로, 컨슈머는 org.apache.kafka.clients.producer.PulsarKafkaConsumer로 생성해요.
프로듀서 예제 (Producer example)
// Topic needs to be a regular Pulsar topic
String topic = "persistent://public/default/my-topic";
Properties props = new Properties();
// Point to a Pulsar service
props.put("bootstrap.servers", "pulsar://localhost:6650");
props.put("key.serializer", IntegerSerializer.class.getName());
props.put("value.serializer", StringSerializer.class.getName());
Producer<Integer, String> producer = new KafkaProducer(props);
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<Integer, String>(topic, i, "hello-" + i));
log.info("Message {} sent successfully", i);
}
producer.close();
컨슈머 예제 (Consumer example)
String topic = "persistent://public/default/my-topic";
Properties props = new Properties();
// Point to a Pulsar service
props.put("bootstrap.servers", "pulsar://localhost:6650");
props.put("group.id", "my-subscription-name");
props.put("enable.auto.commit", "false");
props.put("key.deserializer", IntegerDeserializer.class.getName());
props.put("value.deserializer", StringDeserializer.class.getName());
Consumer<Integer, String> consumer = new KafkaConsumer(props);
consumer.subscribe(Arrays.asList(topic));
while (true) {
ConsumerRecords<Integer, String> records = consumer.poll(100);
records.forEach(record -> {
log.info("Received record: {}", record);
});
// Commit last offset
consumer.commitSync();
}
전체 예제 (Complete Examples)
완전한 프로듀서와 컨슈머 예제는 여기에서 찾을 수 있어요.
호환성 매트릭스 (Compatibility matrix)
현재 Pulsar Kafka 래퍼는 Kafka API가 제공하는 대부분의 연산을 지원해요.
프로듀서 (Producer)
APIs:
| Producer Method | Supported | Notes |
|---|---|---|
Future<RecordMetadata> send(ProducerRecord<K, V> record) |
Yes | |
Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) |
Yes | |
void flush() |
Yes | |
List<PartitionInfo> partitionsFor(String topic) |
No | |
Map<MetricName, ? extends Metric> metrics() |
No | |
void close() |
Yes | |
void close(long timeout, TimeUnit unit) |
Yes |
Properties:
| Config property | Supported | Notes |
|---|---|---|
acks |
Ignored | 내구성과 쿼럼 쓰기는 네임스페이스 수준에서 설정돼요 |
auto.offset.reset |
Yes | 특정 설정을 주지 않으면 기본값 earliest를 사용해요 |
batch.size |
Ignored | |
bootstrap.servers |
Yes | |
buffer.memory |
Ignored | |
client.id |
Ignored | |
compression.type |
Yes | gzip과 lz4를 허용해요. snappy는 안 돼요 |
connections.max.idle.ms |
Yes | 최대 2,147,483,647,000(Integer.MAX_VALUE * 1000) ms의 유휴 시간만 지원해요 |
interceptor.classes |
Yes | |
key.serializer |
Yes | |
linger.ms |
Yes | 메시지를 배치할 때 그룹 커밋 시간을 제어해요 |
max.block.ms |
Ignored | |
max.in.flight.requests.per.connection |
Ignored | Pulsar에서는 여러 요청이 진행 중이어도 순서가 유지돼요 |
max.request.size |
Ignored | |
metric.reporters |
Ignored | |
metrics.num.samples |
Ignored | |
metrics.sample.window.ms |
Ignored | |
partitioner.class |
Yes | |
receive.buffer.bytes |
Ignored | |
reconnect.backoff.ms |
Ignored | |
request.timeout.ms |
Ignored | |
retries |
Ignored | Pulsar 클라이언트는 send 타임아웃이 만료될 때까지 지수 백오프로 재시도해요 |
send.buffer.bytes |
Ignored | |
timeout.ms |
Yes | |
value.serializer |
Yes |
컨슈머 (Consumer)
다음 표는 컨슈머 API를 나열한 것이에요.
| Consumer Method | Supported | Notes |
|---|---|---|
Set<TopicPartition> assignment() |
No | |
Set<String> subscription() |
Yes | |
void subscribe(Collection<String> topics) |
Yes | |
void subscribe(Collection<String> topics, ConsumerRebalanceListener callback) |
No | |
void assign(Collection<TopicPartition> partitions) |
No | |
void subscribe(Pattern pattern, ConsumerRebalanceListener callback) |
No | |
void unsubscribe() |
Yes | |
ConsumerRecords<K, V> poll(long timeoutMillis) |
Yes | |
void commitSync() |
Yes | |
void commitSync(Map<TopicPartition, OffsetAndMetadata> offsets) |
Yes | |
void commitAsync() |
Yes | |
void commitAsync(OffsetCommitCallback callback) |
Yes | |
void commitAsync(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback) |
Yes | |
void seek(TopicPartition partition, long offset) |
Yes | |
void seekToBeginning(Collection<TopicPartition> partitions) |
Yes | |
void seekToEnd(Collection<TopicPartition> partitions) |
Yes | |
long position(TopicPartition partition) |
Yes | |
OffsetAndMetadata committed(TopicPartition partition) |
Yes | |
Map<MetricName, ? extends Metric> metrics() |
No | |
List<PartitionInfo> partitionsFor(String topic) |
No | |
Map<String, List<PartitionInfo>> listTopics() |
No | |
Set<TopicPartition> paused() |
No | |
void pause(Collection<TopicPartition> partitions) |
No | |
void resume(Collection<TopicPartition> partitions) |
No | |
Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes(Map<TopicPartition, Long> timestampsToSearch) |
No | |
Map<TopicPartition, Long> beginningOffsets(Collection<TopicPartition> partitions) |
No | |
Map<TopicPartition, Long> endOffsets(Collection<TopicPartition> partitions) |
No | |
void close() |
Yes | |
void close(long timeout, TimeUnit unit) |
Yes | |
void wakeup() |
No |
Properties:
| Config property | Supported | Notes |
|---|---|---|
group.id |
Yes | Pulsar 구독 이름에 매핑돼요 |
max.poll.records |
Yes | |
max.poll.interval.ms |
Ignored | 메시지는 브로커에서 "푸시"돼요 |
session.timeout.ms |
Ignored | |
heartbeat.interval.ms |
Ignored | |
bootstrap.servers |
Yes | 단일 Pulsar 서비스 URL을 가리켜야 해요 |
enable.auto.commit |
Yes | |
auto.commit.interval.ms |
Ignored | auto-commit을 사용하면 ack가 즉시 브로커로 보내져요 |
partition.assignment.strategy |
Ignored | |
auto.offset.reset |
Yes | earliest와 latest만 지원해요 |
fetch.min.bytes |
Ignored | |
fetch.max.bytes |
Ignored | |
fetch.max.wait.ms |
Ignored | |
interceptor.classes |
Yes | |
metadata.max.age.ms |
Ignored | |
max.partition.fetch.bytes |
Ignored | |
send.buffer.bytes |
Ignored | |
receive.buffer.bytes |
Ignored | |
client.id |
Ignored |
Pulsar 설정 커스터마이징 (Customize Pulsar configurations)
Kafka 프로퍼티에서 직접 Pulsar 인증 제공자(auth provider)를 구성할 수 있어요.
Pulsar 클라이언트 프로퍼티 (Pulsar client properties)
| Config property | Default | Notes |
|---|---|---|
pulsar.authentication.class |
인증 제공자로 설정해요. 예를 들어 org.apache.pulsar.client.impl.auth.AuthenticationTls |
|
pulsar.authentication.params.map |
인증 플러그인의 파라미터를 나타내는 Map | |
pulsar.authentication.params.string |
인증 플러그인의 파라미터를 나타내는 문자열, 예를 들어 key1:val1,key2:val2 |
|
pulsar.use.tls |
false | TLS 전송 암호화를 활성화해요 |
pulsar.tls.trust.certs.file.path |
TLS 신뢰 인증서 저장소 경로 | |
pulsar.tls.allow.insecure.connection |
false | 브로커의 자체 서명 인증서를 허용해요 |
pulsar.operation.timeout.ms |
30000 | 일반 연산 타임아웃 |
pulsar.stats.interval.seconds |
60 | Pulsar 클라이언트 라이브러리 통계 출력 간격 |
pulsar.num.io.threads |
1 | 사용할 Netty IO 스레드 수 |
pulsar.connections.per.broker |
1 | 각 브로커에 대한 최대 연결 수 |
pulsar.use.tcp.nodelay |
true | TCP no-delay |
pulsar.concurrent.lookup.requests |
50000 | 최대 동시 토픽 룩업 수 |
pulsar.max.number.rejected.request.per.connection |
50 | 연결을 강제로 닫는 오류 임계값 |
pulsar.keepalive.interval.ms |
30000 | 각 클라이언트-브로커 연결의 keep-alive 간격 |
Pulsar 프로듀서 프로퍼티 (Pulsar producer properties)
| Config property | Default | Notes |
|---|---|---|
pulsar.producer.name |
프로듀서 이름을 지정해요 | |
pulsar.producer.initial.sequence.id |
이 프로듀서의 시퀀스 ID 기준값을 지정해요 | |
pulsar.producer.max.pending.messages |
1000 | 브로커의 ack를 기다리는 메시지 큐의 최대 크기 |
pulsar.producer.max.pending.messages.across.partitions |
50000 | 모든 파티션에 걸친 최대 대기(pending) 메시지 수 |
pulsar.producer.batching.enabled |
true | 프로듀서의 메시지 자동 배치(batching) 활성화 여부를 제어해요 |
pulsar.producer.batching.max.messages |
1000 | 배치 내 최대 메시지 수 |
pulsar.block.if.producer.queue.full |
큐가 가득 찼을 때 프로듀서를 블록할지 지정해요 | |
pulsar.crypto.reader.factory.class.name |
프로듀서가 CryptoKeyReader를 만들 수 있게 하는 CryptoReader-Factory(CryptoKeyReaderFactory) 클래스 이름을 지정해요 |
Pulsar 컨슈머 프로퍼티 (Pulsar consumer Properties)
| Config property | Default | Notes |
|---|---|---|
pulsar.consumer.name |
컨슈머 이름을 지정해요 | |
pulsar.consumer.receiver.queue.size |
1000 | 컨슈머 수신 큐 크기 |
pulsar.consumer.acknowledgments.group.time.millis |
100 | 컨슈머가 브로커로 ack를 보낼 최대 그룹 시간 |
pulsar.consumer.total.receiver.queue.size.across.partitions |
50000 | 파티션에 걸친 최대 총 수신 큐 크기 |
pulsar.consumer.subscription.topics.mode |
PersistentOnly | 컨슈머의 구독 토픽 모드 |
pulsar.crypto.reader.factory.class.name |
컨슈머가 CryptoKeyReader를 만들 수 있게 하는 CryptoReader-Factory(CryptoKeyReaderFactory) 클래스 이름을 지정해요 |
더 알아보기 (Learn more)
- Kafka에서 Pulsar로 옮겨오는 마이그레이션 전략을 더 보고 싶다면 Pulsar Kafka 호환 관련 문서를 참고해요.
- 프로듀서와 컨슈머의 전체 예제는 Pulsar GitHub 리포지토리에서 확인할 수 있어요.
- Pulsar 인증(TLS) 설정에 대해 알아보려면 인증 관련 문서를 살펴보세요.
- 토픽과 구독 개념이 궁금하다면 핵심 개념 문서를 참고해요.