Как реализовать архитектуру, управляемую событиями микросервиса, с помощью Spring Cloud Stream Kafka и базы данных для каждой службы

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

пример:

 Order service -------> | Kafka |------->Payment Service
       |                                       |
Orders MariaDB DB                   Payment MariaDB Database

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

частный OrderBusiness orderBusiness;

@PostMapping
public Order createOrder(@RequestBody Order order){
    logger.debug("createOrder()");
    //a.- Save the order in the DB
    orderBusiness.createOrder(order);
    //b. Publish in the topic so that Payment Service charges for the item.
    try{
        orderSource.output().send(MessageBuilder.withPayload(order).build());
    }catch(Exception e){
        logger.error("{}", e);
    }
    return order;
}

вот мои сомнения:

  1. шаги a.- (сохранить в порядке DB) и b. - (опубликовать сообщение) должно выполняться в транзакции атомарно. Как я могу этого достичь?
  2. это связано с предыдущим: я отправляю сообщение с: orderSource.выход.)(отправить (MessageBuilder.withPayload(заказ).build ()); эта операция асинхронна и всегда возвращает true, независимо от того, работает ли брокер Kafka. Как я могу знать, что сообщение дошел до брокера Кафки?

2 ответов


шаги a. - (сохранить в порядке DB) и b. - (опубликовать сообщение) должно быть выполняется в транзакции, атомарно. Как я могу этого достичь?

Kafka в настоящее время не поддерживает транзакции (и, следовательно, также не откат или фиксацию), которые вам нужно синхронизировать что-то вроде этого. Короче говоря, вы не можете делать то, что хотите. Это изменится в ближайшем будущем, когда КИП-98 слили, но это может занять некоторое время. Кроме того, даже с транзакции в Кафке, атомарная транзакция через две системы-очень трудная вещь, все, что следует, будет улучшено только транзакционной поддержкой в Кафке, это все равно не полностью решит вашу проблему. Для этого вам нужно будет изучить реализацию какой-либо формы двухфазный commit всей вашей системы.

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

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

гарантированная доставка

вы можете настроить, сколько брокеров должны подтвердить получение ваших сообщений, прежде чем запрос будет возвращен вам с параметром acks: установив это все вы говорите брокеру подождать, пока все реплики не подтвердят ваше сообщение, прежде чем возвращать вам ответ. Это все еще не 100% гарантия того, что ваше сообщение не будет потеряно, поскольку оно было записано только в кэш страницы, и есть теоретические сценарии с брокером, терпящим неудачу, прежде чем он сохранится на диске, где сообщение все еще может быть потеряно. Но это такая же хорошая гарантия, какую вы получите. Вы можете дальнейшее снижение риска потери данных путем снижения intervall, при котором брокеры принудительно fsync на диск (выделенный текст и / или флеш.ms) но, пожалуйста, имейте в виду, что эти значения могут принести с собой тяжелые штрафы за производительность.

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

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

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

1

Kafka first

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

2

MariaDB first

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

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

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


Это связано с предыдущим: я отправляю сообщение с: orderSource.выход.)(отправить (MessageBuilder.withPayload(заказ).строить()); Эта операция является асинхронной и всегда возвращает true, независимо от того, брокер Кафки упал. Как я могу знать, что сообщение достигло брокер Кафки?

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

Producer<String, String> producer = new KafkaProducer<>(myConfig);
final ArrayList<Exception> exceptionList = new ArrayList<>();

for(MessageType message : messages){
  producer.send(new ProducerRecord<String, String>("myTopic", message.getKey(), message.getValue()), new Callback() {
    @Override
    public void onCompletion(RecordMetadata metadata, Exception exception) {
      if (exception != null) {
        exceptionList.add(exception);
      }
    }
  });
}

producer.flush();

if (!exceptionList.isEmpty()) {
  // do stuff
}

Я думаю, что правильный путь для осуществления поиска событий, имея Кафка быть заполнены непосредственно из событий толкнул плагин, который считывает из базы binlog в электронной.G помощью BottledWater сливные(https://www.confluent.io/blog/bottled-water-real-time-integration-of-postgresql-and-kafka/) или более активных Debezium (http://debezium.io/). Затем потреблял микрослужб можете слушать те события, потреблять их и действуют на их соответствующих баз данных, в конце концов соответствует базе данных РСУБД.

посмотрите здесь на мой полный ответ для руководства: https://stackoverflow.com/a/43607887/986160