Работа с исключениями в мире событий

Я пытаюсь понять, как обрабатываются исключения в мире, управляемом событиями, с помощью микросервисов (с использованием apache kafka). Например, если вы воспользуетесь следующим сценарием заказа, в котором должны произойти следующие действия, прежде чем заказ может быть завершен.

  • 1) Авторизуйте платеж у поставщика платежных услуг.
  • 2) Зарезервируйте товар со склада
  • 3.1) Захват платежа у поставщика платежных услуг
  • 3.2) Заказать товар
  • 4) Отправьте по электронной почте уведомление о принятии заказа с квитанцией.

На любом этапе этого сценария может произойти сбой, например:

  • Товара больше нет в наличии
  • Платежная информация неверна
  • На счете, который использует получатель платежа, нет свободных средств
  • Внешние вызовы, например, к поставщику платежных услуг, не работают, например, из-за простоя

Как вы отслеживаете, что каждый этап был запрошен и / или завершен?

Как вы справляетесь с возникающими проблемами? Как бы вы уведомили фронтенд о сбое?


person James    schedule 21.04.2018    source источник
comment
Вы слышали об очередях недоставленных писем?   -  person OneCricketeer    schedule 21.04.2018
comment
Конечно, я знаком с этой концепцией, и это поможет. Как бы вы справились с ситуациями, когда вы смогли прочитать сообщение, но не смогли отправить ответ? Покрыть это транзакциями?   -  person James    schedule 24.04.2018


Ответы (1)


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

Например, то, что товара нет в наличии, является вполне допустимым альтернативным потоком в вашем бизнес-процессе. Тот, который, возможно, требует вмешательства человека. Вы можете переместить сообщение в отдельную очередь и предоставить некоторый пользовательский интерфейс, в котором оператор-человек может справиться с проблемой, решить ее и вызвать продолжение потока событий.

То же самое можно сказать и о проблемах с оплатой, которые вы описываете. Если заказ не может быть успешно обработан, оператор-человек должен будет расследовать случай и решить его. В этом отношении ваш дизайн должен рассматривать этот альтернативный поток как его часть и делать так, чтобы человек мог каким-то образом вмешиваться, когда сообщения попадают в очередь, которая требует, чтобы человек их просмотрел.

Эти случаи следует отличать от ошибок или исключений, создаваемых программой. В этих случаях, в зависимости от обстоятельств, на самом деле может потребоваться переместить сообщение в очередь недоставленных сообщений (DLQ), чтобы инженер мог их просмотреть.

Это очень обширная тема, и об этом можно было бы написать целые книги.

Я считаю, что вам, вероятно, будет полезно лучше понять такие концепции, как:

Идея компенсационных транзакций заключается в том, что у каждого инь есть свой ян: если у вас есть одна транзакция, которая может разместить заказ, вы можете отменить это транзакцией, которая отменяет этот заказ. Эта последняя транзакция является компенсационной транзакцией. Итак, если вы выполняете несколько успешных транзакций, а затем одна из них терпит неудачу, вы можете отследить свои шаги и компенсировать каждую успешную транзакцию, которую вы совершили, и, в результате, отменить их побочные эффекты.

Мне особенно понравилась глава в книге ОТДЫХ от исследования к практике . В его главе 23 (Распределенные атомарные транзакции через службы RESTful) подробно объясняется шаблон Попробовать / Отменить / Подтвердить.

В общих чертах это означает, что, когда вы выполняете группу транзакций, их побочные эффекты не действуют, пока координатор транзакций не получит подтверждение того, что все они были успешными. Например, если вы бронируете билет в Expedia, и ваш рейс проходит по двум направлениям с разными авиакомпаниями, то одна транзакция зарезервирует рейс с American Airlines, а другая - с United Airlines. Если ваше второе бронирование не удалось, вы хотите компенсировать первое. Но не только это, вы хотите избежать того, чтобы первое резервирование действовало до тех пор, пока вы не подтвердите оба. Таким образом, первоначальная транзакция выполняет резервирование, но сохраняет свои побочные эффекты ожидающими подтверждения. И вторая оговорка сделает то же самое. Как только координатор транзакции знает, что все зарезервировано, он может отправить подтверждающее сообщение всем сторонам, чтобы они подтвердили свои резервирования. Если бронирования не подтверждаются в разумное временное окно, они автоматически отменяются затронутой системой.

