DLX ve DLQ Nedir? Mesaj Kuyruklarında Sorunları Zarafetle Çözmenin Yolları

Hepimizin başına gelir, kodu yazdık, kuyruğa mesajları gönderdik, her şey yolunda gidiyor gibi görünür ama bir anda işler ters gitmeye başlar. Mesajlar ya hiç işlenmez ya da sürekli hata verip kuyruğu şişirir. İşte bu tür durumlarda "DLX ve DLQ" dediğimiz kahramanlar devreye giriyor. Gelin, mesajlaşma sistemlerinin bu vazgeçilmezlerini adım adım inceleyelim.

Dead Letter Exchange (DLX) Nedir?

DLX, "Dead Letter Exchange" kelimelerinin kısaltmasıdır ve RabbitMQ gibi mesajlaşma sistemlerinde kullanılan özel bir exchange türüdür. Temelde, "ölü mektup merkezi" gibi düşünebilirsin. Bir mesaj, işlenmesi sırasında herhangi bir nedenden dolayı başarısız olduğunda, bu mesajı normal mesaj akışından çıkarıp özel bir yere yönlendirmek isteriz. İşte o özel yerin giriş kapısı DLX’tir.

Hangi Durumlarda Mesajlar DLX’e Gönderilir?

DLX’in devreye girdiği durumları üç maddede özetleyelim:

  • Mesajın belirlenen sürede işlenememesi (mesajın TTL süresi dolmuşsa)
  • Mesajın tüketici tarafından reddedilmesi veya başarısız olması (reject ya da nack durumları)
  • Kuyruğun kapasitesinin dolması sonucu mesajların artık kabul edilmemesi (queue length limit)

Böyle durumlarda mesajlar orijinal kuyruğundan alınır ve DLX tarafından belirtilen alternatif kuyruğa aktarılır.

Dead Letter Queue (DLQ) Nedir?

DLQ ise aslında DLX’e bağlı çalışan kuyruktur. Yani DLX’in yönlendirdiği tüm problemli veya başarısız mesajların düştüğü kuyruk. Bu yüzden DLX ile DLQ genelde birlikte anılır.

Düşün ki evinde çöpe atmak istemediğin ama düzeni de bozan eşyalar var. Bu eşyaları ne yaparsın? Muhtemelen onları özel bir dolapta veya odada saklarsın. İşte DLQ da tam bu odadır. Başarısız olan mesajları burada tutarak, daha sonra bunları inceleyip neden başarısız olduklarını görebilir ve uygun aksiyonu alabilirsin.

Neden DLX ve DLQ Kullanmalıyız?

Basitçe söylemek gerekirse, DLX ve DLQ kullanımının avantajları şöyle sıralanabilir:

  • Hata Ayıklama ve İzleme Kolaylığı: Mesajların neden işlenmediğini rahatça görebilirsin.
  • Veri Kaybını Önleme: Başarısız mesajlar doğrudan silinmek yerine ayrı bir kuyruğa alındığı için veri kaybı önlenir.
  • Performans: Problemli mesajların sürekli ana kuyruğu meşgul etmesi engellenir ve sistem performansı artar.
  • Tekrar İşleme İmkânı: DLQ’daki mesajları hataları giderdikten sonra tekrar işleyebilirsin.

DLX ve DLQ Nasıl Kullanılır? (Örnek Senaryo)

Diyelim ki bir sipariş uygulaması geliştiriyorsun ve sipariş bilgileri RabbitMQ üzerinden kuyruğa gönderiliyor. Siparişlerin bazıları hata alabilir; örneğin veritabanı bağlantı sorunu veya geçici network sorunları yaşanabilir. İşte DLX ve DLQ burada kurtarıcı rol oynar:

  1. Ana kuyruğu tanımlarken bir DLX ayarlarsın:

channel.QueueDeclare(queue: "order_queue",
                     durable: true,
                     exclusive: false,
                     autoDelete: false,
                     arguments: new Dictionary<string, object>
                     {
                         {"x-dead-letter-exchange", "order.dlx"}
                     });


  1. DLX ve bağlı DLQ kuyruğunu oluşturursun:

// DLX oluştur
channel.ExchangeDeclare("order.dlx", ExchangeType.Fanout);

// DLQ oluştur ve DLX’e bağla
channel.QueueDeclare("order.dlq", true, false, false, null);
channel.QueueBind("order.dlq", "order.dlx", "");


Bu şekilde, sipariş kuyruğundan herhangi bir sebeple reddedilen veya işlenemeyen mesajlar otomatik olarak order.dlq kuyruğuna düşer.

DLQ’daki Mesajları Nasıl Yönetmeliyiz?

