Olay Güdümlü Mimari Araçları: Kafka, SQS, EventBridge ve Cloud Alternatifleri Üzerine Kapsamlı Rehber

Olay güdümlü sistem araçları, mesaj teslimat kalıpları, DLQ stratejileri ve cloud provider eşlenikleri üzerine derinlemesine inceleme. AWS, Azure, GCP ve edge deployment'lar hakkında production deneyimleri.

Olay güdümlü sistemlerle çalışmak bana doğru aracı seçmenin hype'tan çok trade-off'ları anlamakla ilgili olduğunu öğretti. İster basit bir kuyruk ister karmaşık bir event mesh ile uğraşıyor olun, her aracın kendine özgü güçlü yanları var.

Olay güdümlü araçlar, mesaj kalıpları ve cloud eşleniklerinin kapsamlı karşılaştırmasına dalalım.

Mesaj Kalıpları: Temel#

Araçları karşılaştırmadan önce, temel kalıpları anlayalım:

1'e 1 (Kuyruk Kalıbı)#

  • Mesaj tek consumer tarafından tüketilir
  • Kullanım alanları: Task işleme, iş dağıtımı
  • Araçlar: SQS, Azure Service Bus Queues, Cloud Tasks

1'e Çok (Topic/Fan-out Kalıbı)#

  • Mesaj birden fazla subscriber'a iletilir
  • Kullanım alanları: Event yayınlama, bildirimler
  • Araçlar: SNS, Azure Service Bus Topics, Cloud Pub/Sub

Çok'a Çok (Event Mesh)#

  • Birden fazla producer/consumer arası karmaşık routing
  • Kullanım alanları: Microservices iletişimi
  • Araçlar: EventBridge, Azure Event Grid, Eventarc

Tam Araç Manzarası#

Basit Kuyruk Servisleri#

AWS SQS (Simple Queue Service)#

Neyde öne çıkıyor: Çok basit kuyruk operasyonları, serverless entegrasyon, otomatik ölçekleme

Çalışan gerçek production config:

TypeScript
// Uygun error handling ve DLQ ile SQS
const params = {
  QueueUrl: 'https://sqs.us-east-1.amazonaws.com/123/my-queue',
  ReceiveMessageWaitTimeSeconds: 20,  // Long polling
  MaxNumberOfMessages: 10,
  VisibilityTimeout: 30,  // İşleme penceresi
  MessageAttributeNames: ['All']
};

// DLQ konfigürasyonu
const dlqParams = {
  QueueName: 'my-queue-dlq',
  Attributes: {
    MessageRetentionPeriod: '1209600',  // 14 gün
    RedrivePolicy: JSON.stringify({
      deadLetterTargetArn: dlqArn,
      maxReceiveCount: 3  // DLQ'ya gitmeden önce 3 kez dene
    })
  }
};

Teslimat garantileri:

  • Standard Queue: En az bir kez (olası duplikasyonlar)
  • FIFO Queue: Tam olarak bir kez işleme
  • Mesaj sıralaması: Sadece FIFO
  • Max mesaj boyutu: 1MB (Ağustos 2025'te 256KB'den yükseltildi)

📝 Not: Mesaj boyutu limitindeki bu 4x artış, daha büyük veri alışverişi gerektiren AI, IoT ve karmaşık uygulama entegrasyon workload'ları için faydalıdır. AWS Lambda'nın event source mapping'i de yeni 1MB payload'ları destekleyecek şekilde güncellendi.

SQS ne zaman parlıyor:

  • Microservices'i decouple etme
  • Batch job işleme
  • Serverless mimariler (Lambda trigger'ları)
  • Basit task kuyrukları

Azure Service Bus Queues#

Enterprise özellikleriyle SQS'in Azure eşleniği:

C#
// Session'lar ve DLQ handling ile Service Bus
var client = new ServiceBusClient(connectionString);
var processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions
{
    MaxConcurrentCalls = 10,
    AutoCompleteMessages = false,
    MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5),
    SubQueue = SubQueue.DeadLetter  // DLQ'ya erişim
});

