ApsaraMQ for RocketMQ mesajları en az bir kez teslim eder. Bu durum, tüketicinin aynı mesajı birden fazla kez alabileceği anlamına gelir. Ödeme tahsilatı, stok ayarlaması veya sipariş oluşturma gibi tekrar eden işlemlere duyarlı iş mantığınız varsa yinelenmez tüketim uygulamanız gerekir. Yinelenmez tüketim, aynı mesajın birden fazla kez işlenmesinin tek seferlik işleme ile aynı sonucu üretmesini sağlar.
Örneğin, bir tüketici 100 USD tutarındaki bir sipariş için ödeme tahsilatı mesajını işler. Ağ sorunları nedeniyle mesaj iki kez teslim edilir. Yinelenmez tüketim sayesinde ödeme yalnızca bir kez tahsil edilir ve sipariş için sadece bir adet 100 USD tutarında tahsilat kaydı oluşturulur.
Tekrarlanan mesajların oluşma nedenleri
Tekrarlanan mesajlar üç farklı senaryoda ortaya çıkar.
Üretici yeniden denemesi
Bir üretici mesaj gönderir ve ApsaraMQ for RocketMQ aracı sunucusu (broker) bu mesajı kalıcı olarak depolar. Ancak geçici bir ağ sorunu veya üreticinin çökmesi nedeniyle aracı sunucu üreticiye onay (acknowledgment) gönderemeyebilir. Üretici bu durumu başarısız gönderim olarak algılar ve işlemi yeniden dener. Tüketici bu durumda içeriği aynı ancak mesaj kimlikleri farklı olan iki mesaj alır.
Aracı sunucunun yeniden teslim etmesi
Bir tüketici mesajı alır ve işler ancak geçici bir ağ sorunu nedeniyle aracı sunucuya gönderilen onay başarısız olur. Aracı sunucu mesajın tüketilip tüketilmediğini teyit edemediğinden en az bir kez teslim garantisi kapsamında ağ düzeldiğinde mesajı yeniden teslim eder. Tüketici bu durumda içeriği ve mesaj kimliği aynı olan iki mesaj alır.
Yük dengeleme
Ağ dalgalanmaları, aracı sunucunun yeniden başlatılması veya tüketici uygulamasının yeniden başlatılması gibi olaylar yük dengelemeyi tetikler. Yeniden dengeleme sırasında tüketici daha önce teslim edilmiş mesajları tekrar alabilir.
Mesaj kimlikleri yerine iş anahtarlarını kullanın
İlk akla gelen yöntem tekrarları mesaj kimliğine göre filtrelemektir ancak bu yaklaşım güvenilir değildir. Üretici yeniden denemesi senaryosunda belirtildiği üzere üretici gönderimi yeniden denediğinde aynı mantıksal mesaj farklı mesaj kimlikleriyle gelebilir. Mesaj kimliğine dayalı yinelenen öğe kaldırma işlemi bu kopyaları tamamen gözden kaçırır.
Bunun yerine mesaj anahtarı olarak benzersiz bir iş tanımlayıcısı atayın. Örneğin bir sipariş kimliği, ödeme işlemi kimliği veya işleminizi benzersiz şekilde tanımlayan herhangi bir değer kullanabilirsiniz. Mesajın kaç kez gönderildiğinden veya yeniden teslim edildiğinden bağımsız olarak bu anahtar tutarlı kalır. Anahtar mesajlaşma sistemi tarafından değil iş mantığınızdan türetilir.
Bu yaklaşım öngörülebilir tekrarlanabilirlik sağlar. Bir hata oluşup mesaj yeniden gönderildiğinde aynı iş bağlamı her zaman aynı anahtarı üretir ve böylece yinelenen öğelerin kaldırılması güvenilir hale gelir.
Yinelenmez tüketimi uygulayın
Adım 1: Üreticide mesaj anahtarını ayarlayın
Mesaj gönderirken benzersiz bir iş tanımlayıcısını mesaj anahtarı olarak ekleyin.
Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);
ORDERID_100 ifadesini sipariş kimliği veya işlem kimliği gibi gerçek benzersiz iş tanımlayıcısıyla değiştirin.
Adım 2: Tüketicide mesaj anahtarını alın
Tüketici geri arama (callback) işlevinde mesaj anahtarını alın ve yinelenmez işlemeyi zorlamak için kullanın.
consumer.subscribe("ons_test", "*", new MessageListener() {
public Action consume(Message message, ConsumeContext context) {
String key = message.getKey()
// Perform idempotent processing based on the message key that uniquely identifies your business.
}
});
Adım 3: Yinelenen öğe kaldırma deposu ile yinelenmezliği zorlayın
Sadece mesaj anahtarı çift işlenmeyi engellemez; uygulamanızın anahtarın daha önce işlenip işlenmediğini kontrol etmesi gerekir. Yaygın bir desen benzersiz kısıtlamaya sahip ilişkisel bir veritabanı kullanmaktır:
İşlemeden önce mesaj anahtarını anahtar sütununda benzersiz kısıtlama olan bir yinelenen öğe kaldırma tablosuna ekleyin.
Ekleme başarılı olursa mesajı işleyin.
Ekleme birincil anahtar veya benzersiz kısıtlama ihlali nedeniyle başarısız olursa mesaj zaten işlenmiştir. Bu mesajı atlayın.
Kontrol-sonra-ekle yerine ekle-sonra-kontrol kalıbını kullanın. Kontrol-sonra-ekle yönteminde iki iş parçacığı da ekleme yapmadan önce kontrolü geçebilir ve bu durum çift işlenmeye yol açar. Veritabanının benzersiz kısıtlama ihlaline dayanmak atomiktir ve yarış durumu riski taşımaz.
Yinelenen öğe kaldırma tablosu örneği (MySQL):
CREATE TABLE message_dedup (
message_key VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (message_key)
);
Yinelenmez tüketici mantığı örneği:
consumer.subscribe("ons_test", "*", new MessageListener() {
public Action consume(Message message, ConsumeContext context) {
String key = message.getKey();
try {
// Attempt to insert the message key. Fails if already processed.
insertMessageKey(key);
} catch (DuplicateKeyException e) {
// Already processed. Skip.
return Action.CommitMessage;
}
// Process the business logic.
processOrder(key);
return Action.CommitMessage;
}
});
insertMessageKey ve processOrder ifadelerini kendi veritabanı ve iş mantığı yöntemlerinizle değiştirin.
İlişkisel veritabanının darboğaz oluşturabileceği yüksek aktarım hızı gerektiren senaryolar için bunun yerine SETNX (SET if Not eXists) kullanan Redis tabanlı bir yaklaşım benimseyin.
En iyi uygulamalar
| Uygulama | Ayrıntılar |
|---|---|
| Yinelenen öğe kaldırma ve iş mantığını tek bir işlem içinde sarın | Yinelenen öğe kaldırma kaydı eklendikten sonra iş mantığı başarısız olursa yinelenen öğe kaldırma kaydı geri alınır ve mesajın sonraki teslimatta yeniden denenmesine olanak tanınır. İşlem olmadan başarısız bir iş operasyonu yinelenen öğe kaldırma kaydını yerinde bırakır ve mesaj hiçbir zaman yeniden işlenmez. |
| Yinelenen öğe kaldırma deposunu periyodik olarak temizleyin | Yinelenen öğe kaldırma tablosu zamanla büyür. Sınırsız depolama artışını önlemek için bir saklama süresi (örneğin 7 gün) belirleyin ve eski kayıtları toplu halde silin. |
| Aktarım hızınıza uygun yinelenen öğe kaldırma deposunu seçin | Orta düzey aktarım hızı için benzersiz kısıtlamalara sahip ilişkisel bir veritabanı kullanın. Yüksek aktarım hızı gerektiren senaryolar için otomatik temizlemeyi de sağlayan ve saklama sürenizle eşleşen bir TTL ile Redis SETNX kullanın. |