قواعد البيانات والخلفية البرمجية 3 دقائق للقراءة

حل مشكلات الاتساق في الخدمات المصغرة باستخدام نمط Saga: دليل مفصل وأمثلة تطبيقية

PROFSCODE Ekibi 15 يوليو 2026
Paylaş:
تطبيق نمط Saga للمعاملات الموزعة في بنية الخدمات المصغرة

بينما تجعل الخدمات المصغرة في معماريات البرمجيات الحديثة التطبيقات أكثر مرونة وقابلية للتوسع واستقلالية، فإنها تجلب أيضًا بعض التحديات. ربما يكون أهم هذه التحديات هو ضمان اتساق البيانات في العمليات التجارية التي تمتد عبر خدمات متعددة. المعاملات الموزعة التقليدية المتوافقة مع ACID (الذرية، الاتساق، العزل، الديمومة) (مثل Two-Phase Commit - 2PC) غالبًا ما تكون غير ممكنة في بيئة الخدمات المصغرة لأن الخدمات لديها قواعد بيانات مستقلة ويجب أن تكون مرتبطة بشكل غير محكم.

هنا يأتي دور نمط Saga. نمط Saga هو نمط تصميم يهدف إلى الحفاظ على اتساق البيانات في نظام موزع. بدلاً من معاملة ذرية واحدة، يتكون من سلسلة من المعاملات المحلية، حيث تقوم كل معاملة بتحديث البيانات داخل خدمتها الخاصة ثم تقوم بتشغيل حدث للانتقال إلى الخطوة التالية. إذا فشلت خطوة داخل Saga، يتم بدء معاملات التعويض لإلغاء آثار الخطوات الناجحة السابقة.

ما هو نمط Saga؟

يتكون نمط Saga من تسلسل من المعاملات المحلية لإدارة عملية تجارية طويلة الأمد. تعمل كل معاملة محلية ضمن قاعدة بيانات خدمتها الخاصة، وعند الانتهاء بنجاح، تنشر حدثًا لتشغيل المعاملة المحلية التالية. إذا فشلت خطوة داخل Saga، يتم تشغيل معاملات التعويض لإعادة آثار الخطوات الناجحة السابقة.

مناهج تنفيذ Saga: التنظيم (Orchestration) والتنسيق (Choreography)

هناك منهجان رئيسيان لتطبيق نمط Saga:

  • التنظيم (Orchestration): يقوم منسق Saga مركزي بإدارة جميع خطوات Saga، وتحديد الخدمة التي تقوم بأي عملية ومتى.
  • التنسيق (Choreography): تساهم كل خدمة في تدفق Saga من خلال الاستماع إلى الأحداث ذات الصلة ونشر أحداث جديدة بعد إكمال معاملتها المحلية. لا يوجد منسق مركزي.

1. نمط Saga القائم على التنظيم

في النهج القائم على التنظيم، يوجد "منسق Saga" مركزي يعرف جميع خطوات Saga ويدير تسلسلها. يرسل المنسق أوامر إلى كل خدمة ويستمع إلى الأحداث من الخدمات للانتقال إلى الخطوة التالية. في حالة وجود خطأ، يقوم المنسق أيضًا بتشغيل معاملات التعويض.

المزايا: تدفق العمل أوضح، إدارة Saga المعقدة أسهل، معالجة الأخطاء مركزية.

العيوب: يمكن أن يكون المنسق نقطة فشل واحدة (Single Point of Failure)، ويمكن أن يخلق ارتباطًا أوثق بين الخدمات.

مثال: منسق عملية الطلب (شفرة Java شبه-كودية)

