Maison >Java >javaDidacticiel >Opérations transactionnelles sur plusieurs services. Une méthode vers la folie.

Opérations transactionnelles sur plusieurs services. Une méthode vers la folie.

Mary-Kate Olsen
Mary-Kate Olsenoriginal
2024-11-17 00:21:03781parcourir

Transactional Operations Across Multiple Services. A Method To The Madness.

L'une des nombreuses complexités auxquelles une équipe doit faire face dans un environnement de microservices concerne les transactions. Transactions qui couvrent plusieurs microservices. Contrairement aux applications monolithiques, où les transactions sont généralement gérées avec une seule base de données et le @Transactional
annotation, dans les microservices, chaque service possède souvent sa propre base de données, ce qui rend les transactions distribuées plus complexes. Voici un guide sur la façon de gérer efficacement ces transactions distribuées dans Spring Boot.

Tout d'abord, commençons par nous mettre d'accord sur ce qu'est une transaction.

Une transaction est une unité de travail dans un environnement informatique ou de base de données qui est traitée comme une opération unique et indivisible. Il représente une série d'actions ou d'étapes qui doivent toutes réussir ou échouer ensemble, garantissant la cohérence et l'intégrité des données, même en cas d'événements inattendus (comme une panne de courant ou une panne de réseau).

Dans un contexte de base de données, une transaction peut impliquer plusieurs requêtes, telles que la création, la mise à jour ou la suppression d'enregistrements. Une transaction suit généralement quatre propriétés essentielles, dites propriétés ACID :

a. Atomicité- Toutes les opérations au sein d'une transaction sont traitées comme une seule unité. Soit toutes les opérations réussissent, soit aucune ne réussit.

b. Cohérence - Une transaction amène le système d'un état valide à un autre, maintenant la validité des données.

c. Isolement - Les transactions sont exécutées de manière isolée, ce qui signifie que les états intermédiaires ne sont pas visibles pour les autres transactions.

d. Durabilité - Une fois qu'une transaction est validée, ses modifications sont permanentes et survivront aux pannes du système.


Une histoire courte

Dans une application de commerce électronique très animée, imaginez une cliente, Alice, passant une commande pour un nouvel ordinateur portable, un accessoire et une livraison express. Voici l'histoire en coulisses de la façon dont sa commande circule dans le système, géré par OrderSagaOrchestrator.

Dans une application de commerce électronique très animée, imaginez une cliente, Alice, passant une commande pour un nouvel ordinateur portable, un accessoire et une livraison express. Voici l'histoire en coulisses de la façon dont sa commande circule dans le système, géré par OrderSagaOrchestrator.

Alice clique sur « Commander maintenant » après avoir saisi ses informations de paiement et d'expédition. Cette action déclenche un processus appelé saga, une série de transactions soigneusement orchestrées pour garantir que sa commande est traitée correctement du début à la fin.

Étape 1 : Traitement du paiement
L'orchestrateur de la saga vérifie d'abord auprès du PaymentService, lançant un appel pour déduire le montant requis du compte d'Alice. La méthode paymentService.processPayment() est appelée et le paiement d'Alice est autorisé.

Étape 2 : Réservation d'inventaire
Une fois le paiement réussi, l'orchestrateur se déplace vers InventoryService, où il réserve le modèle d'ordinateur portable et l'accessoire spécifiques pour Alice. Cette étape de réservation est cruciale pour que le stock ne soit pas épuisé ou donné à une autre cliente alors que sa commande est encore en cours de traitement.

Étape 3 : Initiation de l'expédition
Une fois l’inventaire réservé avec succès, l’orchestrateur de la saga contacte ShippingService. Ici, shippingService.initiateShipping() démarre la logistique, en s'assurant que les articles sont emballés et prêts à être expédiés à l'adresse d'Alice.

Gestion des échecs : logique de compensation

Mais dans un environnement distribué, les choses peuvent mal tourner à tout moment. Que se passe-t-il si le lancement de l'expédition échoue en raison d'une erreur logistique, ou si le stock ne peut pas être honoré en raison d'un écart de stock ? L'orchestrateur est préparé avec une stratégie de rémunération.

Si une exception est levée, l'orchestrateur lance des transactions compensatoires pour annuler l'ensemble du processus, de sorte qu'Alice ne soit pas facturée pour les éléments qu'elle ne recevra pas :

3.1. Annuler l'expédition - L'orchestrateur appelle shippingService.cancelShipping(), arrêtant l'expédition.

3.2. Libérer l'inventaire - Il déclenche ensuite inventorService.releaseInventory(), libérant les articles réservés d'Alice afin que d'autres clients puissent les acheter.

3.3. Remboursement du paiement - Enfin, il appelle paymentService.refund() pour rembourser le paiement d'Alice, garantissant ainsi qu'elle n'est pas facturée pour la commande.

En fin de compte, cette saga orchestrée garantit que l’expérience d’Alice est fluide et cohérente, et si des problèmes surviennent, ils sont résolus de manière à maintenir l’intégrité du système. C'est la magie des transactions distribuées et de la logique de compensation dans les microservices.


Donc, maintenant que nous savons ce qu'est une transaction et comprenons un scénario réel dans lequel cela pourrait être utile, voyons comment faire fonctionner cela dans un environnement distribué.

Il existe des approches clés que les équipes utilisent pour résoudre ce problème

1. Modèle SAGA : L'un des modèles les plus largement utilisés pour gérer les transactions distribuées dans une architecture de microservices est le modèle Saga. Une saga est une séquence de transactions locales que chaque service exécute indépendamment. Chaque étape d'une saga est compensée par une action qui l'annule si la saga échoue.

