Как реализовать распределенную транзакцию с помощью 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;
}
}
Вот некоторые ключевые моменты:
- С помощью
@HystrixCommand
вOrderResource.create(order)
метод, сfallback
функция. - Если в создании есть какая-то ошибка,
order
экземпляр используется вOrderResource.create(order)
будет снова использован в резервной функции. Хотя настойчивость этогоorder
будет откат. Но данные в этом экземпляре все еще можно использовать для проверки работы. - Поэтому я использую статус: "В ожидании", "Оплачено", "Завершено", чтобы проверить, сделан ли какой-либо сервисный вызов.
ticketCompositeService
а такжеuserCompositeService
притворный клиент. Для симулированного метода клиентаpayForOrder()
есть другой методpayForOrderFallback()
для отступления.- Мне нужно убедиться, что резервные методы могут быть вызваны несколько раз.
- я добавить
try/catch
заticketCompositeService
а такжеuserCompositeService
позвоните, чтобы убедиться, что заказ будет сохранен в любом случае со статусом "Неудачно".
Кажется, что это решение может работать в большинстве случаев. Кроме того, в резервной функции, если есть какая-то ошибка в userCompositeService.payForOrderFallback(payDTO);
, тогда следующий составной сервисный вызов не будет вызван.
И еще одна проблема, я думаю, что это слишком сложно.
Итак, для этого сценария, как мне правильно и эффективно реализовать транзакцию dist. Любое предложение или совет поможет. Благодарю.
1 ответ
Написание логики компенсации в резервном хранилище Hystrix опасно, так как не требует постоянства.
Этот подход не предлагает никакой устойчивости. Гарантии ACID от базы данных здесь недостаточно из-за участия внешних сторон, и запасной вариант Hystrix не защитит вас от всего, что не является частью вашего кода.
Например, если ваше решение испытывает перебои в работе (скажем, перебои с питанием или простой kill -9
) после завершения оплаты вы потеряете как заказ, так и логику компенсации, то есть заказ будет оплачен, но отсутствует в базе данных.
Более гибкий подход предполагает использование любого популярного брокера сообщений для доставки, управляемой событиями, и некоторой дедупликации в логике обработки, чтобы обеспечить ровное качество обслуживания, когда события доставляются после сбоя.