Как реализовать распределенную транзакцию с помощью Hystrix Fallback на основе Spring Cloud architect

Я использую Spring Cloud для реализации своей системы микроуслуг, платформы продажи билетов. Сценарий таков: есть прокси-сервер zuul, реестр eureka и 3 службы: служба пользователя, служба заказа и служба заказа билетов. Сервисы используют симулированный декларативный клиент REST для связи друг с другом.

Теперь есть функция покупки билетов, основной процесс выглядит так:
1. заказать услугу принять запрос на создание заказа
2. Служба заказа создать объект "Заказ" со статусом "Ожидание"
3. Заказать услугу, позвонить в службу пользователя, обработать оплату пользователя.
4. заказать услугу вызова билетов службы для обновления пользовательских билетов.
5. Служба заказа обновляет объект заказа как ЗАКОНЧЕННЫЙ.

И я хочу использовать Hystrix Fallback осуществить транзакцию. Например, если процесс оплаты завершен, но произошла ошибка при перемещении билета. Как узнать оплату пользователя и статус заказа. Потому что пользовательский платеж есть в другом сервисе.

Следующее моё текущее решение, я не уверен, что оно правильное. Или есть другой лучший способ сделать это.

Во-первых, 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);
  }
}

Тогда 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;
    }
}

Вот некоторые ключевые моменты:

  1. С помощью @HystrixCommand в OrderResource.create(order) метод, с fallback функция.
  2. Если в создании есть какая-то ошибка, order экземпляр используется в OrderResource.create(order) будет снова использован в резервной функции. Хотя настойчивость этого order будет откат. Но данные в этом экземпляре все еще можно использовать для проверки работы.
  3. Поэтому я использую статус: "В ожидании", "Оплачено", "Завершено", чтобы проверить, сделан ли какой-либо сервисный вызов.
  4. ticketCompositeService а также userCompositeService притворный клиент. Для симулированного метода клиента payForOrder()есть другой метод payForOrderFallback() для отступления.
  5. Мне нужно убедиться, что резервные методы могут быть вызваны несколько раз.
  6. я добавить try/catch за ticketCompositeService а также userCompositeService позвоните, чтобы убедиться, что заказ будет сохранен в любом случае со статусом "Неудачно".

Кажется, что это решение может работать в большинстве случаев. Кроме того, в резервной функции, если есть какая-то ошибка в userCompositeService.payForOrderFallback(payDTO);, тогда следующий составной сервисный вызов не будет вызван.

И еще одна проблема, я думаю, что это слишком сложно.

Итак, для этого сценария, как мне правильно и эффективно реализовать транзакцию dist. Любое предложение или совет поможет. Благодарю.

1 ответ

Написание логики компенсации в резервном хранилище Hystrix опасно, так как не требует постоянства.

Этот подход не предлагает никакой устойчивости. Гарантии ACID от базы данных здесь недостаточно из-за участия внешних сторон, и запасной вариант Hystrix не защитит вас от всего, что не является частью вашего кода.

Например, если ваше решение испытывает перебои в работе (скажем, перебои с питанием или простой kill -9) после завершения оплаты вы потеряете как заказ, так и логику компенсации, то есть заказ будет оплачен, но отсутствует в базе данных.

Более гибкий подход предполагает использование любого популярного брокера сообщений для доставки, управляемой событиями, и некоторой дедупликации в логике обработки, чтобы обеспечить ровное качество обслуживания, когда события доставляются после сбоя.