Le modèle Saga peut être implémenté de deux manières principales :

  1. a. SAGA basée sur la chorégraphie : Chaque service impliqué dans la transaction écoute les événements et effectue sa transaction. Une fois terminé, il émet un événement qui déclenche la prochaine étape de la saga. Si une étape échoue, un événement compensatoire est déclenché pour annuler les étapes précédentes.

  2. b. SAGA basée sur l'orchestration : Un service centralisé (l'orchestrateur de la saga) coordonne les étapes de la saga. Il détermine l'ordre des opérations et gère les compensations en cas de panne.

2. Commit en deux phases (2PC) : Bien qu'il soit couramment utilisé dans les systèmes monolithiques, le protocole de validation en deux phases peut être utilisé sur des systèmes distribués avec un gestionnaire de transactions distribué comme Atomikos ou Bitronix. Cependant, je ne recommanderais pas cette approche car elle présente certaines limites dans le contexte des microservices car elle entraîne une latence élevée et est moins tolérante aux pannes. J'éviterais généralement cette approche au profit du modèle Saga si j'étais vous.

3. Architecture basée sur les événements : L'utilisation d'une approche basée sur les événements, où les services communiquent via des événements, est particulièrement adaptée à la gestion des transactions distribuées. Cette approche s'aligne bien avec le modèle Saga. Chaque service effectue sa transaction indépendamment, puis émet un événement pour informer les autres services du résultat. Ces événements peuvent être gérés à l'aide d'Apache Kafka, RabbitMQ ou d'autres courtiers de messages.

Maintenant, voyons comment cela fonctionne dans le code.

Il existe plusieurs versions du modèle Saga, mais dans cet article, je vais tenter d'implémenter un modèle Saga basé sur l'orchestration dans Spring Boot :

ÉTAPE 1 : Définir un orchestrateur de saga :
Ici, je vais créer un service simple pour agir en tant qu'orchestrateur, responsable de la coordination des transactions.

Ce service définira le déroulement de la saga, appelant chaque service dans le bon ordre et gérant les transactions compensatoires si nécessaire.

@Service
public class OrderSagaOrchestrator {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private ShippingService shippingService;

    public void createOrderSaga(Order order) {
        try {
            paymentService.processPayment(order.getPaymentDetails());
            inventoryService.reserveInventory(order.getItems());
            shippingService.initiateShipping(order.getShippingDetails());
        } catch (Exception e) {
            // Compensation logic
            shippingService.cancelShipping(order.getShippingId());
            inventoryService.releaseInventory(order.getItems());
            paymentService.refund(order.getPaymentId());
        }
    }
}

ÉTAPE 2 : Création de transactions locales et de méthodes de compensation dans chaque service :

Chaque service devrait avoir sa transaction pour terminer son étape dans la saga et une autre pour la compenser si besoin. Voici la structure générale de ce à quoi cela pourrait ressembler.

@Service
public class PaymentService {

    @Transactional
    public void processPayment(PaymentDetails details) {
        // Perform payment logic
    }

    @Transactional
    public void refund(String paymentId) {
        // Perform refund logic
    }
}

ÉTAPE 3 : Communication basée sur les événements (Facultatif, pour la chorégraphie) : Chaque service peut émettre des événements pour informer les autres du résultat de la transaction.

public class PaymentService {

    private final ApplicationEventPublisher eventPublisher;

    public PaymentService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void processPayment(PaymentDetails details) {
        // Process payment
        eventPublisher.publishEvent(new PaymentProcessedEvent(this, details));
    }
}

ÉTAPE 4 : Prendre des mesures pour garantir la cohérence des données : Utilisez des contrôles d'idempotence pour vous assurer que chaque étape de la saga n'est exécutée qu'une seule fois. Ceci est important dans les systèmes distribués, où les pannes de réseau ou les tentatives peuvent entraîner des demandes en double.

ÉTAPE 5 : Utilisez un courtier de messages pour plus de fiabilité : Si vous utilisez des événements pour gérer la saga, un courtier de messages comme Kafka de RabbitMq fournit durabilité et peut mettre en mémoire tampon les événements si un service est temporairement indisponible.

ÉTAPE 6 : Gestion des erreurs et tentatives : Intégrez la gestion des erreurs et la logique de nouvelle tentative dans votre orchestrateur et vos services individuels pour gérer les échecs temporaires. Spring Retry est utile ici, car il peut réessayer automatiquement les opérations ayant échoué dans le cadre d'une politique configurable.

@Service
public class OrderSagaOrchestrator {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private ShippingService shippingService;

    public void createOrderSaga(Order order) {
        try {
            paymentService.processPayment(order.getPaymentDetails());
            inventoryService.reserveInventory(order.getItems());
            shippingService.initiateShipping(order.getShippingDetails());
        } catch (Exception e) {
            // Compensation logic
            shippingService.cancelShipping(order.getShippingId());
            inventoryService.releaseInventory(order.getItems());
            paymentService.refund(order.getPaymentId());
        }
    }
}

Conclusion

Les transactions distribuées dans les microservices sont un défi, mais en utilisant des modèles comme Saga (en particulier avec l'orchestration) et une communication basée sur les événements, vous pouvez obtenir des solutions fiables et évolutives.

Spring Boot facilite cela en offrant une prise en charge de la gestion transactionnelle, de la publication d'événements et de l'intégration avec les courtiers de messages.

En fin de compte, cette saga orchestrée garantit que l’expérience d’Alice est fluide et cohérente, et si des problèmes surviennent, ils sont résolus de manière à maintenir l’intégrité du système. C'est la magie des transactions distribuées et de la logique de compensation dans les microservices.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn