Efsane Modeli: Mikro Hizmet Mimarisinde Dağıtılmış İşlemler
Geleneksel monolitik uygulamalarda, birden fazla varlık arasında veri tutarlılığının sağlanması basittir. İlişkisel veritabanı motorları, yerel SQL işlemlerine sarılmış ACID (Atomiklik, Tutarlılık, Yalıtım, Dayanıklılık) garantileri sağlar. Bir sipariş verme, ödeme kesintisi veya stok ayırma işlemi yarı yolda başarısız olursa ROLLBACK çağrısı, her veritabanı değişikliğini anında geri döndürür.
Ancak modern bir Mikro Hizmet Mimarisine geçiş yapıldığında veri yönetimi temelden değişir. Etki alanı özerkliğini ve bağımsız ölçeklenebilirliği sağlamak için her mikro hizmet kendi özel veritabanına sahiptir. Bir e-ticaret ödemesinin işlenmesi gibi tek bir iş operasyonu artık birden fazla hizmet sınırını ve veritabanı motorunu (ör. Siparişler için PostgreSQL, Ödemeler için DynamoDB, Envanter için Redis) kapsıyor.
Dağıtılmış mikro hizmetler tek bir veritabanı işlemine dayanamayacağından, ağ sınırları boyunca veri tutarlılığını korumak, dağıtılmış sistem mühendisliğindeki en zorlu sorunlardan biri haline gelir.
Yazılım mimarları, sistemin kullanılabilirliği veya performansından ödün vermeden bu sorunu çözmek için Saga Modeline güveniyor.
Bu derinlemesine incelemede, geleneksel dağıtılmış işlemlerin neden başarısız olduğunu keşfedeceğiz, Saga Modeli’nin temel mekanizmalarını parçalara ayıracağız, Koreografi ile Orkestrasyon‘u karşılaştıracağız, izolasyon karşı önlemlerini analiz edeceğiz, Go ve Java‘daki üretim kodu uygulamalarını inceleyeceğiz ve gerçek hayattaki başarısızlık geri alma işlemlerinin güvenli bir şekilde nasıl ele alınacağını öğreneceğiz.
Temel Sorun: Mikro Hizmetlerde İki Aşamalı Taahhüt (2PC) Neden Başarısız?
Saga Modelini benimsemeden önce mühendisler sıklıkla şunu soruyor: Mikro hizmetlerimizde neden geleneksel İki Aşamalı Taahhüt’ü (2PC / XA) kullanamıyoruz?
2PC’nin Mekaniği
İki Aşamalı Taahhüt, merkezi bir İşlem Yöneticisi kullanarak birden fazla veritabanı düğümünde dağıtılmış bir işlemi iki aşamada koordine eder:
- Hazırlık Aşaması: İşlem Yöneticisi, katılan tüm veritabanı düğümlerinden gerekli satırları hazırlamalarını ve kilitlemelerini ister. Katılımcılar
YESveyaNOoyu veriyor. - Kayıt Aşaması: Tüm katılımcılar
YESoyu verirse, Yönetici tüm düğümlere birCOMMITkomutu yayınlar. Herhangi bir düğümNOoyu verirse veya zaman aşımına uğrarsa, birROLLBACKverir.
Client ----> Transaction Manager
|
+-------------+-------------+
| (Prepare) | (Prepare) | (Prepare)
v v v
Order DB Payment DB Inventory DB
2PC Neden Mikro Hizmetlere Yönelik Bir Anti-Desendir?
2PC güçlü tutarlılığı garanti ederken, çeşitli mimari kusurlar nedeniyle bulutta yerel mikro hizmet ortamlarında bozulur:
- Engelleme ve Kaynak Çatışması: Çok aşamalı ağ anlaşması boyunca veritabanı satırları kilitli kalır. Ağ gecikmesi artarsa veya hizmet yavaşlarsa kilitler açık tutulur, bağlantı havuzları hızla tüketilir ve kademeli sistem arızasına neden olur.
- Kullanılabilirlik Darboğazı: 2PC’de sistem kullanılabilirliği, tüm katılımcıların kullanılabilirliklerinin ürünlerine bağlıdır ($A_{toplam} = A_1 \times A_2 \times \dots \times A_n$). Hazırlık aşamasında herhangi bir hizmet veya veritabanı düğümü çevrimdışı olursa, tüm küresel işlem süresiz olarak engellenir.
- Hizmet Özerkliğinin Kaybı: 2PC, hizmetleri, veritabanı motorlarını doğrudan bağlayarak ağ API’leri genelinde veritabanı düzeyinde XA protokollerini açığa çıkarmaya zorlar.
- Aracı Kısıtlamaları: Apache Kafka veya RabbitMQ gibi yüksek verimli mesaj aracıları, ilişkisel veritabanları genelinde geleneksel XA 2PC işlemlerine yerel olarak katılmaz.
CAP Teoremine göre, dağıtılmış sistemlerin Ağ Bölümleri (P) altında Güçlü Tutarlılık (C) ve Yüksek Kullanılabilirlik (A) arasında seçim yapması gerekir. Modern mikro hizmet sistemleri Kullanılabilirlik ve Bölüm Toleransına öncelik vererek anlık tutarlılığı Nihai Tutarlılık (BASE: Temel Olarak Kullanılabilir, Geçici Durum, Nihai Tutarlılık) ile değiştirir.
Destan Kalıbı Nedir?
Saga Modeli ilk olarak 1987 yılında Hector Garcia-Molina ve Kenneth Salem tarafından veritabanı yönetim sistemlerinde uzun ömürlü işlemleri yönetmeye yönelik bir mekanizma olarak önerildi. Modern mikro hizmetlerde Saga, ayrı yerel işlemlerin bir dizisini temsil eder.
Çoklu hizmet akışının tamamını tek bir küresel kilit içine sarmak yerine Saga, bir iş sürecini bir dizi bağımsız yerel veritabanı işlemi ($T_1, T_2, \dots, T_n$) olarak yürütür.
Her yerel işlem, tek bir mikro hizmetin veritabanındaki verileri günceller ve bir etki alanı olayı veya mesajı yayınlar. Bu olay, aşağı akış hizmetinde bir sonraki yerel işlemi ($T_{i+1}$) tetikler.
[Order Service] [Payment Service] [Inventory Service]
Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order) (Process Payment) (Reserve Stock)
İleriye Yönelik Uygulama ve Geriye Dönük Tazminat
Tüm yerel işlemler başarılı olursa Saga başarıyla tamamlanır (İleri Yürütme).
Bununla birlikte, yerel bir işlem yarı yolda başarısız olursa (örneğin, bir öğenin stokta kalmaması nedeniyle $T_3$ başarısız olursa), Saga önceki adımlar ($T_1, T_2$) için bir ROLLBACK veritabanını basitçe çağıramaz çünkü bu yerel işlemler zaten ilgili veritabanlarına taahhütte bulunmuştur.
Halihazırda gerçekleştirilen yerel işlemleri geri almak için Saga’nın Telafi Edici İşlemleri ($C_{n-1}, \dots, C_1$) ters sırada yürütmesi gerekir.
Forward Flow: T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
|
Backward Rollback: C1 (Cancel Order) <--- C2 (Refund Card) <----------+
Destan İşlemlerinin Sınıflandırılması
Sağlam bir Saga iş akışı tasarlamak için işlem sırasındaki her adımın üç yapısal türden birine göre sınıflandırılması gerekir:
| İşlem Türü | Açıklama | Yetkisizlik ve Geri Alma Gereksinimi |
|---|---|---|
| Telafi Edilebilir İşlemler | Geri dönüşü olmayan noktadan önce yürütülen adımlar. Aşağı yöndeki bir adım başarısız olursa bunlar geri alınabilir veya tersine çevrilebilir. | İlgili bir Telafi İşlemi olmalıdır ($C_i$). |
| Pivot İşlem | Saga’daki kesin adım. Pivot başarılı olursa Saga’nın bitmesi garantidir. Başarısız olursa Saga geri döner. | Ne telafi edilebilir ne de yeniden yargılanabilir; geri alma ve ileri tamamlama arasındaki sınırı işaret eder. |
| Yeniden Denenebilir İşlemler | Pivot işleminden sonra yürütülen adımlar. Sonunda başarılı olmaları garanti edilir ve tazminat gerektirmezler. | Başarılı olana kadar otomatik olarak yeniden deneneceğinden kesinlikle eşit yetkiye sahip olmalıdır. |
E-Ticaret Ödeme Örneği Dağılımı
Dört adımdan oluşan bir e-ticaret sipariş ödemesini düşünün:
- $T_1$: Bekleyen Emir Oluştur (Telafi Edilebilir) $\to$ $C_1$ aracılığıyla Geri Al: Emri İptal Et.
- $T_2$: Ödemeye Yetki Ver (Telafi Edilebilir) $\to$ $C_2$ ile Geri Al: Ödemeyi Geri Ödeme.
- $T_3$: Envanter Rezervi (Pivot İşlem) $\to$ Stok tahsisi başarılı olursa sipariş kesinleşir. Başarısız olursa $C_2$ ve $C_1$ tetikleyin.
- $T_4$: Gönderi İsteğini Gönderin (Yeniden Denenebilir) $\to$ Pivottan sonra yürütülür; teslim edilene kadar yeniden denendi.
Saga Mimari Tarzları: Koreografi ve Orkestrasyon
Saga modelini dağıtılmış sistemlerde uygulamaya yönelik iki temel mimari stil vardır: Koreografi (Merkezi Olmayan) ve Orkestrasyon (Merkezileştirilmiş).
Stil 1: Koreografi (Etkinlik Odaklı Merkezi Olmayanlaştırma)
Koreografiye dayalı bir destanda merkezi bir kontrolör veya koordinatör yoktur. Bunun yerine mikro hizmetler, merkezi bir olay veriyoluna (Apache Kafka, NATS veya RabbitMQ gibi) yayınlanan etki alanı olaylarını dinleyerek eşzamansız olarak iletişim kurar.
İş Akışı Mekaniği
- Sipariş Hizmeti $T_1$‘ı yürütür (bekleyen emir oluşturur) ve Kafka’ya bir
OrderCreatedolayı gönderir. - Ödeme Hizmeti
OrderCreatedöğesini dinler, $T_2$ işlemini gerçekleştirir (kredi kartından ödeme alır) ve birPaymentCompletedolayı (veyaPaymentFailed) gönderir. - Envanter Hizmeti
PaymentCompleted‘yi dinler, $T_3$‘ı yürütür (öğeleri ayırır). Öğeler stokta yoksaInventoryReservationFailedkodunu verir. - Ödeme Hizmeti,
InventoryReservationFaileddosyasını dinler ve $C_2$ işlemini gerçekleştirir (geri ödemeyi gerçekleştirir). - Sipariş Hizmeti
PaymentRefundeddosyasını dinler ve $C_1$ işlemini gerçekleştirir (siparişi iptal edildi olarak işaretler).
Koreografinin Avantajları
- Gevşek Bağlantı: Hizmetler yalnızca etkinlik konularına abone olur; diğer hizmet uygulamaları hakkında bilgi sahibi değiller.
- Yüksek Verim ve Merkezi Olmayanlaştırma: Doğrudan etkinlik pub/sub, merkezi orkestratör darboğazlarını ortadan kaldırır.
- Kısa İş Akışları için Basitlik: 2-3 adımlı basit iş akışları için kurulumu kolaydır.
Koreografinin Dezavantajları
- Döngüsel Bağımlılık Riski: Hizmetler birbirlerinin olaylarını dinleyerek karmaşık döngüsel bağımlılıklar oluşturabilir.
- Zor Kod İzlenebilirliği: Uçtan uca iş sürecini anlamak, birden fazla kod tabanı deposunda mantığın izlenmesini gerektirir.
- Olay Fırtınası ve Karmaşıklık: Adım sayısı arttıkça (ör. 10’dan fazla hizmet), uç hata durumlarının yönetilmesi olay durumunda patlamaya yol açar.
Stil 2: Düzenleme (Merkezi İş Akışı Yönetimi)
Orkestrasyon tabanlı bir Saga‘da, Saga Orchestrator olarak bilinen özel bir mikro hizmet, dağıtılmış işlemin tüm yaşam döngüsünü kontrol eder. Orkestratör, katılımcı mikro hizmetlere açık komutlar vererek ve yanıt olaylarını dinleyerek merkezi bir koordinatör görevi görür.
İş Akışı Mekaniği
- Müşteri Saga Orchestrator‘a bir sipariş isteği gönderir.
- Orkestratör Sipariş Hizmeti’ne bir
CreateOrderkomutu gönderir. Sipariş HizmetiOrderCreateddeğerini döndürüyor. - Orkestratör durum makinesini günceller ve Ödeme Hizmeti’ne bir
ProcessPaymentkomutu gönderir. Ödeme HizmetiPaymentSuccessfuldeğerini döndürüyor. - Orkestratör Envanter Hizmeti’ne bir
ReserveInventorykomutu gönderir. Envanter HizmetiInventoryFailed (Out of Stock)değerini döndürüyor. - Orkestratör arızayı tespit eder ve telafi akışını başlatır:
- Ödeme Hizmeti’ne
RefundPaymentkomutunu gönderir. - Sipariş Hizmeti’ne
CancelOrderkomutunu gönderir.
- Ödeme Hizmeti’ne
- Orkestratör, Saga yürütmesini
FAILEDolarak işaretler.
Düzenlemenin Avantajları
- Merkezi İş Mantığı: İş akışı durumu ve iş mantığı, tek bir orkestratör hizmetinde veya durum makinesinde yerelleştirilir.
- Döngüsel Bağımlılık Yok: Mikro hizmetler, orkestratörden gelen komutlara yanıt verir; diğer alt hizmetlere bağımlı değildirler veya bunlar hakkında bilgi sahibi değildirler.
- Net İzleme ve Hata Ayıklama: Uçtan uca işlem durumu, orkestratörün durum deposunda (ör. PostgreSQL veya Temporal / Camunda gibi iş akışı motorları) açıkça depolanır.
- Daha Kolay Hata İşleme: Yeni adımların eklenmesi veya geri alma kurallarının değiştirilmesi tamamen orkestratör içinden yönetilir.
Düzenlemenin Dezavantajları
- Orkestratör Karmaşıklığı: Orkestratöre çok fazla etki alanı mantığı yerleştirme riski, onu “akıllı orkestratör, aptal hizmet” anti-örüntüsüne dönüştürme riski.
- Potansiyel Tek Arıza Noktası: Orkestratörün yüksek düzeyde kullanılabilir ve durum bilgisi olan hale getirilmesi gerekir.
Karşılaştırmalı Matris: Koreografi ve Orkestrasyon
| Özellik | Koreografi | Orkestrasyon |
|---|---|---|
| Kontrol Yapısı | Merkezi Olmayan (Etkinlik Pub/Sub) | Merkezileştirilmiş (Saga Koordinatörü / Durum Makinesi) |
| Kaplin | Son Derece Düşük (Hizmetler olayları tüketir) | Orta (Hizmetler Koordinatörden gelen komutları kabul eder) |
| Süreç Görünürlüğü | Düşük (Günlük dosyalarına dağıtılır) | Yüksek (Tek durumlu mağaza iş akışını görselleştirir) |
| En Uygun Olanlar | Basit iş akışları (2 - 4 servis adımı) | Karmaşık kurumsal iş akışları (5+ adım, dallanma mantığı) |
| Araçlar / Çerçeveler | Kafka, RabbitMQ, NATS, AWS EventBridge | Temporal.io, AWS Adım İşlevleri, Camunda, Axon |
İzolasyon Zorlukları ve Karşı Tedbirler (“ASİT eksi I” ile Mücadele)
Bir Saga’daki yerel işlemler anında yerel veritabanlarına bağlandığından, Saga modelinde geleneksel ACID garantilerinden İzolasyon (I) yoktur.
Bir istemci, Saga hala çalışırken $T_1$ tarafından değiştirilmiş bir veritabanı satırını okursa, taahhüt edilmemiş, ara durum okuyordur. Aşağı yöndeki bir adım başarısız olursa ve telafiyi tetiklerse ($C_1$), istemci Kirli Okuma gerçekleştirmiştir.
İzolasyon Eksikliğinden Kaynaklanan Yaygın Anomaliler
- Kayıp Güncellemeler: Saga A bir kaydı günceller. Saga A tamamlanmadan önce Saga B aynı kaydın üzerine yazar. Saga A başarısız olursa ve telafiyi uygularsa Saga B’nin güncellemesinin üzerine yazar.
- Kirli Okumalar: Bir müşteri, Saga A ($T_1$) tarafından güncellenen mevcut hisse senedini okur. Saga A, satış yönünde başarısız oluyor ($T_3$) ve stoğu geri yüklüyor ($C_1$), ancak müşteri zaten eski verilere dayanarak bir sipariş vermiş.
- Tekrarlanamayan Okumalar: Bir hizmet, $T_1$ adımındaki verileri okur ve $T_3$ adımında tekrar okur, ancak eşzamanlı başka bir Saga, aradaki verileri değiştirdi.
Karşı Tedbirler ve Etki Azaltma Stratejileri
Yalıtım eksikliğine rağmen veri bütünlüğünü korumak için yazılım mimarları belirli yalıtım tasarım modellerini uygular:
1. Anlamsal Kilit (Beklemede / İşaretli Durum)
$T_1$ yerel işlemi bir veritabanı kaydını güncellediğinde, durum alanını PENDING veya APPROVAL_REQUIRED (ör. ORDER_PENDING_PAYMENT) olarak ayarlar.
Bu kaydı okuyan diğer eşzamanlı Saga’lar anlamsal kilit işaretini kontrol etmeli ve durum COMMITTED veya CANCELLED olarak değişene kadar davranışlarını engellemeli veya değiştirmelidir.
2. Siparişin Verilmesi
Yerel işlemlerin sırasını, yüksek riskli veya geri döndürülemez işlemlerin Saga yürütmesinin sonlarında gerçekleşeceği şekilde tasarlayın ve güvenlik açığı penceresini en aza indirin.
3. Yeniden Okuma Doğrulaması (İyimser Eşzamanlılık Kontrolü)
Kritik bir adımı veya telafiyi yürütmeden önce, hedef veritabanı kaydını yeniden okuyun ve eşzamanlı değişiklik yapılmadığından emin olmak için sürüm zaman damgalarını (version_id) doğrulayın.
4. Kötümser Bakış
Ekonomik riskleri en aza indirmek için bir Saga’nın adımlarını yeniden sıralayın (örneğin, ödeme yetkilendirmesini pivot işleme mümkün olduğunca yakın bir yere yerleştirin).
Üretim Sırası Akışı: Telafi İşlemleri
Aşağıda, ileri yürütme hatasını ve bunun sonucunda ortaya çıkan geri telafi yürütmesini gösteren tam sıra akış diyagramı bulunmaktadır:
Uygulamalı Kod Uygulamaları
Hem Koreografi (Go‘da) hem de Düzenleme (Java Spring Boot‘da) için üretime hazır uygulama örneklerini inceleyelim.
Uygulama 1: Go’da Koreografiye Dayalı Destan
Bu Go örneğinde, telafi edici geri alma mantığını yürütmek için bir olay komisyoncusu üzerinden sipariş oluşturmayı ve ödeme hatası olaylarını dinleyen bir Sipariş Hizmeti gösteriyoruz.
package saga
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
)
// Event definitions
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
CustomerID string `json:"customer_id"`
Amount float64 `json:"amount"`
}
type PaymentFailedEvent struct {
OrderID string `json:"order_id"`
Reason string `json:"reason"`
}
// OrderRepository handles local DB operations
type OrderRepository interface {
CreateOrder(ctx context.Context, orderID string, amount float64) error
UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}
// EventBus abstraction for message broker (e.g., Kafka / NATS)
type EventBus interface {
Publish(topic string, payload []byte) error
Subscribe(topic string, handler func(payload []byte)) error
}
type OrderSagaChoreographer struct {
repo OrderRepository
eventBus EventBus
}
func NewOrderSagaChoreographer(repo OrderRepository, bus EventBus) *OrderSagaChoreographer {
c := &OrderSagaChoreographer{repo: repo, eventBus: bus}
c.registerSubscriptions()
return c
}
// Step 1: Forward Transaction (T1)
func (s *OrderSagaChoreographer) StartOrderSaga(ctx context.Context, orderID, customerID string, amount float64) error {
// Execute local database transaction
err := s.repo.CreateOrder(ctx, orderID, amount)
if err != nil {
return fmt.Errorf("failed local DB transaction T1: %w", err)
}
// Emit domain event for downstream Payment Service
event := OrderCreatedEvent{OrderID: orderID, CustomerID: customerID, Amount: amount}
bytes, _ := json.Marshal(event)
log.Printf("[SAGA][T1] Order %s created. Publishing OrderCreatedEvent...", orderID)
return s.eventBus.Publish("orders.created", bytes)
}
// Register subscription for compensating events
func (s *OrderSagaChoreographer) registerSubscriptions() {
_ = s.eventBus.Subscribe("payments.failed", func(payload []byte) {
var event PaymentFailedEvent
if err := json.Unmarshal(payload, &event); err != nil {
log.Printf("[ERROR] Corrupt payment event: %v", err)
return
}
// Execute Compensating Transaction (C1)
s.handlePaymentFailed(context.Background(), event)
})
}
// Step C1: Compensating Transaction
func (s *OrderSagaChoreographer) handlePaymentFailed(ctx context.Context, event PaymentFailedEvent) {
log.Printf("[SAGA][C1] Payment failed for Order %s (Reason: %s). Rolling back local order...", event.OrderID, event.Reason)
// Revert order status to CANCELLED in local DB
err := s.repo.UpdateOrderStatus(ctx, event.OrderID, "CANCELLED_PAYMENT_FAILED")
if err != nil {
log.Printf("[CRITICAL] Failed to execute compensating transaction C1 for Order %s: %v", event.OrderID, err)
// Trigger alert or write to Dead Letter Queue (DLQ)
return
}
log.Printf("[SAGA][SUCCESS] Order %s successfully compensated and cancelled.", event.OrderID)
}
Uygulama 2: Java’da Düzenleme Tabanlı Efsane (Spring Boot)
Bu Java örneğinde, ileri komutları koordine etmek ve aşağı yönde bir adım başarısız olduğunda telafi edici geri alma işlemlerini yürütmek için durum makinesi modelini kullanarak bir Saga Orchestrator oluşturuyoruz.
package com.ghaznix.saga.orchestrator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.UUID;
public enum SagaState {
STARTED,
ORDER_CREATED,
PAYMENT_PROCESSED,
INVENTORY_RESERVED,
COMPLETED,
COMPENSATING_PAYMENT,
COMPENSATING_ORDER,
FAILED
}
@Service
public class OrderSagaOrchestrator {
private static final Logger log = LoggerFactory.getLogger(OrderSagaOrchestrator.class);
private final OrderServiceClient orderClient;
private final PaymentServiceClient paymentClient;
private final InventoryServiceClient inventoryClient;
public OrderSagaOrchestrator(OrderServiceClient orderClient,
PaymentServiceClient paymentClient,
InventoryServiceClient inventoryClient) {
this.orderClient = orderClient;
this.paymentClient = paymentClient;
this.inventoryClient = inventoryClient;
}
public boolean executeOrderSaga(String customerId, String productId, double amount, int quantity) {
String sagaId = UUID.randomUUID().toString();
log.info("[SAGA {}] Starting Order Saga Workflow...", sagaId);
SagaState currentState = SagaState.STARTED;
String orderId = null;
String paymentId = null;
try {
// Step 1: Forward Local Tx T1 - Create Order
log.info("[SAGA {}][T1] Sending CreateOrder command...", sagaId);
orderId = orderClient.createOrder(customerId, productId, amount);
currentState = SagaState.ORDER_CREATED;
// Step 2: Forward Local Tx T2 - Process Payment
log.info("[SAGA {}][T2] Sending ProcessPayment command for Order {}...", sagaId, orderId);
paymentId = paymentClient.chargePayment(orderId, customerId, amount);
currentState = SagaState.PAYMENT_PROCESSED;
// Step 3: Forward Local Tx T3 (Pivot Step) - Reserve Inventory
log.info("[SAGA {}][T3] Sending ReserveInventory command for Product {}...", sagaId, productId);
boolean stockReserved = inventoryClient.reserveStock(productId, quantity);
if (!stockReserved) {
throw new InventoryAllocationException("Stock allocation failed: Item out of stock.");
}
currentState = SagaState.INVENTORY_RESERVED;
log.info("[SAGA {}][SUCCESS] Saga completed successfully!", sagaId);
return true;
} catch (Exception ex) {
log.error("[SAGA {}][FAILURE] Step failed during state {}. Triggering Compensation...", sagaId, currentState, ex);
rollbackSaga(sagaId, currentState, orderId, paymentId);
return false;
}
}
private void rollbackSaga(String sagaId, SagaState failedState, String orderId, String paymentId) {
log.info("[SAGA {}] Initiating backward compensating transactions from state: {}", sagaId, failedState);
// Compensate Step 2 if payment was processed
if (failedState == SagaState.PAYMENT_PROCESSED || failedState == SagaState.INVENTORY_RESERVED) {
try {
log.info("[SAGA {}][C2] Executing Payment Refund compensation for Payment {}...", sagaId, paymentId);
paymentClient.refundPayment(paymentId);
} catch (Exception e) {
log.error("[CRITICAL][SAGA {}] Payment refund C2 failed! Manual intervention or DLQ required.", sagaId, e);
}
}
// Compensate Step 1 if order was created
if (failedState != SagaState.STARTED && orderId != null) {
try {
log.info("[SAGA {}][C1] Executing Order Cancel compensation for Order {}...", sagaId, orderId);
orderClient.cancelOrder(orderId);
} catch (Exception e) {
log.error("[CRITICAL][SAGA {}] Order cancellation C1 failed!", sagaId, e);
}
}
log.info("[SAGA {}] Saga compensation workflow finished. Final State: FAILED.", sagaId);
}
}
Prodüksiyon Esasları: Saga’yı İşlemsel Giden Kutusu Modeli ile Eşleştirme
Hem Koreografide hem de Orkestrasyonda, yerel bir işlemin ($T_i$) yürütülmesi, ağ üzerinden bir etki alanı olayının veya komutunun yayınlanmasını gerektirir.
Hizmetiniz SQL veritabanını günceller ve ardından Kafka’ya bir mesaj yayınlarsa, veritabanı işleme sonrasında meydana gelen bir ağ arızası sessiz olay kaybına neden olur. Bunun tersine, mesajın veritabanına kaydedilmeden önce yayınlanması hayalet olay işlemeye yol açar.
Bunu çözmek için Sagas’ın İşlemsel Giden Kutusu Modeli ile eşleştirilmesi gerekir:
[Service Database Transaction Boundary]
+-----------------------------------------------------+
| 1. INSERT INTO business_table (orders/payments) |
| 2. INSERT INTO outbox_table (event_payload) |
+-----------------------------------------------------+
|
(CDC / Polling Message Relay)
|
v
[Message Broker / Kafka]
Olay yükünün aynı yerel veritabanı işlemi içindeki bir outbox tablosunda sürdürülmesiyle atomiklik garanti edilir. Eşzamansız bir arka plan işlemi (Debezium veya yoklama rölesi gibi) giden kutusu tablosundan okur ve olayları Kafka’ya güvenilir bir şekilde yayınlar.
Ayrıca, her alt hizmet tüketicisi Idempotency (benzersiz idempotency_key veya mesaj tekilleştirme başlıklarını kullanarak) uygulamalıdır, böylece yeniden denemeler sırasında yinelenen ileti teslimleri, yinelenen ücretleri veya envanter tahsislerini tetiklemez.
Mimari Karar Kontrol Listesi
Mikro hizmet uygulamaları için dağıtılmış işlemleri tasarlarken bu pratik karar matrisini kullanın:
Do you need cross-service data consistency?
|
+----------------+----------------+
| No | Yes
v v
Standard Single Service Can you accept Eventual
Local Database Consistency (BASE)?
|
+----------------+----------------+
| No | Yes
v v
Use Monolithic Core Adopt Saga Pattern
with Single ACID DB |
|
How complex is the workflow?
|
+-----------------+-----------------+
| Simple (2-3 steps) | Complex (4+ steps/branches)
v v
Choreography Saga Orchestration Saga
(Event-Driven Bus) (Temporal/Custom State Machine)
Sonuç ve Temel Çıkarımlar
Saga Modeli, kaynakları kilitlemeden veya sistem kullanılabilirliğinden ödün vermeden, mikro hizmet sınırları boyunca dağıtılmış işlemleri yönetmek için gerekli bir mimari modeldir.
Özet Kontrol Listesi:
- Bulutta Yerel Mikro Hizmetlerde 2PC/XA’dan vazgeçin: İki Aşamalı Taahhüt, sıkı kilitlemeye, yüksek gecikme süresine ve ciddi kullanılabilirlik darboğazlarına neden olur.
- İşlemleri Yerel Adımlara Bölün: Küresel işlemleri, tersten telafi edici işlemlerle ($C_1 \dots C_{n-1}$) eşleştirilmiş yerel işlemlere ($T_1 \dots T_n$) bölün.
- Doğru Mimari Stili Seçin:
- Gevşek bağlantıya sahip basit, 2-3 adımlı, olaya dayalı akışlar için Koreografi‘yi kullanın.
- Merkezi görünürlük, dallara ayırma ve durum makinesi takibi gerektiren karmaşık iş akışları için Orkestrasyon‘u kullanın.
- İzolasyona Karşı Önlemleri Uygulayın: Anlamsal Kilitler (
PENDINGbayrakları) ve Yeniden Okuma İyimser Kilitleme’yi kullanarak kirli okumalara ve kaybolan güncellemelere karşı koruma sağlayın. - Güvenilir Mesajlaşmayı Garanti Edin: Saga uygulamalarını her zaman İşlemsel Giden Kutusu Modeli ile eşleştirin ve Idempotent Tüketicilerin yeniden denemeleri güvenli bir şekilde gerçekleştirmesini zorunlu kılın.
Saga modelini dikkatli bir şekilde uygulayarak, ağlar başarısız olduğunda bile dayanıklı ve tutarlı kalan, yüksek düzeyde kullanılabilir, ölçeklenebilir mikro hizmetler oluşturabilirsiniz.