5

J'utilise Spring Cloud pour implémenter mon système de micro services, une plateforme de vente de tickets. Le scénario est, il y a un proxy zuul, un registre eureka, et 3 service: service utilisateur, service de commande et service de billetterie. Les services utilisent un client REST déclaratif feignant pour communiquer entre eux.Comment implémenter une transaction distribuée avec hystrix fallback basée sur l'architecte Spring Cloud

Maintenant, il y a une fonction pour acheter des billets, le processus principal est comme suit: 1.
Service de commande accepte la demande de créer l'ordre
2. Service de commande créer entité ordres à l'état en attente.
3. de demander au service utilisateur de l'appel de service de traiter la paie de l'utilisateur.
4. de commander un service de ticket d'appel de service pour mettre à jour les tickets d'utilisateur.
5. service de mise à jour mettre à jour l'entité de commande comme FINI.

Et je veux utiliser Hystrix Fallback pour mettre en œuvre la transaction. Par exemple, si le processus de paiement est terminé, mais qu'une erreur s'est produite lors du déplacement du ticket. Comment vérifier le paiement de l'utilisateur et le statut de la commande. Parce que le paiement de l'utilisateur est dans un autre service.

Ce qui suit est ma solution actuelle, je ne suis pas sûr que ce soit approprié. Ou y a-t-il une autre meilleure façon de le faire?

Au début, le OrderResource:

@RestController 
@RequestMapping("/api/order") 
public class OrderResource { 

    @HystrixCommand(fallbackMethod = "createFallback") 
    @PostMapping(value = "/") 
    public Order create(@RequestBody Order order) { 
    return orderService.create(order); 
    } 

    private Order createFallback(Order order) { 
    return orderService.createFallback(order); 
    } 
} 

Puis le OrderService:

@Service 
public class OrderService { 

    @Transactional 
    public Order create(Order order) { 
     order.setStatus("PENDING"); 
     order = orderRepository.save(order); 

     UserPayDTO payDTO = new UserPayDTO(); 
     userCompositeService.payForOrder(payDTO); 

     order.setStatus("PAID"); 
     order = orderRepository.save(order); 

     ticketCompositeService.moveTickets(ticketIds, currentUserId); 

     order.setStatus("FINISHED"); 
     order = orderRepository.save(order); 
     return order; 
    } 

    @Transactional 
    public Order createFallback(Order order) { 
     // order is the object processed in create(), there is Transaction in create(), so saving order will be rollback, 
     // but the order instance still exist. 
     if (order.getId() == null) { // order not saved even. 
      return null; 
     } 
     UserPayDTO payDTO = new UserPayDTO(); 
     try { 
      if (order.getStatus() == "FINISHED") { // order finished, must be paid and ticket moved 
       userCompositeService.payForOrderFallback(payDTO); 
       ticketCompositeService.moveTicketsFallback(getTicketIdList(order.getTicketIds()), currentUserId); 
      } else if (order.getStatus() == "PAID") { // is paid, but not sure whether has error during ticket movement. 
       userCompositeService.payForOrderFallback(payDTO); 
       ticketCompositeService.moveTicketsFallback(getTicketIdList(order.getTicketIds()), currentUserId); 
      } else if (order.getStatus() == "PENDING") { // maybe have error during payment. 
       userCompositeService.payForOrderFallback(payDTO); 
      } 
     } catch (Exception e) { 
      LOG.error(e.getMessage(), e); 
     } 

     order.setStatus("FAILED"); 
     orderRepository.save(order); // order saving is rollbacked during create(), I save it here to trace the failed orders. 
     return order; 
    } 
} 

Quelques points clés sont:

  1. En utilisant @HystrixCommand dans la méthode OrderResource.create(order), avec fallback fonction.
  2. En cas d'erreur de création, l'instance order utilisée dans OrderResource.create(order) sera à nouveau utilisée dans la fonction de repli. Bien que la persistance de ce order sera roll-backed. Mais les données dans cette instance peuvent toujours être utilisées pour vérifier le fonctionnement.
  3. Donc j'utilise un statut: 'PENDING', 'PAID', 'FINISHED' pour vérifier si un appel de service est fait.
  4. ticketCompositeService et userCompositeService est un client feignant. Pour feindre la méthode client payForOrder(), il existe une autre méthode payForOrderFallback() pour le repli.
  5. Je dois m'assurer que les méthodes de repli peuvent être appelées plusieurs fois.
  6. J'ajoute try/catch pour ticketCompositeService et userCompositeService appel, pour m'assurer que la commande sera sauvegardée quand même avec le statut 'FAILED'.

Il semble que cette solution peut fonctionner le plus souvent. Sauf que, dans la fonction de secours, s'il y a une erreur dans userCompositeService.payForOrderFallback(payDTO);, l'appel de service composite suivant ne sera pas appelé.

Et, un autre problème est, je pense que c'est trop compliqué. Donc, pour ce scénario, comment dois-je implémenter la transaction dist correctement et efficacement. Toute suggestion ou conseil vous aidera. Merci.

+0

Vous devriez essayer Event Sourcing + technique CQRS [link] (https://stackoverflow.com/questions/44114755/how-to-do-2-phase-commit-between-two-micro-servicesspring-boot) – sathees

Répondre

1

L'écriture d'une logique de compensation dans Hystrix fallback est dangereuse en raison de l'absence de persistance.

Cette approche n'offre aucune résilience. La garantie ACID de la base de données ne suffit pas ici à cause des parties externes impliquées, et la solution de secours Hystrix ne vous protégera pas de tout ce qui ne fait pas partie de votre code. Par exemple, si votre solution subit une panne (disons une coupure de courant ou un simple kill -9) après la fin du paiement, vous perdrez à la fois la logique de commande et de compensation, ce qui signifie que la commande sera payée mais pas présente dans la base de données . Une approche plus résiliente impliquerait n'importe quel courtier de messages populaire pour la livraison événementielle et une certaine déduplication dans la logique de traitement pour garantir une qualité de service d'une seule fois lorsque les événements sont redistribués après une panne.

+0

merci, j'étudie aussi à ce sujet beaucoup, en écrivant beaucoup de code de test, ma solution finale est également d'utiliser certains processus MQ et événementiel. Merci. – Mavlarn