// Duplikasyon algılamalı mesaj
var message = new ServiceBusMessage(body)
{
    MessageId = Guid.NewGuid().ToString(),  // Deduplication için
    SessionId = sessionId,  // Sıralı işleme için
    TimeToLive = TimeSpan.FromMinutes(5)
};

SQS'ten temel farklar:

  • Sıralı işleme için built-in session'lar
  • Duplikasyon algılama (yapılandırılabilir pencere)
  • Zamanlanmış mesajlar
  • Mesaj boyutu: 256KB (standard), 100MB (premium)

Pub/Sub Sistemleri#

AWS SNS (Simple Notification Service)#

1'e çok mesaj dağıtımı:

TypeScript
// Akıllı routing için filter policy'li SNS
const publishParams = {
  TopicArn: 'arn:aws:sns:us-east-1:123:my-topic',
  Message: JSON.stringify(event),
  MessageAttributes: {
    eventType: { DataType: 'String', StringValue: 'ORDER_CREATED' },
    priority: { DataType: 'Number', StringValue: '1' }
  }
};

// Filter'lı subscription
const subscriptionPolicy = {
  eventType: ['ORDER_CREATED', 'ORDER_UPDATED'],
  priority: [{ numeric: ['>', 0] }]
};

SNS + SQS Pattern (Fanout):

Loading diagram...

Dead Letter Queue (DLQ) Stratejileri#

DLQ Nedir?#

Dead Letter Queue, mesajların birden fazla deneme sonrası başarıyla işlenemediğinde gittiği yerdir. Başarısız mesajlar için acil servis gibi düşünün.

DLQ Implementation Pattern'ları#

Pattern 1: Exponential Backoff ile Basit Retry#

TypeScript
class MessageProcessor {
  async processWithRetry(message: Message, maxRetries = 3) {
    let retryCount = 0;
    let lastError;

    while (retryCount < maxRetries) {
      try {
        return await this.process(message);
      } catch (error) {
        lastError = error;
        retryCount++;

        // Exponential backoff: 1s, 2s, 4s
        const delay = Math.pow(2, retryCount - 1) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));

        // Retry metadata ekle
        message.metadata = {
          ...message.metadata,
          retryCount,
          lastError: error.message,
          lastRetryTimestamp: new Date().toISOString()
        };
      }
    }

    // Max retry'dan sonra DLQ'ya gönder
    await this.sendToDLQ(message, lastError);
  }
}

Pattern 2: DLQ ile Circuit Breaker#

TypeScript
class CircuitBreakerDLQ {
  private failureCount = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

  async processMessage(message: Message) {
    if (this.state === 'OPEN') {
      // Circuit açık, direkt DLQ'ya
      return this.sendToDLQ(message, 'Circuit breaker open');
    }

    try {
      const result = await this.process(message);
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();

      if (this.state === 'OPEN') {
        await this.sendToDLQ(message, error);
      } else {
        // Retry mantığı
        await this.retryQueue.send(message);
      }
    }
  }

  private onFailure() {
    this.failureCount++;

    if (this.failureCount >= 5) {
      this.state = 'OPEN';
      console.error('Circuit breaker hatalar nedeniyle açıldı');
    }
  }
}

Edge ve Hybrid Deployment'lar#

Edge Computing Değerlendirmeleri#

Edge'deki olay güdümlü sistemler benzersiz kısıtlamalara sahip:

TypeScript
// Edge-optimize edilmiş event işleme
class EdgeEventProcessor {
  private localQueue: Queue[] = [];
  private cloudBuffer: Message[] = [];

