← Blog'a Dön
Kafka1 Mart 20268 dk okuma

Apache Kafka ile Gerçek Zamanlı Veri İşleme

İstanbul Havalimanı'nda her dakika binlerce uçuş verisi, sensör okuma, yolcu hareketi ve operasyon bildirimi akar. Bu veriyi gerçek zamanlı işlemek için Apache Kafka'yı kullandık. Bu yazıda Kafka'yı production'da kullanırken öğrendiklerimi paylaşıyorum.

Kafka Nedir?

Apache Kafka; LinkedIn tarafından geliştirilen, dağıtık, yüksek-verimli bir mesaj akış platformudur. Geleneksel mesaj kuyruklarından (RabbitMQ, ActiveMQ) farklı olarak Kafka mesajları kalıcı olarak saklar ve consumer'lar istedikleri an geriye sararak yeniden okuyabilir. Bu özellik onu hem event streaming hem de event sourcing için ideal kılar.

Temel Kavramlar

Kafkayı anlamak için birkaç temel terimi bilmek gerekir:

  • Topic: Mesajların organize edildiği kategori. Bir gazete gibi düşünün; her topic farklı bir başlık.
  • Partition: Topiclerin yatay parçaları. Paralel okuma/yazma sağlar ve ölçeklenebilirliğin temelidir.
  • Offset: Her mesajın partition içindeki benzersiz sıra numarası. Consumer nereye kadar okuduğunu bu sayede bilir.
  • Consumer Group: Aynı topic'i paralel tüketen consumer'lar grubu. Her partition grubun yalnızca bir üyesine atanır.
  • Broker: Kafka sunucusu. Cluster içinde genellikle 3+ broker çalışır.

Basit Bir Producer/Consumer Örneği (Node.js / TypeScript)

Aşağıda kafkajs kütüphanesi ile basit bir producer ve consumer örneği göreceksiniz. Önce paketi yükleyin: npm install kafkajs

// producer.ts
import { Kafka, Producer } from 'kafkajs';

interface FlightEvent {
  flight_id: string;
  status: 'DEPARTED' | 'ARRIVED' | 'DELAYED';
  timestamp: number;
}

const kafka = new Kafka({
  clientId: 'flight-producer',
  brokers: ['localhost:9092'],
});

const producer: Producer = kafka.producer();

const run = async (): Promise<void> => {
  await producer.connect();

  setInterval(async () => {
    const event: FlightEvent = {
      flight_id: 'TK1234',
      status: 'DEPARTED',
      timestamp: Date.now(),
    };

    await producer.send({
      topic: 'flight-events',
      messages: [{ key: event.flight_id, value: JSON.stringify(event) }],
    });

    console.log('Gönderildi:', event);
  }, 1000);
};

run().catch(console.error);
// consumer.ts
import { Kafka, Consumer, EachMessagePayload } from 'kafkajs';

interface FlightEvent {
  flight_id: string;
  status: string;
  timestamp: number;
}

const kafka = new Kafka({
  clientId: 'flight-consumer',
  brokers: ['localhost:9092'],
});

const consumer: Consumer = kafka.consumer({
  groupId: 'flight-processor-group',
});

const run = async (): Promise<void> => {
  await consumer.connect();
  await consumer.subscribe({ topic: 'flight-events', fromBeginning: true });

  await consumer.run({
    eachMessage: async ({ partition, message }: EachMessagePayload) => {
      const event: FlightEvent = JSON.parse(message.value!.toString());
      console.log('Alındı: ' + JSON.stringify(event) + ' | Partition: ' + partition + ' | Offset: ' + message.offset);
    },
  });
};

run().catch(console.error);

Topic Tasarımı: Nelere Dikkat Ettim?

İyi bir topic tasarımı, sonradan düzeltmesi çok zor performans sorunlarını önler. İşte production'da öğrendiklerim:

  • Partition sayısını doğru belirleyin: Çok az partition = darboğaz. Çok fazla partition = coordinator yükü. Genel kural: hedef throughput / tek partition throughput.
  • Message key'i dikkatlice seçin: Aynı anahtara sahip mesajlar her zaman aynı partition'a gider. Uçuş takibinde flight_id'yi key olarak kullandık; böylece bir uçuşa ait tüm olaylar sıralı geldi.
  • Retention süresini ayarlayın: Varsayılan 7 gün. Analitik sistemler için bunu 30 güne çıkardık; bu sayede upstream sorun olduğunda consumer'lar geriye sarabiliyordu.
  • Topic isimlendirmesi: <domain>.<entity>.<action> formatını öneririm. Örnek: airport.flight.status-changed