В книге Enterprise Integration Patterns содержатся некоторые основные идеи о том, как для реализации такого типа координации событий (например, см. шаблон диспетчера процессов и сравните с шаблоном маршрутизации, которые аналогичны оркестровка против хореографии в мире микросервисов).

Как видите, возможность компенсировать транзакции может быть сложной в зависимости от того, насколько сложен ваш распределенный рабочий процесс. Менеджеру процессов может потребоваться отслеживать состояние каждого шага и знать, когда все это нужно отменить. Это в значительной степени идея саги в мире микросервисов.

В книге Шаблоны микросервисов есть целая глава под названием «Управление транзакциями с помощью Sagas», в которой подробно рассматривается, как для реализации этого типа решения.

Я также обычно учитываю следующие несколько других аспектов:

Идемпотентность

Я считаю, что ключ к успешной реализации ваших сервисных транзакций в распределенной системе состоит в том, чтобы сделать их идемпотент. Как только вы можете гарантировать, что данная услуга идемпотентна, вы можете безопасно повторить ее, не беспокоясь о дополнительных побочных эффектах. Однако простая попытка неудачной транзакции не решит ваших проблем.

Временные и постоянные ошибки

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

Воспринимайте ошибку как альтернативу

Как упоминалось в начале моего ответа, не все является ошибкой. Некоторые вещи - просто альтернативные потоки.

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

Например, возможно, когда вы обрабатываете заказ, платежный сервис не работает из-за нехватки средств. Значит, все остальное отменять нет смысла. Все, что нам нужно, - это привести порядок в такое состояние, чтобы какой-нибудь решатель проблем мог решить его в системе, и после исправления вы можете продолжить остальную часть рабочего процесса.

Состояние транзакции и модели данных - ключ к успеху

Я обнаружил, что этот тип транзакционных рабочих процессов требует хорошего проектирования различных состояний, которые должна пройти ваша модель. Как и в случае с шаблоном «Попробовать / Отменить / Подтвердить», это подразумевает первоначальное применение побочных эффектов без обязательного предоставления пользователям доступа к модели данных.

Например, когда вы размещаете заказ, возможно, вы добавляете его в базу данных в статусе «Ожидание», который не будет отображаться в пользовательском интерфейсе складских систем. После подтверждения оплаты заказ появится в пользовательском интерфейсе, и пользователь сможет, наконец, обработать свои поставки.

Сложность здесь состоит в том, чтобы понять, как разработать гранулярность транзакции таким образом, чтобы даже в случае сбоя одного из этапов рабочего процесса транзакции система оставалась в допустимом состоянии, из которого можно было возобновить работу после устранения причины сбоя.

Разработка распределенных транзакционных рабочих процессов

Итак, как видите, проектирование распределенной системы, которая работает таким образом, немного сложнее, чем индивидуальный вызов распределенных транзакционных сервисов. Теперь каждый вызов службы может завершиться ошибкой по ряду причин и оставить ваш распределенный рабочий процесс в несогласованном состоянии. И повторная попытка транзакции не всегда может решить проблему. И ваши данные должны быть смоделированы как конечный автомат, чтобы побочные эффекты применялись, но не подтверждались до тех пор, пока вся оркестровка не будет успешной.

Вот почему все это, возможно, придется спроектировать иначе, чем это обычно делается в монолитном клиент-серверном приложении. Теперь ваши пользователи могут быть частью разработанного решения, когда дело доходит до разрешения конфликтов, и учитывать, что оркестровка транзакций может занять часы или даже дни в зависимости от того, как разрешаются их конфликты.

Как я изначально сказал, тема слишком широка, и для подробного обсуждения, возможно, одного или двух из этих аспектов потребуется более конкретный вопрос.

В любом случае, я надеюсь, что это как-то помогло вам в вашем расследовании.

person Edwin Dalorzo    schedule 25.04.2018