Bu mesajları yönetirken genelde birkaç yol izleyebilirsin:

  • Elle İnceleyip İşlemek: Bir admin panel üzerinden mesajları görüntüleyip elle tekrar kuyruğa gönderebilirsin.
  • Otomatik Tekrar İşleme: Örneğin, 5 dakika sonra yeniden deneme mekanizmasıyla mesajları otomatik olarak tekrar işlemeyi deneyebilirsin.
  • Alarm ve Bildirim Mekanizması: DLQ’ya mesaj düştüğünde mail veya Slack gibi kanallar üzerinden bildirim gönderip anında müdahale edebilirsin.

Nelere Dikkat Etmeli?

  • DLQ biriken mesajları periyodik olarak kontrol etmeli ve yönetmelisin. Mesajların burada sonsuza kadar kalmasına izin vermek sistemi karmaşıklaştırabilir.
  • Mesajları yeniden işlemeyi denerken aynı hatanın tekrar tekrar oluşmaması için sorunun kök nedenini bulup çözmen önemlidir.

Acknowledge (ACK), Delivery Tag ve BasicAck Nedir?

1. Acknowledge (ACK - Onaylama)

RabbitMQ'da tüketici (consumer) kuyruktan mesaj aldığında, mesajı işlediğini ve artık kuyruğa tekrar koyulmasına gerek olmadığını belirtmesi gerekir. İşte bu onaylama işlemine "Acknowledge (ACK)" diyoruz.

  • ACK gönderilirse: Mesaj başarıyla işlendi demektir, RabbitMQ mesajı kuyruktan kaldırır.
  • ACK gönderilmezse: Mesajın işlenmediği veya sorun oluştuğu varsayılır. Mesaj kuyruğa tekrar düşebilir veya alternatif bir yere yönlendirilebilir (DLX/DLQ mekanizması burada devreye girer).

Özetle, ACK işlemi RabbitMQ’ya şöyle der:

"Mesaj bana ulaştı, görev tamam, artık bunu kuyruktan silebilirsin."

2.Delivery Tag Nedir?

Her mesajın RabbitMQ tarafından üretilen benzersiz bir numarası vardır. Bu benzersiz numaraya Delivery Tag denir.

  • Delivery Tag’in görevi: Tüketiciye gönderilen mesajların benzersiz kimliğini belirtir.
  • Tüketici mesajı işler ve mesajı silmesini belirtmek için ACK gönderirken, hangi mesajın işlendiğini belirtmek için Delivery Tag'i kullanır.

Örneğin:

  • RabbitMQ mesaj gönderdiğinde deliveryTag = 12 olsun.
  • Bu mesajı aldıktan sonra sen RabbitMQ’ya şöyle diyorsun:
“12 numaralı mesajı aldım, silebilirsin.”

İşte Delivery Tag, bu işlemi mümkün kılar.

3.BasicAck Metodu Nedir?

RabbitMQ istemcileri (örneğin .NET’in RabbitMQ.Client kütüphanesi) mesajların başarıyla işlendiğini bildirmek için BasicAck metodunu kullanır.

BasicAck metodunun yapısı şu şekildedir:


channel.BasicAck(deliveryTag: mesajın_deliveryTag'i, multiple: false);

  • deliveryTag: Hangi mesajın başarılı olduğunu gösteren, RabbitMQ tarafından atanan mesajın benzersiz numarasıdır.
  • multiple: Eğer bu değer true yapılırsa, belirtilen deliveryTag’e kadar olan tüm mesajlar başarılı sayılır. Genelde tek tek işlemeyi tercih ederiz, bu yüzden çoğunlukla false kullanılır.
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);

Burada ea (event args), RabbitMQ tarafından sana iletilen mesajın bilgilerini tutan nesnedir.

4.BasicReject ve BasicNack Kavramları

Bazen mesajı işleyemeyebilirsin ve bunu da RabbitMQ’ya bildirmek gerekebilir:

  • BasicReject: Tek bir mesajı reddeder, dilersen mesajı tekrar kuyruğa koyar veya koymazsın.
  • BasicNack: Birden fazla mesajı reddetmene ve tekrar kuyruğa koymana olanak sağlar. (Genelde hata durumlarında kullanılır.)
// Tek bir mesajı reddet ve tekrar kuyruğa gönderme
channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: false);

// Birden fazla mesajı reddetme (çoklu reddetme)
channel.BasicNack(deliveryTag: ea.DeliveryTag, multiple: true, requeue: false);
KavramAçıklamaKullanımı
Acknowledge (ACK)Mesajın başarıyla işlendiğini belirtir.channel.BasicAck(...)
Delivery TagMesajın benzersiz kimliğini temsil eder.ea.DeliveryTag
BasicAckMesajın işlenmesinin tamamlandığını bildirir.channel.BasicAck(ea.DeliveryTag, false)
BasicReject/NackMesajları reddetmek ve tekrar kuyruklara göndermek için kullanılır.channel.BasicReject(...), channel.BasicNack(...)