// خدمة منسق الطلب@Servicepublic class OrderSagaOrchestrator {    @Autowired    private KafkaTemplate<String, Object> kafkaTemplate; // أو نظام مراسلة آخر    public void createOrderSaga(OrderDto orderDto) {        // 1. إرسال أمر إنشاء الطلب        kafkaTemplate.send("order-commands", new CreateOrderCommand(orderDto));        // يتوقع المنسق 'OrderCreatedEvent' من خدمة الطلب.    }    @KafkaListener(topics = "order-events", groupId = "order-saga-group")    public void handleOrderEvents(OrderEvent event) {        if (event instanceof OrderCreatedEvent) {            // 2. إرسال أمر معالجة الدفع            kafkaTemplate.send("payment-commands", new ProcessPaymentCommand(((OrderCreatedEvent) event).getOrderId(), event.getAmount()));        } else if (event instanceof PaymentProcessedEvent) {            // 3. إرسال أمر تقليل المخزون            kafkaTemplate.send("inventory-commands", new ReduceStockCommand(((PaymentProcessedEvent) event).getOrderId(), event.getProductId(), event.getQuantity()));        } else if (event instanceof PaymentFailedEvent) {            // فشل الدفع، طلب تعويض من خدمة الطلب            kafkaTemplate.send("order-commands", new RejectOrderCommand(((PaymentFailedEvent) event).getOrderId()));        } else if (event instanceof StockReducedEvent) {            // جميع الخطوات ناجحة، اكتمل الطلب            System.out.println("تم إكمال الطلب " + ((StockReducedEvent) event).getOrderId() + " بنجاح.");        } else if (event instanceof StockReductionFailedEvent) {            // فشل تقليل المخزون، طلب تعويض من خدمات الدفع والطلب            kafkaTemplate.send("payment-commands", new RefundPaymentCommand(((StockReductionFailedEvent) event).getOrderId()));            kafkaTemplate.send("order-commands", new RejectOrderCommand(((StockReductionFailedEvent) event).getOrderId()));        }    }}

2. نمط Saga القائم على التنسيق

في النهج القائم على التنسيق، لا يوجد منسق مركزي. تستمع كل خدمة إلى الأحداث المنشورة بواسطة الخدمات الأخرى وتنشر أحداثًا جديدة بعد إكمال معاملتها المحلية. هذا يضمن ترابطًا فضفاضًا، حيث تمتلك الخدمات معرفة أقل عن بعضها البعض.

المزايا: ترابط فضفاض، لا توجد نقطة فشل واحدة.

العيوب: من الصعب تتبع تدفق العمل (خاصة في Saga المعقدة)، يتطلب تخطيطًا دقيقًا لتشغيل معاملات التعويض بشكل صحيح.

مثال: تنسيق عملية الطلب (شفرة Java شبه-كودية)

// خدمة الطلب@Servicepublic class OrderService {    @Autowired    private KafkaTemplate<String, Object> kafkaTemplate;    public Order createOrder(OrderDto orderDto) {        // ... إنشاء الطلب وحفظه في قاعدة البيانات ...        Order newOrder = new Order(orderDto.getProductId(), orderDto.getQuantity(), orderDto.getAmount(), OrderStatus.PENDING);        // نشر الحدث        kafkaTemplate.send("order-events", new OrderCreatedEvent(newOrder.getId(), newOrder.getAmount(), newOrder.getProductId(), newOrder.getQuantity()));        return newOrder;    }    @KafkaListener(topics = "payment-events", groupId = "order-service-group")    public void handlePaymentEvents(PaymentEvent event) {        if (event instanceof PaymentProcessedEvent) {            // الدفع ناجح، تحديث حالة الطلب            Order order = findOrderById(((PaymentProcessedEvent) event).getOrderId());            order.setStatus(OrderStatus.PAID);            // ... الحفظ في قاعدة البيانات ...        } else if (event instanceof PaymentFailedEvent) {            // فشل الدفع، رفض حالة الطلب            Order order = findOrderById(((PaymentFailedEvent) event).getOrderId());            order.setStatus(OrderStatus.REJECTED);            // ... الحفظ في قاعدة البيانات ...        }    }    // ... طرق أخرى (findOrderById إلخ) ...}// خدمة الدفع@Servicepublic class PaymentService {    @Autowired    private KafkaTemplate<String, Object> kafkaTemplate;    @KafkaListener(topics = "order-events", groupId = "payment-service-group")    public void handleOrderCreatedEvent(OrderCreatedEvent event) {        try {            // ... معالجة الدفع ...            if (processPayment(event.getOrderId(), event.getAmount())) {                kafkaTemplate.send("payment-events", new PaymentProcessedEvent(event.getOrderId(), event.getAmount()));            } else {                kafkaTemplate.send("payment-events", new PaymentFailedEvent(event.getOrderId(), "فشل الدفع"));            }        } catch (Exception e) {            kafkaTemplate.send("payment-events", new PaymentFailedEvent(event.getOrderId(), "خطأ في معالجة الدفع: " + e.getMessage()));        }    }    @KafkaListener(topics = "inventory-events", groupId = "payment-service-group")    public void handleInventoryEvents(InventoryEvent event) {        if (event instanceof StockReductionFailedEvent) {            // إذا فشل تقليل المخزون، استرداد الدفع (تعويض)            refundPayment(((StockReductionFailedEvent) event).getOrderId());            kafkaTemplate.send("payment-events", new PaymentRefundedEvent(((StockReductionFailedEvent) event).getOrderId()));        }    }    private boolean processPayment(String orderId, double amount) {        // ... التكامل مع بوابة الدفع ...        return Math.random() > 0.1; // محاكاة نسبة نجاح 90%    }    private void refundPayment(String orderId) {        // ... عمليات استرداد الدفع ...        System.out.println("تم استرداد الدفع للطلب " + orderId + ".");    }    // ... طرق أخرى ...}// خدمة المخزون@Servicepublic class InventoryService {    @Autowired    private KafkaTemplate<String, Object> kafkaTemplate;    @KafkaListener(topics = "payment-events", groupId = "inventory-service-group")    public void handlePaymentProcessedEvent(PaymentProcessedEvent event) {        try {            // ... إجراء تقليل المخزون ...            if (reduceStock(event.getProductId(), event.getQuantity())) {                kafkaTemplate.send("inventory-events", new StockReducedEvent(event.getOrderId(), event.getProductId(), event.getQuantity()));            } else {                kafkaTemplate.send("inventory-events", new StockReductionFailedEvent(event.getOrderId(), "مخزون غير كافٍ"));            }        } catch (Exception e) {            kafkaTemplate.send("inventory-events", new StockReductionFailedEvent(event.getOrderId(), "خطأ في معالجة المخزون: " + e.getMessage()));        }    }    private boolean reduceStock(String productId, int quantity) {        // ... فحص المخزون وتقليله ...        // محاكاة: ليس هناك دائمًا مخزون كافٍ        return Math.random() > 0.05; // محاكاة نسبة نجاح 95%    }    private void increaseStock(String productId, int quantity) {        // ... إجراء التعويض: زيادة المخزون ...        System.out.println("تم زيادة المخزون للمنتج " + productId + " بمقدار " + quantity + " (تعويض).");    }    // ... طرق أخرى ...}