  async processEvent(event: Event) {
    // Önce lokal işle
    const processed = await this.localProcess(event);

    // Cloud sync için batch'le
    if (this.shouldSyncToCloud(processed)) {
      this.cloudBuffer.push(processed);

      if (this.cloudBuffer.length >= 100 ||
          Date.now() - this.lastSync > 60000) {
        await this.syncToCloud();
      }
    }
  }

  private async syncToCloud() {
    try {
      // Sıkıştır ve batch gönder
      const compressed = this.compress(this.cloudBuffer);
      await this.cloudClient.sendBatch(compressed);
      this.cloudBuffer = [];
    } catch (error) {
      // Cloud ulaşılamazsa lokal sakla
      await this.localStorage.store(this.cloudBuffer);
    }
  }
}

Cloudflare Workers ile Kuyruklar#

TypeScript
// Cloudflare Workers Queue Handler
export default {
  async queue(batch: MessageBatch, env: Env): Promise<void> {
    for (const message of batch.messages) {
      try {
        // Edge'de işle
        const result = await processMessage(message.body);

        // Durable Objects veya KV'de sakla
        await env.KV.put(
          `processed:${message.id}`,
          JSON.stringify(result),
          { expirationTtl: 3600 }
        );

        message.ack();
      } catch (error) {
        // Backoff ile retry
        message.retry({ delaySeconds: 30 });
      }
    }
  }
};

Cross-Cloud Eşlenikler#

Servis Eşleme Tablosu#

AWSAzureGCPKullanım Alanı
SQSService Bus QueuesCloud TasksBasit kuyruk
SNSService Bus TopicsCloud Pub/SubPub/Sub mesajlaşma
EventBridgeEvent GridEventarcEvent routing
KinesisEvent HubsPub/Sub + DataflowStream processing
Lambda + SQSFunctions + Service BusCloud Run + Pub/SubServerless event'ler
DynamoDB StreamsCosmos DB Change FeedFirestore TriggersDatabase event'leri
Step FunctionsLogic AppsWorkflowsEvent orchestration
MSK (Kafka)Event Hubs (Kafka mode)Confluent CloudKafka-uyumlu

Performance Karşılaştırma Matrisi#

AraçThroughputLatencyMesaj BoyutuSıralamaTeslimat GarantisiDLQ Desteği
SQS Standard3K/sec batch10-100ms1MBHayırEn az bir kezEvet
SQS FIFO300/sec10-100ms1MBEvetTam olarak bir kezEvet
SNS100K/sec100-500ms256KBHayırEn az bir kezEvet
Kafka1M+/sec<10ms1MB defaultPartition başınaYapılandırılabilirManuel
RabbitMQ50K/sec1-5ms128MBOpsiyonelEn az bir kezEvet
EventBridge10K/sec500ms-2s256KBHayırEn az bir kezEvet
Kinesis1MB/sec/shard200ms1MBShard başınaEn az bir kezManuel
Azure Service Bus2K/sec10-50ms256KB/100MBEvetEn az bir kezEvet
Cloud Pub/Sub100MB/sec100ms10MBKey başınaEn az bir kezEvet

Karar Çerçevesi#

Hızlı Karar Ağacı#

Loading diagram...

Ne Zaman Ne Kullanılır#

Basit Kuyruklar (SQS/Service Bus) kullan:

  • Servisleri decouple ederken
  • İş dağıtımı
  • Basit retry gereksinimleri
  • Serverless işleme

Pub/Sub (SNS/Topics) kullan:

  • Event yayınlama
  • Fan-out pattern'lar
  • Birden fazla consumer
  • Bildirim sistemleri

Event Router'lar (EventBridge/EventGrid) kullan:

  • Karmaşık routing kuralları
  • Multi-service orchestration
  • SaaS entegrasyonları
  • Event-driven otomasyon

Streaming (Kafka/Kinesis) kullan:

  • Real-time analitik
  • Event sourcing
  • Yüksek throughput (>100K/sec)
  • Event replay gerekli

Yaygın Tuzaklar ve Çözümler#