Email Aktivasyon Senaryosu

Düşün ki bir freelancer platformunda üyelik sistemi geliştiriyoruz. Kullanıcı kayıt olduktan hemen sonra e-posta adresini doğrulaması gerekiyor. Bu senaryoyu hem gerçekçi hem de performanslı bir yapıda kurgulayalım:

Senaryomuzun İşleyiş Adımları:

  1. Kullanıcı sisteme kayıt olur.
  2. Kayıt sonrası sistem tarafından benzersiz bir aktivasyon linki oluşturulur.
  3. Bu aktivasyon bilgisi RabbitMQ kullanılarak bir mesaj kuyruğuna atılır.
  4. Kuyruğa düşen mesaj, arka planda çalışan bir Consumer (tüketici) tarafından alınarak e-posta olarak kullanıcıya gönderilir.
  5. Eğer mail gönderiminde hata oluşursa (örneğin SMTP server'a erişilemiyorsa), mesaj kaybolmaz ve Dead Letter Queue (DLQ)'ya taşınır.
  6. DLQ'ya düşen mesajlar sonradan tekrar işlenebilir, böylece sistem güvenilir ve izlenebilir hale gelir.

RabbitMQ

  • Ana Kuyruk: activation_email_queue
  • Dead Letter Exchange (DLX): activation_email.dlx
  • Dead Letter Queue (DLQ): activation_email.dlq

Öncelikle RabbitMQ ve e-posta gönderimi için gerekli .NET Core Web API kodlarını yazacağız. Yapıyı şöyle organize edeceğiz:

1️) Producer (Mesaj Gönderen – API Controller)
2️) Consumer (Mesaj Tüketen ve E-posta Gönderen)
3️) RabbitMQ Bağlantıları (DLX ve DLQ Yapılandırması)
4️) E-posta Servisi (SMTP Kullanarak Gerçekçi Mail Gönderimi)

1) Producer – RabbitMQ’ya Mesaj Gönderen API

Kullanıcı kaydolduğunda bir aktivasyon maili göndermesi için RabbitMQ'ya mesaj bırakacağız.

Bu adımda ne yapıyoruz?

  • Kullanıcının e-posta adresini ve aktivasyon kodunu kuyruğa atıyoruz.

API Controller – EmailController.cs

using System.Text;
using System.Text.Json;
using RabbitMQ.Client;
using Microsoft.AspNetCore.Mvc;

[Route("api/email")]
[ApiController]
public class EmailController : ControllerBase
{
    [HttpPost("send-activation")]
    public IActionResult SendActivationEmail([FromBody] EmailRequest request)
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        
        using var connection = factory.CreateConnection();
        using var channel = connection.CreateModel();

        // DLX tanımlamasıyla kuyruk oluşturuyoruz
        var queueArgs = new Dictionary<string, object>
        {
            { "x-dead-letter-exchange", "activation_email.dlx" }
        };

        channel.QueueDeclare(queue: "activation_email_queue",
                             durable: true,
                             exclusive: false,
                             autoDelete: false,
                             arguments: queueArgs);

        var message = JsonSerializer.Serialize(request);
        var body = Encoding.UTF8.GetBytes(message);

        channel.BasicPublish(exchange: "",
                             routingKey: "activation_email_queue",
                             basicProperties: null,
                             body: body);

        return Ok("Aktivasyon maili kuyruğa eklendi.");
    }
}

// DTO: Kullanıcının e-posta adresi ve aktivasyon kodunu içeren model
public class EmailRequest
{
    public string Email { get; set; }
    public string ActivationCode { get; set; }
}

2) Consumer – Mesajları Okuyup E-Posta Gönderen İşlem

Şimdi kuyruğa düşen mesajları okuyacak ve e-posta gönderecek olan Consumer kodunu yazalım.

Consumer – EmailConsumer.cs

``` csharp
using System.Text;
using System.Text.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

class EmailConsumer
{
    static void Main()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using var connection = factory.CreateConnection();
        using var channel = connection.CreateModel();

        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            var emailRequest = JsonSerializer.Deserialize<EmailRequest>(message);

            try
            {
                EmailService.SendActivationEmail(emailRequest.Email, emailRequest.ActivationCode);

                channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
                Console.WriteLine($"Aktivasyon e-postası gönderildi: {emailRequest.Email}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Mail gönderimi başarısız oldu: {ex.Message}");

                // Mesajı DLQ'ya düşürmek için requeue=false yapıyoruz
                channel.BasicReject(deliveryTag: ea.DeliveryTag, requeue: false);
            }
        };

        channel.BasicConsume(queue: "activation_email_queue",
                             autoAck: false,
                             consumer: consumer);

        Console.WriteLine("E-posta tüketici servisi başlatıldı...");
        Console.ReadLine();
    }
}

```

