Рекомендации по выполнению последовательных операций
-
03-07-2019 - |
Вопрос
Каков наилучший способ выполнить пару задач вместе, и если одна задача завершается неудачей, то следующие задачи не должны быть выполнены?Я знаю, что если бы это были операции с базой данных, то я должен был бы использовать транзакции, но я говорю о различных типах операций, таких как следующие:
Все задания должны быть выполнены:
Отправить почту Архивная база данных отчетов Создать файл
В приведенном выше сценарии все задачи должны быть выполнены, иначе вся пакетная операция должна быть откатана.
Решение
Исключения, как правило, хороши для такого рода вещей.Псевдо-Java/JavaScript/C++ код:
try {
if (!SendEmail()) {
throw "Could not send e-mail";
}
if (!ArchiveReportsInDatabase()) {
throw "Could not archive reports in database";
}
if (!CreateAFile()) {
throw "Could not create file";
}
...
} catch (Exception) {
LogError(Exception);
...
}
А еще лучше, если ваши методы сами выдают исключения:
try {
SendEmail();
ArchiveReportsInDatabase();
CreateAFile();
...
} catch (Exception) {
LogError(Exception);
...
}
Очень приятным результатом такого стиля является то, что ваш код не становится все более разбитым по мере продвижения вниз по цепочке задач;все вызовы вашего метода остаются на том же уровне отступа.Слишком большой отступ затрудняет чтение кода.
Более того, у вас есть единственная точка в коде для обработки ошибок, ведения журнала, отката и т.д.
Другие советы
Откаты - это сложно - AFAIK, на самом деле есть только 2 способа сделать это.Либо a протокол 2-фазной фиксации, или компенсирующие операции.Вам действительно нужно найти способ структурировать свои задачи одним из этих способов.
Обычно лучшая идея - воспользоваться тяжелым трудом других людей и использовать технологии, в которые уже встроен 2PC или компенсация.Это одна из причин такой популярности СУБД.
Итак, специфика зависит от задачи ... но шаблон довольно прост:
class Compensator {
Action Action { get; set; }
Action Compensate { get; set; }
}
Queue<Compensator> actions = new Queue<Compensator>(new Compensator[] {
new Compensator(SendEmail, UndoSendEmail),
new Compensator(ArchiveReportsInDatabase, UndoArchiveReportsInDatabase),
new Compensator(CreateAFile, UndoCreateAFile)
});
Queue<Compensator> doneActions = new Queue<Compensator>();
while (var c = actions.Dequeue() != null) {
try {
c.Action();
doneActions.Add(c);
} catch {
try {
doneActions.Each(d => d.Compensate());
} catch (EXception ex) {
throw new OhCrapException("Couldn't rollback", doneActions, ex);
}
throw;
}
}
Конечно, для ваших конкретных задач - возможно, вам повезет.
- Очевидно, что работа с СУБД уже может быть заключена в транзакцию.
- Если вы используете Vista или Server 2008, то вы получаете Транзакционные NTFS чтобы охватить ваш сценарий создания файла.
- Электронная почта немного сложнее - я не знаю ни о каких 2PC или компенсаторах вокруг нее (я был бы лишь слегка удивлен, если бы кто-нибудь указал, что у Exchange есть такой), поэтому я бы, вероятно, использовал MSMQ написать уведомление и позволить подписчику забрать его и в конечном итоге отправить по электронной почте.На этом этапе ваша транзакция действительно охватывает только отправку сообщения в очередь, но этого, вероятно, достаточно.
Все они могут участвовать в Система.Транзакции Транзакция, так что вы должны быть в довольно хорошей форме.
в C#
возвращает отправленную почту() && Архивную базу данных portsindatabase() && CreateAFile();
Еще одна идея:
try {
task1();
task2();
task3();
...
taskN();
}
catch (TaskFailureException e) {
dealWith(e);
}
Пара предложений:
В распределенном сценарии может потребоваться какой-то двухфазный протокол фиксации.По сути, вы отправляете всем участникам сообщение со словами "Приготовьтесь сделать X".Затем каждый участник должен отправить ответ со словами "Хорошо, я гарантирую, что смогу выполнить X" или "Нет, не могу этого сделать". Если все участники гарантируют, что смогут выполнить, отправьте сообщение с просьбой сделать это."Гарантии" могут быть настолько строгими, насколько это необходимо.
Другой подход заключается в том, чтобы предоставить какой-то механизм отмены для каждой операции, а затем использовать такую логику:
try:
SendEmail()
try:
ArchiveReportsInDatabase()
try:
CreateAFile()
except:
UndoArchiveReportsInDatabase()
raise
except:
UndoSendEmail()
raise
except:
// handle failure
(Вы бы не хотели, чтобы ваш код выглядел так;это всего лишь иллюстрация того, как должна протекать логика.)
Если ваш язык позволяет это, это очень аккуратно:
- Поместите ваши задачи в массив блоков кода или указателей на функции.
- Выполните итерацию по массиву.
- Прервитесь, если какой-либо блок вернет сбой.
Вы не упомянули, какой язык программирования / среду вы используете.Если это .NET Framework, возможно, вы захотите взглянуть на эта статья.В нем описывается среда выполнения параллелизма и управления от Microsoft Robotics Studio, которая позволяет применять всевозможные правила к набору (асинхронных) событий:например, вы можете дождаться завершения любого из них, отменить, если одно событие завершится неудачно, и т.д.Он также может запускать что-то в нескольких потоках, так что вы получаете очень мощный метод выполнения задач.
Вы не указываете свое окружение.В сценариях оболочки Unix оператор && делает именно это.
SendEmail () {
# ...
}
ArchiveReportsInDatabase () {
# ...
}
CreateAFile () {
# ...
}
SendEmail && ArchiveReportsInDatabase && CreateAFile
Если вы используете язык, который использует оценка схемы сортировки (Java и C # делают), вы можете просто сделать:
return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();
Это вернет значение true, если все функции вернут значение true, и остановится, как только первая из них вернет значение false.
Чтобы действительно сделать это правильно, вы должны использовать асинхронный шаблон обмена сообщениями.Я только что закончил проект, в котором я сделал это, используя Nсервисный автобус и MSMQ.
По сути, каждый шаг выполняется путем отправки сообщения в очередь.Когда NServiceBus обнаруживает сообщения, ожидающие в очереди, он вызывает ваш метод обработки, соответствующий этому типу сообщения.Таким образом, каждый отдельный шаг независимо терпит неудачу и может быть повторен.Если один шаг завершается неудачей, сообщение попадает в очередь ошибок, так что вы можете легко повторить его позже.
Предлагаемые решения с чистым кодом не столь надежны, поскольку в случае сбоя какого-либо шага у вас не будет возможности повторить только этот один шаг в будущем, и вам придется реализовать код отката, который в некоторых случаях даже невозможен.