Tuzak 1: Mesaj Boyut Limitleri#

TypeScript
// Çözüm: Claim check pattern
class LargeMessageHandler {
  async send(largePayload: any) {
    if (JSON.stringify(largePayload).length > 256000) {
      // S3'e sakla
      const s3Key = await this.uploadToS3(largePayload);

      // Referans gönder
      return this.queue.send({
        type: 'large_message',
        s3Key,
        size: largePayload.length
      });
    }

    return this.queue.send(largePayload);
  }
}

Tuzak 2: Zehirli Mesajlar#

TypeScript
// Çözüm: Zehirli mesaj algılama
class PoisonMessageDetector {
  private messageAttempts = new Map<string, number>();

  async process(message: Message) {
    const attempts = this.messageAttempts.get(message.id) || 0;

    if (attempts >= 3) {
      // Zehirli mesaj olarak tanımlandı
      await this.quarantine(message);
      return;
    }

    try {
      await this.processMessage(message);
      this.messageAttempts.delete(message.id);
    } catch (error) {
      this.messageAttempts.set(message.id, attempts + 1);

      if (this.isPoisonPattern(error)) {
        await this.quarantine(message);
      } else {
        throw error; // Retry
      }
    }
  }
}

Tuzak 3: Sıralama Garantileri#

TypeScript
// Çözüm: Partition key stratejisi
class OrderedEventProcessor {
  async publishOrdered(events: Event[]) {
    // Entity ID'ye göre sıralama için grupla
    const grouped = this.groupBy(events, e => e.entityId);

    for (const [entityId, entityEvents] of grouped) {
      // Timestamp'e göre sırala
      entityEvents.sort((a, b) => a.timestamp - b.timestamp);

      // Aynı partition key ile gönder
      for (const event of entityEvents) {
        await this.kafka.send({
          topic: 'events',
          key: entityId,  // Sıralamayı garanti eder
          value: event
        });
      }
    }
  }
}

Monitoring ve Gözlemlenebilirlik#

Takip Edilecek Temel Metrikler#

TypeScript
// Kapsamlı metrik toplama
class EventMetrics {
  private metrics = {
    messagesPublished: new Counter('messages_published_total'),
    messagesConsumed: new Counter('messages_consumed_total'),
    messagesFailed: new Counter('messages_failed_total'),
    processingDuration: new Histogram('message_processing_duration_seconds'),
    queueDepth: new Gauge('queue_depth'),
    consumerLag: new Gauge('consumer_lag'),
    dlqDepth: new Gauge('dlq_depth')
  };

  async recordProcessing(message: Message, processor: Function) {
    const timer = this.metrics.processingDuration.startTimer();

    try {
      const result = await processor(message);
      this.metrics.messagesConsumed.inc();
      return result;
    } catch (error) {
      this.metrics.messagesFailed.inc({
        error_type: error.constructor.name,
        queue: message.source
      });
      throw error;
    } finally {
      timer();
    }
  }
}

Sonuç#

Olay güdümlü manzara geniş, ama anahtar şunları anlamak:

  1. Mesaj kalıpları araç seçimini belirler
  2. Teslimat garantileri mimariyi etkiler
  3. DLQ stratejileri production sistemleri oyuncak sistemlerden ayırır
  4. Cloud eşlenikleri çoğu pattern için mevcut
  5. Edge gereksinimleri özel değerlendirme gerektirir

Basit başla, her şeyi ölç ve tahmin edilenlerden ziyade gerçek gereksinimlere göre evrimleş. En önemlisi, failure için tasarla - çünkü mesajlar başarısız olacak, servisler çökecek ve zehirli mesajlar ortaya çıkacak.

En iyi mimari, güvenilirlik ve gözlemlenebilirliği korurken ihtiyaçlarınla birlikte evrimleşebilen mimaridir.


İlgili Derinlemesine İncelemeler:

Conclusion#

Çeviri eklenecek.

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts