토스에서 진행한 SLASH 22를 보고 Kafka에 대한 호기심이 강하게 들었다. (이름은 많이 들어봤는데 그동안 알아볼 생각을 안했다ㅠ)
현재 팀에서 내가 맡아서 관리해야 될 서버들이 점점 늘어나고 있는데
여기서 이 서버들이 실제 오픈을 하고 운영 레벨로 넘어가게 되면 모니터링을 하기가 굉장히 어려워질 것임을 예상했다.
현재 팀에는 딱히 모니터링 시스템이라고 할 만한 게 없고 (AWS CloudWatch를 이용하는 정도?)
내가 원하는 것은 어플리케이션 단의 모니터링 시스템이 었기 때문에
이 참에 Kafka를 활용해서 한 번 모니터링 시스템을 구축해보면 어떨까 하는 생각이 들었다.
일단 그 생각의 실현을 위한 첫 스텝으로 Kafka가 뭔지 알아보려 한다.
아래는 최범균 님의 'kafka 조금 아는척하기 시리즈' 유튜브 영상을 보며 정리한 내용입니다.
Kafka
분산 이벤트 스트리밍 플랫폼
4개의 구성요소
- 프로듀서
- 메시지(이벤트)를 카프카에 넣는다
- 컨슈머
- 메시지(이벤트)를 카프카에서 읽는다.
- 카프카 클러스터
- 메시지(이벤트)를 저장한다.
- 하나의 카프카 클러스터는 여러 개의 브로커로 구성되어 있으며 각각 서버라고 보면 된다.
- 브로커는 메시지를 나눠서 저장하고, 이중화 처리도 하고, 장애가 나면 대체도 하는 등의 역할을 수행한다.
- 메시지(이벤트)를 저장한다.
- 주키퍼 클러스터 (주키퍼 앙상블)
- 카프카 클러스터를 관리하는 용으로 클러스터 정보가 저장되어 관리가 됩니다.
- 브로커가 한 개 밖에 없을 때에도 클러스터로 동작하는데 클러스터 내의 브로커에 대한 분산 처리를 주키퍼가 담당한다.
- 주키퍼
- 분산 시스템에서 시스템 간이 정보 공유, 상태 체크, 서버들 간의 동기화를 위한 락 등을 처리해주는 '분산 코디네이션 시스템'. 카프카에서는 서버의 상태를 감지하기 위해 사용되며 새로운 토픽이 생성되었을 때 토픽의 생성과 소비에 대한 상태를 지정합니다.
- 주키퍼
토픽과 파티션
- 토픽
- 카프카에서 메세지를 저장하는 단위가 토픽
- 여러 매세지가 있을 때 이 메세지가 어떤 종류의 메세지인지 구분할 필요가 있는데 이때 사용하는 것이 토픽이다
- 예를 들어 주문용 토픽, 뉴스용 토픽 같이 각각의 메세지를 알맞게 구분하기 위해 토픽을 사용한다.
- 파일 시스템의 폴더와 유사하다고 보면 된다.
- 한 개의 토픽은 한 개 이상의 파티션으로 구성된다.
- 파티션은 메세지를 저장하는 물리적인 파일
- 프로듀서와 컨슈머는 토픽을 기준으로 메세지를 주고받는다!
- 파티션은 메세지를 저장하는 물리적인 파일
- 파티션 (= 파일이라고 보면 된다)
- 파티션은 추가만 가능한 파일이다.
- 각 메세지 저장 위치를 오프셋(offset)이라고 한다.
- 프로듀서가 카프카에 메세지를 저장하면 저장된 메세지는 offset1, offset2 이렇게 오프셋 값을 가지게 된다.
- 여러 consumer가 한 topic(일종의 queue 개념)으로부터 여러 번에 걸쳐 메시지를 가져올 수 있습니다. 이런 방식이 가능한 이유는 클라이언트가 해당 queue에서 어느 부분까지 데이터를 받아갔는지 위치를 알려주는 'offset'을 관리하기 때문입니다.
- 프로듀서가 넣은 메세지는 파티션의 맨 뒤에 추가한다.
- 컨슈머는 오프셋 기준으로 메세지를 순서대로 읽는다.
- 메세지는 삭제되지 않는다. (설정에 따라 일정 시간이 지난 뒤 삭제)
- 각 메세지 저장 위치를 오프셋(offset)이라고 한다.
- 한 파티션 내에서만 메세지 순서가 보장된다.
- 파티션은 추가만 가능한 파일이다.
여러 파티션과 프로듀서
- 프로듀서는 라운드로빈 또는 키로 파티션을 선택한다. 혹은 키를 이용해서 파티션을 선택한다.
- 프로듀서가 카프카에 메세지를 전송할 때 토픽의 이름 뿐만 아니라 키를 지정할 수 있는데 키가 있는 경우에는 그 키의 해시값을 이용해서 저장할 토픽을 선택할 수 있게 된다. 그래서 같은 키를 갖고 있는 메세지는 같은 파티션에 저장이 된다 (같은 키에 대해서는 메세지 순서가 정해진다)
여러 파티션과 컨슈머
- 컨슈머는 컨슈머 그룹이라는 거에 속하게 되어 있는데 컨슈머가 카프카 브로커에 연결할 때 나는 어떤 그룹에 속해있다고 지정하게 되어있음
- 한 개의 파티션은 그룹의 한 개 컨슈머에만 연결이 가능하다. (= 컨슈머 그룹 기준으로 파티션의 메세지가 순서대로 처리되는 것을 보장할 수 있게 된다.)
카프카 성능이 왜 좋을까요?
- 페이지캐쉬 - 카프카는 파티션 파일에 대해서 OS에서 제공하는 페이지 캐쉬를 이용하기 때문에 파일 IO가 메모리에서 처리되기 때문에 IO속도가 빨라진다.
- 페이지 캐시란? ( https://medium.com/@tas.com/ )
- 처리한 데이터를 메인 메모리 영역(RAM)에 저장해서 가지고 있다가, 다시 이 데이터에 대한 접근이 발생하면 disk에서 IO 처리를 하지 않고 메인 메모리 영역의 데이터를 반환하여 처리할 수 있도록 하는 컴포넌트다. 즉 OS가 파일을 read하여 메모리에 올려두고 있다가, 빠르게 접근하여 사용하겠다는 것.
- 다시 kafka와 내용을 같이 보면, producer가 서버인 broker에게 넣는 데이터는 consumer가 사용하기 전 일정 시간동안 page cache 올려두어, consumer가 데이터를 읽어 갈 때 그 읽어가는 속도를 빠르게 한다는 것으로 이해하면 되겠다.
- 페이지 캐시란? ( https://medium.com/@tas.com/ )
- 제로카피 - 디스크에서 데이터를 읽어다가 네트워크로 보내는 속도가 빠르다.
- 파일에서 소켓으로 데이터를 전송하는 전통적인 과정 -> 비효율 (4개의 사본과 2개의 시스템 호출 )
- 운영 체제는 디스크에서 커널 공간의 페이지 캐시로 데이터를 읽습니다.
- 응용 프로그램은 커널 공간에서 사용자 공간 버퍼로 데이터를 읽습니다.
- 응용 프로그램은 데이터를 다시 커널 공간에 소켓 버퍼에 쓴다.
- 운영체제는 소켓 버퍼에서 네트워크를 통해 전송되는 NIC 버퍼로 데이터를 복사한다.
- Disk > Kernel(PageCache) > User-Space(Buffer) > Kernel(Socket Buffer) > Kernel(NIC Buffer)
- kafka는 OS가 페이지캐시에서 네트워크로 데이터를 직접 보낼 수 있으므로 위와 같은 재복사가 방지됩니다. 따라서 이 최적화된 경로에서는 NIC 버퍼에 대한 최종 복사본만 필요합니다. 데이터가 메모리에 저장되고 읽을 때마다 사용자 공간으로 복사되는 대신 페이지 캐시에 정확히 한 번 복사되고 소비할 때마다 재사용됩니다.
- pagecache와 sendfile의 조합 덕분에 Kafka 클러스터에서 디스크가 완전히 캐시에서 데이터를 제공하기 때문에 디스크에서 읽기 활동을 볼 수 없음을 의미합니다.
- 파일에서 소켓으로 데이터를 전송하는 전통적인 과정 -> 비효율 (4개의 사본과 2개의 시스템 호출 )
- 빠르다 - 브로커가 컨슈머에 대해서 할 수 있는 역할이 없어서 상대적으로 빠르다. (제재하지 않고 프로듀서와 컨슈머가 직접 함)
- 일괄작업 - 묶어서 보내고 묶어서 받는다. (Batch) 프로듀서와 컨슈머는 일정 크기만큼 메시지를 모아서 전송 그리고 조회가 가능하다. 따라서 낱개로 건건히 보내는 것보다 아무래도 더 빨라질 수밖에 없음
- 처리량 조절 쉬움 - 그냥 브로커 추가하고 파티션 추가하거나 컨슈머가 느리다고 생각되면 컨슈머 추가하면 됨.
- 장애 복구 간단 - 장애가 났을 때 대처하기 위해 리플리카를 사용한다. (리더/팔로워 구조)
- 리플리카는 파티션의 복제본으로 복제수만큼 파티션의 복제본이 각 브로커에 생김
- 하나가 리더 나머지가 팔로워가 되어서 팔로워는 리더로부터 데이터를 읽어와서 저장하므로 리더가 속한 브로커가 장애가 발생하면 이때 다른 팔로워 중에서 하나가 리더가 되고, 프로듀서와 컨슈머는 신규 리더를 통해 메세지를 처리할 수 있게 됩니다.
- 리플리카는 파티션의 복제본으로 복제수만큼 파티션의 복제본이 각 브로커에 생김
프로듀서
Properties prop = new Properties();
prop.put("bootstrap.servers", "kafka01:9092, kafka01:9092, kafka01:9092");
prop.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
prop.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<Integer, String> producer = new KafkaProducer<>(prop);
producers.send(new ProducerRecord<>("topicname", "key", "value")); // 방법1
producers.send(new ProducerRecord<>("topicname", "value")); // 방법2
producer.close();
KafkaProducer 클래스는 send 메소드를 제공하며 이 send 메소드에 ProducerRecord를 전달하면 됩니다.
ProducerRecord가 브로커에 전달할 카프카 메세지가 됩니다.
프로듀서 내부 동작 흐름
(강의에 나온 내용 대사 그대로를 옮겨 적었습니다.)
send() 메소드를 통해 레코드를 전달하면 Serializer를 통해 byte 배열로 변환하고
Partitioner를 이용해 메세지를 어느 토픽의 파티션으로 보낼지 결정합니다.
그리고 변환된 바이트 메시지를 버퍼에 저장하는데 버퍼에 바로 저장하지 않고 배치로 묶어서 저장하게 됩니다.
그리고 sender를 통해 배치를 차례대로 가져와 카프카 브로커로 전송합니다.
여기서 Sender는 별도 쓰레드로 동작하며 배치가 찼는지 여부에 상관없이 보내며
Sender 쓰레드와는 별개의 쓰레드에서 send 메서드를 통해 메세지를 배치로 모으게 됩니다.
(즉, 메세지를 모으는 쓰레드와 배치를 전송하는 쓰레드는 다릅니다.)
배치하고 sender와 관련된 설정이 처리량에 영향을 주게 됩니다. (batch.size / linger.ms)
batch.size는 배치의 최대 크기를 지정하고 지정한 크기만큼 메세지가 차면 메세지가 바로 전송을 하게 됩니다.
그래서 배치 사이즈가 너무 작으면 한 번에 보낼 수 있는 메세지 크기가 작고, 전송 횟수가 많아 처리량이 떨어지게 되겠죠?!
linger.ms는 센더가 메시지를 보내는 대기 시간입니다.
기본값은 0이며 대기시간을 주게 되면 기다렸다 배치를 전송하기 때문에 한 번에 많은 메세지를 보내게 됩니다.
send() 메소드를 통해 전송한 것은 결과를 확인하지 않습니다. (실패 여부 모름) 따라서 실패에 대한 별도 처리가 필요없는 메시지 전송에 사용합니다. 그런데 실패 여부를 알아야 될 때가 있는데 이 때 두 가지 방법이 사용 가능하다.
전송 후 실패 여부를 알고 싶다면
1. Future 사용 (처리량이 낮아도 정확해야 하는 경우)
get()을 사용하면 블로킹이 되기 때문에 루프를 돌면서 전송하는 경우에는 전송-블로킹-전송-블로킹이라
배치에 메시지가 1개씩만 들어가기 때문에 처리량도 떨어짐
Future<RecordMetadata> f = producer.send(new ProducerRecord<>("topic", "value"));
try {
RecordMetadata meta = f.get(); // 블로킹
} catch (ExecutionException ex) {}
2. Callback 사용
콜백 객체는 전송 후 전송 결과를 onCompletion 메서드로 받게 되는데 Exception을 받게 되면 전송이 실패된 것.
처리량이 떨어지지 않는다.
producer.sned(new ProducerRecord<>("simple", "value"),
new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception ex) {
}
});
전송보장과 ack
Producer는 전송을 보장하기 위해 ack값을 제공한다. ack가 0이면 처리량은 많아 지겠지만 메세지 전송 여부는 알 수 없습니다. ack가 1이면 파티션의 리더에 값이 저장되면 성공 응답을 알려줍니다. 따라서 리더에 장애가 발생하면 메세지가 유실될 가능성이 있습니다. (팔로워에 저장이 아직 안됐는데 성공 응답을 내려주었고, 이 상태로 리더에 장애가 발생하는 경우가 이에 해당된다) ack가 all 이면 모든 팔로워에 다 저장이 되었을 때 응답을 내려줍니다. 따라서 메시지 유실이 없어야 되는 경우에는 all로 주는 것이 맞다고 볼 수 있다.
+ 전송하다 에러가 나는 경우 재시도가 가능한 경우에는 kafka에서 재시도를 수행한다.
+ enable.idempotence 속성을 사용하면 메시지 중복 전송 가능성을 낮출 수 있다.
재시도와 순서
재시도의 주의 사항은 중복 전송과 순서가 바뀐다는 것이다.
- max.in.flight.requestes.per.connection 옵션
- 블록킹 없이 한 커넥션에서 전송할 수 있는 최대 전송중인 요청 개수
이 옵션값이 1보다 크게 되면 재시도가 언제 이뤄지냐에 따라 메시지 순서가 바뀔 수 있다.
따라서, 전송 순서가 중요하면 이 값을 1로 지정해야 한다.
컨슈머
토픽 파트션에서 특정 레코드 조회
Properties prop = new Properties();
prop.put("bootstrap.servers", "localhost:9092");
prop.put("group.id", "group1");
prop.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
prop.put("value.deserializer", "org.apache.kafka.common.serialization.StringDesrializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String> (prop);
consumer.subscribe(Collections.singleton("simple")); // 토픽 구독
while (조건) {
ConsumerRecords<String, String> recoreds = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record: records) {
System.out.println(record.value() + ":" + record.topic() + ":" +
record.partition() + ":" + record.offset());
}
}
consumer.close();
토픽 파티션은 그룹 단위로 할당된다.
각 컨슈머가 파티션에 연결되는데 파티션보다 컨슈머가 더 많이 생기면 이후로 생기는 컨슈머는 놀게 된다. 특정 파티션에 연결될 수 없기 때문에.. 그래서 컨슈머 개수가 파티션 개수보다 커지면 안되고, 처리량이 떨어져서 컨슈머가 커져야 되면 파티션 개수도 함께 늘려야 합니다.
커밋과 오프셋
컨슈머의 poll 메소드는 이전에 커밋한 오프셋이 있으면 그 오프셋 이후의 레코드를 읽어 옵니다.
만약에 poll 메소드로 레코드를 읽어오려는데 커밋된 레코드가 없는 경우에는 auto.offset.reset 옵션 설정값을 사용합니다.
auto.offset.reset
- earliest : 맨 처음 오프셋 사용
- latest : 가장 마지막 오프셋 사용
- none : 익셉션 발생
컨슈머 설정
조회에 영향을 주는 주요 설정
- fetch.min.bytes
- 조회시 브로커가 전송할 최소 데이터 크기
- 기본값은 1이며 이게 크면 대기 시간이 늘지만 처리량은 올라감
- 조회시 브로커가 전송할 최소 데이터 크기
- fetch.max.wait.ms
- 데이터가 최소 크기가 될 때까지 기다릴 시간
- 기본값은 500(0.5초)이며 브로커가 리턴할 때까지 대기하는 시간으로 poll() 메서드의 대기 시간과 다름
- 데이터가 최소 크기가 될 때까지 기다릴 시간
- max.partition.fetch.bytes
- 파티션 당 서버(브로커)가 리턴할 수 있는 최대 크기
- 기본값은 1MB
- 파티션 당 서버(브로커)가 리턴할 수 있는 최대 크기
자동 커밋 / 수동 커밋
enable.auto.commit 설정을 통해 자동 커밋할지 수동 커밋을 할지 결정한다.
true이면 일정 주기로 컨슈머가 오프셋을 커밋하고, false면 수동으로 진행되게 된다.
자동 커밋은 poll(), close() 메서드 호출시 자동으로 실행된다.
수동커밋 방법
1. 동기
ConsumerRecord<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
처리
}
try {
consumer.commitSync();
} catch (Exception ex) {
// 커밋 실패시 에러 발생
// 실패하면 알맞은 처리 하면 됨
}
2. 비동기
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
..처리
}
consumer.commitAsync(); // commitAsync(OffsetCommitCallback callback)
비동기이기 때문에 코드 자체에서 실패 여부를 바로 알 수가 없고, 알고 싶으면 콜백을 받아서 처리해야 한다.
컨슈머가 동일한 메시지를 읽어올 수 있다. 커밋이 실패했다거나 컨슈머가 추가 되는 케이스에서 리밸런싱이 일어나고, 이 과정에서 동일한 메시지를 읽어올 수 있다. 이때문에 멱등성(idempotence - 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질)을 고려해야 한다. 그리고 데이터 특성에 따라 타임스탬프, 일련 번호 등을 활용해야 한다.
세션 타임아웃, 하트비트, 최대 poll 간격
컨슈머는 하트비트를 계속 브로커에 전송해서 연결을 유지한다. 브로커는 일정 시간 동안 컨슈머를 통해 하트비트를 전달받지 못하면 그룹에서 빼버린다. 그리고 리밸런싱을 진행한다.
관련 설정은
- session.timeout.ms : 세션 타임 아웃 시간 (기본값 10초)
- hearbeat.interval.ms : 하트비트 전송 주기 (기본값 3초)
- session.timeout.ms의 1/3 이하 추천
- max.poll.interval.ms : 정해진 시간 동안 poll()하지 않으면 컨슈머를 그룹에서 빼고 리밸런스를 진행한다.
세션 종료
보통은 무한 루프를 돌면서 poll() 메서드로 레코드를 불러온느 코드를 작성하게 되는데 이 loop를 어떻게 벗어날 수 있을까! 바로 wakeup() 메서드를 호출한다. finally에서 consumer.close()를 해주자~
KafkaConsumer는 쓰레드에 안전하지 않기 때문에 여러 쓰레드에서 동시에 사용하지 않아야 한다.
추가 내용
주키퍼
• 주키퍼 사용용도
주키퍼는 클러스터에서 구성 서버들끼리 공유되는 데이터를 유지하거나 어떤 연산을 조율하기 위해 주로 사용
- 설정 관리 : 클러스터의 설정 정보를 최신으로 유지하기 위한 조율 시스템으로 사용됩니다.
- 클러스터 관리 : 클러스터의 서버가 추가되거나 제외될 때 그 정보를 클러스터 안 서버들이 공유하는 데 사용됩니다.
- 리더 채택: 다중 어플리케이션 중에서 어떤 노드를 리더로 선출할 지를 정하는 로직을 만드는 데 사용됩니다. 주로 복제된 여러 노드 중 연산이 이루어지는 하나의 노드를 택하는 데 사용됩니다.
- 락, 동기화 서비스 : 클러스터에 쓰기 연산이 빈번할 경우 경쟁상태에 들어갈 가능성이 커집니다. 이는 데이터 불일치를 발생시킵니다. 이 때, 클러스터 전체를 대상을 동기화해( 락을 검 ) 경쟁상태에 들어갈 경우를 사전에 방지합니다.
Kafka vs RabbitMQ
둘 다 pub/sub 기반의 메시지 큐 서비스인데 Kafka는 이벤트 브로커이고, RabbitMQ는 메세지 브로커이다.
이벤트 브로커는 메세지 브로커의 기능을 포함하는 더 큰 범위의 개념이기에 이벤트 브로커가 메세지 브로커 역할을 수행할 수도 있다.
메세지 브로커는 중간 다리 역할을 수행하는 broker로 publisher가 생산한 메세지를 큐에 저장하고, consumer가 데이터를 가져가면 즉시 혹은 짧은 시간 내에 큐에서 데이터를 삭제한다. 보통 서로 다른 시스템 사이에서 데이터를 비동기 형태로 처리하고 싶을 때 사용하며 AWS에서는 비슷하게 SQS가 있다.
이벤트 브로커는 publisher가 생산한 이벤트를 저장하고, consumer가 해당 이벤트를 사용하더라도 이벤트가 저장된다는 특징으로 이후에 다시 재사용 할 수 있는 장점을 가지고 있다.
(https://www.cloudamqp.com/blog/when-to-use-rabbitmq-or-apache-kafka.html)
- 일반적으로 단순/전통적인 pub-sub 메시지 브로커를 원하는 경우 확실한 선택은 RabbitMQ입니다. 요구 사항이 channels/queues을 통한 시스템 통신을 처리할 만큼 간단하고, 메세지를 보존하거나 스트리밍을 요구하는게 아닌 경우에 말이다.
- 두 개의 주요 사용 사례로 나눌 수 있다.
- LONG-RUNNING TASKS
- RabbitMQ를 사용하는데 오래 걸리는 작업이 백그라운드에서 안정적으로 실행되어야 할 때
- MIDDLEMAN IN A MICROSERVICE ARCHITECTURES
- 애플리케이션 내부 및 애플리케이션 간의 통신 및 통합을 위한 경우
- 마이크로서비스 간의 중개자로서 시스템에게 단순히 작업을 실행하라는 것을 알릴 때 예를 들면 주문 처리나 주문 상태 업데이트 같은 경우다.
- LONG-RUNNING TASKS
Apache Kafka 사용 사례
- 일반적으로 스트리밍 데이터를 저장, 읽기(다시 읽기), 분석하기 위한 프레임워크를 원한다면 Apache Kafka를 사용합니다. 감사를 받거나 메시지를 영구적으로 저장해야 하는 시스템에 이상적입니다.
- 두 개의 주요 사용 사례로 나눌 수 있다.
- DATA ANALYSIS (추적, 수집, 로깅, 보안 등)
- 데이터를 분석해서 Insights를 얻고, 수많은 데이터에 대한 감사 또는 분석이 필요한 경우.
- 주요 분석, 검색 및 저장 시스템
- 실시간 처리
- 처리량이 많은 분산 시스템 역할을 합니다. 소스 서비스는 데이터 스트림을 실시간으로 가져오는 대상 서비스로 푸시
- Kafka는 적은 수의 소비자와 실시간으로 많은 생산자를 처리하는 시스템에서 사용할 수 있습니다. 즉, 주식 데이터를 모니터링하는 금융 IT 시스템.
- DATA ANALYSIS (추적, 수집, 로깅, 보안 등)
RabbitMQ | Apache Kafka | |
무엇인가? | 견고하고 성숙한 범용 메시지 브로커 | 높은 유입 데이터 스트림 및 재생에 최적화된 Message Bus |
주요 용도 | 애플리케이션 내부 및 애플리케이션 간의 통신 및 통합으로 기 실행 작업 또는 안정적인 백그라운드 작업을 실행해야 하는 경우 | 스트리밍 데이터의 저장, 읽기(다시 읽기) 및 분석 |
메세지 지속성 | 수신 확인 시 삭제 | 보존 기간 옵션에 따라 메세지 유지 (수신 되어도 삭제하지 않음) |
라우팅 | 소비자 노드에 정보를 반환할 수 있는 유연한 라우팅 지원 | 유연한 라우팅을 지원하지 않으며 별도의 주제를 통해 수행해야 합니다. |
메시지 우선순위 | 지원 | 지원하지 않음 |
출처
https://www.youtube.com/watch?v=0Ssx7jJJADI (최범균 님의 kafka 조금 아는척하기 시리즈)
https://m.blog.naver.com/kgw1988/221212827363 (카프카에서의 데이터 저장 방식)
https://kafka.apache.org/documentation/#gettingStarted 공식문서
https://programacion.tistory.com/156 [KA's Regalo:티스토리]
https://www.cloudamqp.com/blog/when-to-use-rabbitmq-or-apache-kafka.html
'TIL (Today I Learned)' 카테고리의 다른 글
String배열과 ArrayList, 무엇을 써야되나? (0) | 2022.07.13 |
---|---|
소스 분석 01 (0) | 2022.06.23 |
Redis (0) | 2021.12.08 |
Docker mac에 설치해서 컨테이너 실행해보기 / Docker 문법 (0) | 2021.08.04 |
Docker 도커가 대체 뭐야 ㅠㅠ (0) | 2021.08.03 |