Consumer Group Stratejileri

Consumer group'ları doğru tasarlamak, sistemin hem ölçeklenmesi hem de izole çalışması için kritik:

  • Her servis için ayrı group: Uygulamamızda uçuş takip servisi, bildirim servisi ve analitik servisi aynı topic'i farklı group_id'lerle tüketiyordu. Biri yavaşladığında diğerleri etkilenmedi.
  • Rebalance fırtınalarına dikkat: Consumer pod'ları Kubernetes üzerindeydi. Hızlı scale-in/out'da rebalance çok sık tetikleniyordu. session.timeout.ms ve heartbeat.interval.ms değerlerini ayarlayarak bunu azalttık.
  • Manuel offset commit kullanın: eachBatchAutoResolve: false yapıp işlemi tamamladıktan sonra resolveOffset() çağırmak, mesaj kaybını önledi. Özellikle veritabanı write işlemlerinde hayat kurtarıcı.
// consumer-manual-commit.ts
import { Kafka, EachBatchPayload } from 'kafkajs';

const consumer = kafka.consumer({ groupId: 'flight-processor-group' });

const run = async (): Promise<void> => {
  await consumer.connect();
  await consumer.subscribe({ topic: 'flight-events' });

  await consumer.run({
    eachBatchAutoResolve: false,         // Manuel commit modu
    eachBatch: async ({ batch, resolveOffset, heartbeat }: EachBatchPayload) => {
      for (const message of batch.messages) {
        try {
          await processEvent(JSON.parse(message.value!.toString())); // İş mantığı
          resolveOffset(message.offset);                              // Başarılıysa commit
          await heartbeat();
        } catch (err) {
          console.error('İşlem hatası, commit atlandı:', err);        // Hata → retry
          break;
        }
      }
    },
  });
};

run().catch(console.error);

Hata Toleransı ve Dead Letter Queue

Gerçek dünyada her mesaj başarıyla işlenemez. Boznuk veri, geçici servis hatası veya aşırı yük gibi senaryolarda sistemi dayanıklı kılmak için şu stratejiyi uyguladık:

  • Retry topic'ler: Başarısız mesajları flight-events.retry topic'ine publish ettik. 3 deneme sonrası hâlâ başarısız olanlar flight-events.dlq (Dead Letter Queue) topic'ine gitti.
  • Idempotent producer: kafkajs'de producer'ı { idempotent: true } seçeneğiyle başlatarak ağ kesintisinde aynı mesajın iki kez yazılmasını önledik.
  • Replication factor: Her topic'i 3 broker'a replike ettik. Bir broker düşse bile veri kaybolmadı.

İzleme ve Gözlemlenebilirlik

Kafka'yı kör uçmak tehlikelidir. Takip ettiğimiz kritik metrikler:

  • Consumer Lag: En önemli metrik. Consumer'ın producer'dan ne kadar geride olduğunu gösterir. Lag sürekli artıyorsa kapasite artırma zamanı gelmiştir.
  • Under-replicated partitions: Replikasyon geride kalmışsa veri kaybı riski var demektir.
  • Broker request rate: Darboğaz tespiti için.

Tüm bu metrikleri Prometheus + Grafana ile görselleştirdik. kafka-lag-exporter aracı consumer lag'ı Prometheus'a aktarmak için mükemmel çalıştı.

Sonuç

Kafka, yüksek-throughput gerektiren gerçek zamanlı sistemlerde inanılmaz güçlü bir araç. Ama tıpkı güçlü araçlarda olduğu gibi, yanlış kullanıldığında operasyonel baş ağrısına dönüşebilir. Topic tasarımını ciddiye alın, consumer group stratejinizi iş ihtiyaçlarına göre şekillendirin ve mutlaka consumer lag'ı izleyin. Bu üç maddeye sadık kalırsanız Kafka sizi asla yarı yolda bırakmaz.