متى يجب استخدام نمط Saga؟

نمط Saga مفيد بشكل خاص في السيناريوهات التالية:

  • العمليات التجارية المعقدة التي تمتد عبر خدمات مصغرة متعددة.
  • البيئات الموزعة حيث لا يمكن توسيع ضمانات ACID عبر النظام بأكمله.
  • الحالات التي يكون فيها استمرارية المعاملات واتساقها مقبولين حتى مع نموذج الاتساق النهائي (eventual consistency).

اعتبارات وتحديات

  • الاتساق النهائي (Eventual Consistency): مع نمط Saga، يصبح نظامك متسقًا في النهاية. هذا يعني أن البيانات قد تبدو غير متسقة لفترة قصيرة في منتصف العملية التجارية. يجب أن تكون تطبيقاتك وواجهات المستخدم متسامحة مع هذا الوضع.
  • معاملات التعويض (Compensating Transactions): يعد تصميم آلية تعويض لكل معاملة محلية ناجحة أمرًا بالغ الأهمية. تضمن هذه المعاملات أن يعود النظام إلى حالة سابقة (أو متسقة) في حالة حدوث خطأ. من المهم أن تكون معاملات التعويض أيضًا "idempotent" (قابلة للتشغيل بشكل متكرر).
  • قابلية المراقبة وتصحيح الأخطاء: قد يكون تتبع تدفقات Saga وتصحيح الأخطاء أمرًا صعبًا لأنها تمتد عبر خدمات متعددة. يساعد التسجيل المركزي، والتتبع الموزع (مثل Zipkin و Jaeger)، وتدقيق الأحداث في هذا الصدد.
  • "Idempotency": من المهم التأكد من أن خدماتك تنتج نفس النتيجة (أن تكون "idempotent") حتى لو تمت معالجة الأحداث عدة مرات.

الخلاصة

الطبيعة الموزعة التي أدخلتها معماريات الخدمات المصغرة تجعل مناهج إدارة المعاملات التقليدية غير كافية. يقدم نمط Saga حلاً قويًا ومرنًا لإدارة منطق الأعمال بشكل متسق عبر خدمات متعددة. من خلال الفهم الشامل لمنهجي التنظيم والتنسيق، يمكنك اختيار الأنسب لمتطلبات تطبيقك وضمان اتساق البيانات بنجاح في أنظمتك الموزعة. تذكر أن آليات التعويض المصممة جيدًا وقابلية المراقبة القوية هي أحجار الزاوية لتنفيذ Saga ناجح.

العودة إلى المدونة

التعليقات (0)

لا توجد تعليقات بعد. كن أول من يعلق!

إرسال التعليق