Сбой машины при публикации сообщения с Rebus и подключением к базе данных, зачисленных в область транзакции - PullRequest
0 голосов
/ 28 августа 2018

Предположим, у меня есть Rebus и соединение с базой данных (например, соединение с сервером sql), зачисленные в область транзакции. При подключении к базе данных будут выполнены некоторые операции с базой данных, а некоторые сообщения будут опубликованы Rebus, и область транзакции не будет расширена до MSDTC (я проверил, что в Windows нет распределенной транзакции, а также этот сценарий работает и в Linux, где MSDTC не поддерживается). Complete () вызывается в области транзакции, которая инструктирует и соединение с базой данных, и Rebus для фиксации. Теперь давайте предположим, что соединение с базой данных фиксируется первым и успешно, и до того, как Rebus сможет зафиксировать (= опубликовать сообщения), происходит сбой машины. Что случилось бы? Я могу думать об этих сценариях:

  1. Операции с базой данных зафиксированы, но сообщения не публикуются (неверное состояние).
  2. Операции с базой данных откатываются (не уверен, кто, поскольку MSDTC не будет задействован, и когда машина перезапускается, я не думаю, что кто-нибудь проверит, что произошло с транзакцией во время сбоя), и никакие сообщения не публикуются ( правильное состояние).
  3. Операции с базой данных зафиксированы, и сообщения публикуются (кем?) После перезагрузки компьютера (правильное состояние).

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

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

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

Как упоминает @ mookid8000, гарантии на 100% отсутствуют даже при использовании распределенных транзакций. Причиной этого является 2 общая проблема . Но вы могли бы сказать, что использование распределенных транзакций превосходит все остальное с точки зрения надежности. К сожалению, это создает много накладных расходов и Serializable блокирует ваши данные в SQL Server. Oracle даже не поддерживает . Большинство администраторов баз данных ненавидят это и не без причины. Я строил системы, используя MSMQ и SQL Server, которые работали нормально, но для этого нужно подумать.

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

Хорошим решением, упомянутым @ mookid8000, является сохранение идентификатора каждого входящего сообщения в базе данных и проверка того, было ли это сообщение уже обработано. Но это не останавливается там. Представьте публикуемое событие с идентификатором 1b068720-b558-4edf-9ebd-7142bc8cd3c0. Затем мы пытаемся сообщить очереди, что она может удалить сообщение, но мы не можем сделать это из-за ошибки. Когда мы сохранили идентификатор сообщения в базе данных и совершили эту транзакцию? Удалось ли это или нет? Если мы обработаем входящее сообщение еще раз, найдется ли идентификатор в базе данных? Возможно, но был ли этот идентификатор представлен до или после того, как мы опубликовали событие? Каждый шаг может провалиться!

Вопрос в том, будет ли событие опубликовано снова? Потому что, если будет, с каким идентификатором? Вероятно, новый уникальный, как 00d13f2b-ce5b-4880-9a5b-2cb541015902. Проблема здесь в том, как получающая конечная точка может знать, что это одно и то же логическое сообщение, и оно не должно обрабатываться, потому что мы уже обработали сообщение, но с другим идентификатором? Нам нужно убедиться, что событие действительно опубликовано, но также, что если мы опубликуем его снова, оно будет с точно таким же идентификатором. В противном случае идемпотентность на другой стороне очень трудна, если не невозможна!

Вот тут и приходит шаблон исходящих сообщений .

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

0 голосов
/ 28 августа 2018

При обработке сообщения Rebus выполняет свою работу в следующем порядке:

  1. Ваш обработчик выполнен (возможно, задействует транзакцию базы данных для фиксации вашей собственной работы)
  2. Исходящие сообщения отправляются
  3. Входящее сообщение удаляется из очереди

Поскольку мир полон неудач, ваша программа может потерпеть неудачу в любой момент между (или во время!) Этими шагами.

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

Если что-то не получится после (1) и до полного завершения (3), вы получите гарантию «хотя бы раз» доставки Rebus, что означает, что - в случае подобных сбоев - сообщения БУДУТ обрабатываться хотя бы один раз, что означает, что с можно справиться дважды, а может, даже больше, если вам повезет.

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

Идемпотентность может быть достигнута многими способами: иногда благодаря идемпотентности самой операции (например, просто добавление полученных данных, установка значений некоторых полей в значение из сообщения и т. Д.), Иногда полагаясь на то, что возможность удалять устаревшие данные (например, если вы можете сравнить «последнее измененное» значение ваших данных с отметкой времени обновления из сообщения).

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

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

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

(...) создатели NServiceBus утверждают, что всегда будет правильный результат области транзакции - либо все зафиксировано, либо все откатано, независимо от того, произойдет ли сбой машины в любой точке области транзакции (.. .)

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

...