3) DLQ'daki Mesajları Tekrar İşlemek


Eğer bir mesaj hata alıp DLQ’ya düştüyse, onu tekrar işlememiz gerekir. Bunu elle veya otomatik olarak yapabiliriz.

DLQ'daki mesajları tekrar kuyruğa almak için script – RetryDLQ.cs

``` csharp

using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

class RetryDLQ
{
    static void Main()
    {
        var factory = new ConnectionFactory() { HostName = "localhost" };
        using var connection = factory.CreateConnection();
        using var channel = connection.CreateModel();

        var dlqConsumer = new EventingBasicConsumer(channel);
        dlqConsumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);

            Console.WriteLine($"DLQ mesajını tekrar gönderiyor: {message}");

            channel.BasicPublish(exchange: "",
                                 routingKey: "activation_email_queue",
                                 basicProperties: null,
                                 body: body);

            channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
        };

        channel.BasicConsume(queue: "activation_email.dlq",
                             autoAck: false,
                             consumer: dlqConsumer);

        Console.WriteLine("DLQ Retry Servisi başlatıldı...");
        Console.ReadLine();
    }
}

```

Burada ne yaptık?

  • DLQ’daki mesajları çekiyoruz.
  • Onları tekrar activation_email_queue kuyruğuna atıyoruz.
  • Böylece e-posta tekrar gönderilmeyi deneyecek.

4)SMTP Email Servisi (SendGrid Kullanarak Mail Gönderimi)

Eğer gerçek bir e-posta servisi kullanacaksan, aşağıdaki gibi bir SMTP servisi oluşturabilirsin:

EmailService.cs

``` csharp
using System;
using System.Net;
using System.Net.Mail;

public class EmailService
{
    public static void SendActivationEmail(string toEmail, string activationCode)
    {
        var smtpClient = new SmtpClient("smtp.sendgrid.net")
        {
            Port = 587,
            Credentials = new NetworkCredential("apikey", "SENDGRID_API_KEY"),
            EnableSsl = true,
        };

        var mailMessage = new MailMessage
        {
            From = new MailAddress("no-reply@xxxx.com"),
            Subject = "Hesabınızı Aktive Edin",
            Body = $"Lütfen aşağıdaki bağlantıya tıklayarak hesabınızı aktif edin: \n\n https://xxxx.com/activate?code={activationCode}",
            IsBodyHtml = false,
        };

        mailMessage.To.Add(toEmail);
        smtpClient.Send(mailMessage);

        Console.WriteLine($"Aktivasyon e-postası gönderildi: {toEmail}");
    }
}

```

Sonuç

Artık RabbitMQ + DLX + DLQ + SMTP kullanarak asenkron email aktivasyon sistemi kurduk.

✅ Kullanıcı kayıt olduğunda, e-posta kuyruğa ekleniyor.
✅ Consumer mesajı alıp SMTP ile e-posta gönderiyor.
✅ Eğer hata olursa mesaj DLQ’ya düşüyor.
✅ DLQ'daki mesajlar daha sonra tekrar işleniyor.

Alternatif Bir Yöntem Olarak Polly Kullanımı

DLQ (Dead Letter Queue) kullanımı, başarısız mesajları yönetmek için harika bir yöntemdir. Ancak bazı durumlarda, mesajları doğrudan DLQ'ya göndermek yerine birkaç kez tekrar denemek (retry) daha verimli olabilir.

Bu noktada Polly gibi .NET hata yönetim kütüphaneleri devreye girer. Polly kullanarak SMTP sunucusu geçici olarak yanıt vermediğinde veya küçük bir hata oluştuğunda, mesajı belirli bir sayıda tekrar deneyebiliriz.

Polly ile exponential backoff (artan bekleme süresiyle tekrar deneme) uygulanarak sunucunun aşırı yüklenmesini önlemek ve mesajların gereksiz yere DLQ'ya düşmesini engellemek mümkündür. Eğer belirlenen deneme sayısı aşıldığında hâlâ başarısızlık devam ediyorsa, mesajı yine DLQ'ya göndermek en sağlıklı yöntemdir.

Bu yüzden DLQ ve Polly’yi birlikte kullanarak sağlam bir hata yönetimi stratejisi oluşturulabilir.


Dostlar, bu tür makaleleri hazırlamak ciddi bir emek ve zaman gerektiriyor. Eğer faydalı bulduysanız, paylaşarak daha fazla kişiye ulaşmasına destek olabilirsiniz. Serinin bir sonraki bölümünde görüşmek üzere!
İyi çalışmalar dilerim.