Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 07:39
Оценка:
Тут такой вопрос.

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

Что-то может отвалиться, к примеру, временно может не работать внешний сервис — возникнет WebException или там временно перестанет работать база.

Вопрос такой — как вы реализуете повторный запуск/повторную попытку после исключения?

К примеру, можно весь метод Fun1 вызывать повторно бесконечное количество раз. Или же вызывать только n раз.

А можно иначе сделать — вызывать повторно только те части кода, которые, по вашему видению, наиболее вероятно могут упасть. К примеру, применить Retry-паттерн только для вызова метода внешнего сервиса. Ну и тут опять же вопрос — повторять ли бесконечно или же n раз?

Кто как делает?
Re: Повторный запуск операции после exception...
От: Qulac Россия  
Дата: 15.11.21 08:24
Оценка: 4 (1) +2
Здравствуйте, Shmj, Вы писали:

S>Тут такой вопрос.


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


S>Что-то может отвалиться, к примеру, временно может не работать внешний сервис — возникнет WebException или там временно перестанет работать база.


S>Вопрос такой — как вы реализуете повторный запуск/повторную попытку после исключения?


S>К примеру, можно весь метод Fun1 вызывать повторно бесконечное количество раз. Или же вызывать только n раз.


S>А можно иначе сделать — вызывать повторно только те части кода, которые, по вашему видению, наиболее вероятно могут упасть. К примеру, применить Retry-паттерн только для вызова метода внешнего сервиса. Ну и тут опять же вопрос — повторять ли бесконечно или же n раз?


S>Кто как делает?


В своей бд делаем запись что, сколько, куда переводим и запрос к внешнему сервису, потом отправляем запрос к внешнему сервису и пишем в бд результат. В отчетах используются только те записи в которых ответ от внешних сервисов валидный. Если ответ от внешнего сервиса не был получен, запрос повторяем до получения ответа и не более n раз. Запросы к внешним сервисам должны быть идемпотентные.
Программа – это мысли спрессованные в код
Re[2]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 08:46
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>В своей бд делаем запись что, сколько, куда переводим и запрос к внешнему сервису, потом отправляем запрос к внешнему сервису и пишем в бд результат. В отчетах используются только те записи в которых ответ от внешних сервисов валидный. Если ответ от внешнего сервиса не был получен, запрос повторяем до получения ответа и не более n раз. Запросы к внешним сервисам должны быть идемпотентные.


А почему повтор лишь запросов к внешним сервисам? Ведь и локальная база может упасть и Event Broker может упасть и т.д.

Ведь можно сделать просто повтор всей операции — все равно все идемпотентно и в транзакции — т.е. от повторного вызова никаких неожиданностей быть не может.
Re[3]: Повторный запуск операции после exception...
От: Qulac Россия  
Дата: 15.11.21 08:55
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, Qulac, Вы писали:


Q>>В своей бд делаем запись что, сколько, куда переводим и запрос к внешнему сервису, потом отправляем запрос к внешнему сервису и пишем в бд результат. В отчетах используются только те записи в которых ответ от внешних сервисов валидный. Если ответ от внешнего сервиса не был получен, запрос повторяем до получения ответа и не более n раз. Запросы к внешним сервисам должны быть идемпотентные.


S>А почему повтор лишь запросов к внешним сервисам? Ведь и локальная база может упасть и Event Broker может упасть и т.д.


S>Ведь можно сделать просто повтор всей операции — все равно все идемпотентно и в транзакции — т.е. от повторного вызова никаких неожиданностей быть не может.


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

А вообще это все от т.з. зависит. Мы можем сразу генерить ошибку если ответ от внешних сервисов не был получен и запускать отменяющею транзакцию, а уж ее при необходимости повторять.
Программа – это мысли спрессованные в код
Re[4]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 09:49
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>Это же кто-то делает, а его взяли и выключили, а бд как бы всегда присутствует. т.е. есть служба которая занимается повторами.


А если база временно перестала быть доступной?

Q>А вообще это все от т.з. зависит. Мы можем сразу генерить ошибку если ответ от внешних сервисов не был получен и запускать отменяющею транзакцию, а уж ее при необходимости повторять.


Вопрос: только ли сбой внешних сервисов предусматриваете и готовы как-то обработать? Или и базу и вообще все?

Что мешает делать по простой схеме:

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

2. Делать повторы не для маленьких частей кода — а для всего метода Fun1.

?
Re[5]: Повторный запуск операции после exception...
От: Qulac Россия  
Дата: 15.11.21 09:57
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, Qulac, Вы писали:


Q>>Это же кто-то делает, а его взяли и выключили, а бд как бы всегда присутствует. т.е. есть служба которая занимается повторами.


S>А если база временно перестала быть доступной?


Служба при запуске просмотрит записи, те что нужно повторить и сделает повтор, те что нужно отменить — сделает отмену.

Q>>А вообще это все от т.з. зависит. Мы можем сразу генерить ошибку если ответ от внешних сервисов не был получен и запускать отменяющею транзакцию, а уж ее при необходимости повторять.


S>Вопрос: только ли сбой внешних сервисов предусматриваете и готовы как-то обработать? Или и базу и вообще все?


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

S>Что мешает делать по простой схеме:


S>1. Повторный вызов метода Fun1 никогда не приводит к искажению бизнес-процесса. Т.е. хоть миллиард раз вызывай Fun1 — проблем не будет. Все в транзакциях, все критичные моменты проверяются по состоянию и т.д.


S>2. Делать повторы не для маленьких частей кода — а для всего метода Fun1.


А как организовать эти повторы?

S>?
Программа – это мысли спрессованные в код
Re[6]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 10:13
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>Служба при запуске просмотрит записи, те что нужно повторить и сделает повтор, те что нужно отменить — сделает отмену.


В смысле при запуске? При перезагрузке сервера?

А если база отвалилась на 1 мин. потом опять была поднята, при этом сервис обработки платежей не останавливался. Что тогда? Просто падение с записью в лог? Или же пытаться и делать повторы пока база будет поднята?

Q>По идее отменяющий запрос мы должны писать с разу в бд, до обращения к сервису.


Но мы обсуждаем — что если отвалилась база, а не внешний сервис. Worker Service работает постоянно, но база отвалилась — что тогда?

Q>А как организовать эти повторы?


Если система основана на событиях — то средствами Event Broker, для примера. Он будет вызывать подписчиков пока те не скажут что все ОК, смогли обработать. Просто если база сломалась — не говорить OK — пусть дальше пытается до посинения — все равно базу рано или поздно поднимут.

Можно и без Event Broker.
Re[7]: Повторный запуск операции после exception...
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.11.21 10:44
Оценка: +1
Здравствуйте, Shmj, Вы писали:
S>Но мы обсуждаем — что если отвалилась база, а не внешний сервис. Worker Service работает постоянно, но база отвалилась — что тогда?
Вы уже обсуждали этот вопрос. С тех пор ничего не изменилось: если отвалилась "база", то сервис, построенный над ней, обязан отвечать 5xx любым внешним запросам.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 10:45
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Вы уже обсуждали этот вопрос. С тех пор ничего не изменилось: если отвалилась "база", то сервис, построенный над ней, обязан отвечать 5xx любым внешним запросам.


А если это не внешний запрос а внутренний Worker Service? Почему бы не продолжать долбить до тех пор, пока база не будет поднята?
Re: Повторный запуск операции после exception...
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.11.21 11:06
Оценка: 23 (2) +1
Здравствуйте, Shmj, Вы писали:

S>Что-то может отвалиться, к примеру, временно может не работать внешний сервис — возникнет WebException или там временно перестанет работать база.

Это два принципиально разных случая.
Если не работает база — всё, копец, приплыли, возвращаем 5хх.
S>Вопрос такой — как вы реализуете повторный запуск/повторную попытку после исключения?
https://rsdn.org/forum/dotnet/8120684.1
Автор: Sinclair
Дата: 28.10.21

S>К примеру, можно весь метод Fun1 вызывать повторно бесконечное количество раз. Или же вызывать только n раз.
Метод Fun1 — это что? Часть внешнего API?
Если так, то за выбор "повторять/не повторять" отвечаете не вы, а клиент вашего API.
Если нет, то есть Fun1 — это какая-то внутренняя подробность реализации, то всё зависит от того, какие гарантии вы дали внешнему сервису.
Вот у вас есть идея публичного метода, скажем, Transfer(SourceAccount, TargetAccount, Amount).
Реализуете вы его, понятное дело, при помощи HTTP-метода PUT (ну, либо, если вы приверженец альтернативной медицины, то POST с ключом идемпотентности).

При этом у вас SourceAccount — в одной внешней системе, а TargetAccount — в другой.

Дальше возникают различные интересные возможности.
Можно рассмотреть две из них:
1. Вы делаете stateless сервис. Обработчик этого вашего PUT просто берёт, и отправляет PUT <system1>/accounts/<SourceAccount>/withdrawals/<TransferKey>W/ с заказанным amount.
Затем — PUT <system2>/accounts/<TargetAccount>/charges/<TransferKey>W/ с тем же amount.
Если оба вернули 200, то возвращаем 200. Если любой вернул 5xx, возвращаем 5xx.
(Будут сложности с ситуациями, когда первый отдал 200, а второй — 4xx. Для этого есть отдельные методы борьбы).
Идея — в том, что клиент, получивший 5хх, будет вынужден выполнять повтор на своей стороне, пока не получит 2xx или 4xx.
При повторах он будет использовать тот же ключ идемпотентности. А мы генерируем ключи идемпотентности для внешних систем на основе его ключей, поэтому можно пользоваться идемпотентностью исходящих вызовов.
Заметьте — здесь вообще нет никакой "базы". Только внешний сервис и всё.

2. Вы делаете stateful сервис.
При обработке реквеста вы первым делом смотрите в базу.
— если такого реквеста нет — то добавляете, возвращаете 201 Created.
— если такой реквест есть, но в нём не совпадает один из параметров (номера счетов или amount), возвращаете 409 Conflict
— если такой реквест есть, и всё совпадает, то возвращаете 201 Created
— если база лежит, возвращаете 500/503.
В фоне у вас работает async runner, который
— выбирает из базы незавершённые реквесты
— для каждого из них смотрит последний завершённый шаг
— выполняет следующий за ним шаг, и кладёт результат шага в базу\
Если async runner не может связаться с базой, то он ждёт сколько-то времени, и снова пытается связаться. До бесконечности — безо всяких N.
При обработке GET реквеста вы опять смотрите в базу. Если база не отвечаент — 500/503. Если отвечает — смотрите, что там у вас с прогрессом выполнения, и отражаете его возвращаемых данных.

Повторно хочу обратить внимание на деталь, которую вы в прошлый раз проигнорировали: для stateful сервисов отсутствие связи с базой — фатально. Никаких "ретраев N раз" в этом контексте нету.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 11:33
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Это два принципиально разных случая.

S>Если не работает база — всё, копец, приплыли, возвращаем 5хх.

Если возвращать не нужно — просто внутренний Worker Service, который может долбить до победного конца.

S>>К примеру, можно весь метод Fun1 вызывать повторно бесконечное количество раз. Или же вызывать только n раз.

S>Метод Fun1 — это что? Часть внешнего API?

Это наш внутренний метод, в котором есть и вызов внешних сервисов и работа с базой. Допустим, мы вызвали этот метод с неким параметром — transferId. Далее — возникла ошибка при подключении к базе. Что делать? Можно подождать немного и вызывать с тем же параметром еще раз. Или же сдаться и просто записать ошибку в лог и более не вызывать.


S>Дальше возникают различные интересные возможности.

S>Можно рассмотреть две из них:

S>Повторно хочу обратить внимание на деталь, которую вы в прошлый раз проигнорировали: для stateful сервисов отсутствие связи с базой — фатально. Никаких "ретраев N раз" в этом контексте нету.


Речь не о внешнем сервисе, который кто-то дергает и ожидает ответа. А о Worker Service или о демоне, который может делать работу сколь угодно долго — никто не торопит его и не ждет прямо сейчас ответа.
Re[3]: Повторный запуск операции после exception...
От: Qulac Россия  
Дата: 15.11.21 11:46
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>>Это два принципиально разных случая.

S>>Если не работает база — всё, копец, приплыли, возвращаем 5хх.

S>Если возвращать не нужно — просто внутренний Worker Service, который может долбить до победного конца.


S>>>К примеру, можно весь метод Fun1 вызывать повторно бесконечное количество раз. Или же вызывать только n раз.

S>>Метод Fun1 — это что? Часть внешнего API?

S>Это наш внутренний метод, в котором есть и вызов внешних сервисов и работа с базой. Допустим, мы вызвали этот метод с неким параметром — transferId. Далее — возникла ошибка при подключении к базе. Что делать? Можно подождать немного и вызывать с тем же параметром еще раз. Или же сдаться и просто записать ошибку в лог и более не вызывать.



S>>Дальше возникают различные интересные возможности.

S>>Можно рассмотреть две из них:

S>>Повторно хочу обратить внимание на деталь, которую вы в прошлый раз проигнорировали: для stateful сервисов отсутствие связи с базой — фатально. Никаких "ретраев N раз" в этом контексте нету.


S>Речь не о внешнем сервисе, который кто-то дергает и ожидает ответа. А о Worker Service или о демоне, который может делать работу сколь угодно долго — никто не торопит его и не ждет прямо сейчас ответа.


Если Вас интересует более общий подход для таких случаем, то он есть, но я его специально не приводил.
Программа – это мысли спрессованные в код
Re[3]: Повторный запуск операции после exception...
От: Sinclair Россия https://github.com/evilguest/
Дата: 15.11.21 13:04
Оценка: :)
Здравствуйте, Shmj, Вы писали:

S>Если возвращать не нужно — просто внутренний Worker Service, который может долбить до победного конца.

С одной стороны, Worker Service должен, конечно же, долбить до победного конца.

S>Это наш внутренний метод, в котором есть и вызов внешних сервисов и работа с базой. Допустим, мы вызвали этот метод с неким параметром — transferId. Далее — возникла ошибка при подключении к базе. Что делать? Можно подождать немного и вызывать с тем же параметром еще раз. Или же сдаться и просто записать ошибку в лог и более не вызывать.

С другой стороны, вы, похоже, не понимаете, как должен быть устроен worker service.
Откуда он взял параметр для вызова Fun1? Этот сервис не должен обладать никаким внутренним состоянием, помимо хранимого в базе. По очевидным причинам.
Цикл "долбления" для этого сервиса — вовсе не "вызов Fun1 с параметром transferId".
Это вызов цикла "read state machine from database — execute step".
Из этого сразу понятно, что без базы данных сервис ничего не может сделать, и ни до какого вызова функции Fun1 дело не доходит.

Поймите, важно не то, что будет делать worker service в том случае, если во время его работы пропадёт база.
Важно, что он будет делать, если во время его работы упадёт сам worker service. Он не может полагаться на то, что он переживёт crash, и цикл ретраев продолжит работать "сам по себе".
В любой момент этого цикла из-под сервиса могут выдернуть базу, внешнюю сеть, внутреннюю сеть, или вообще питание.
Поэтому сервису важно уметь продолжать долбить даже после холодного рестарта.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Повторный запуск операции после exception...
От: vaa  
Дата: 15.11.21 13:29
Оценка:
Здравствуйте, Shmj, Вы писали:

S> просто внутренний Worker Service, который может долбить до победного конца.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: Повторный запуск операции после exception...
От: 4058  
Дата: 15.11.21 14:03
Оценка: 14 (1)
Здравствуйте, Shmj, Вы писали:

S>Тут такой вопрос.


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


S>Что-то может отвалиться, к примеру, временно может не работать внешний сервис — возникнет WebException или там временно перестанет работать база.


S>Вопрос такой — как вы реализуете повторный запуск/повторную попытку после исключения?


Недавно обсуждался вопрос связанный с распределенными транзакциями, это всё по той-же части.
Рассмотрим пример, Вы вызвали чей-то сервис, который успешно обработал запрос и зафиксировал результаты в БД, но в процессе формирования/отправки ответа неожиданно "прилёг", причем на пару часов, что не благоприятно поспособствует бесполезным повторным вызовам на вашей стороне в течении этого времени.
Получается, со стороны того сервиса транзакция/операция обработана успешно, но Вы об этом не узнали.

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

В простейшем случае можно синхронно "подолбиться" повторами в течении какого-то времени, но сценарий, в котором чужой сервис стал не доступен на неопределенное время это не покрывает.
Отредактировано 15.11.2021 14:08 4058 . Предыдущая версия . Еще …
Отредактировано 15.11.2021 14:06 4058 . Предыдущая версия .
Re[4]: Повторный запуск операции после exception...
От: Shmj Ниоткуда  
Дата: 15.11.21 17:16
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Откуда он взял параметр для вызова Fun1? Этот сервис не должен обладать никаким внутренним состоянием, помимо хранимого в базе. По очевидным причинам.


Успел взять из базы, пока она еще работала. Потом вызвал метод с этим параметром — уже все, база не работает. Что делать?

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


Почему не доходит? Оно же не мгновенно все происходит. Получил transferId из базы, с этим transferId сделали внешние запросы. Получили результат — пробуем записать в базу — а уже все, база не доступна. Что делать?
Re[5]: Повторный запуск операции после exception...
От: Sharov Россия  
Дата: 15.11.21 18:06
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Почему не доходит? Оно же не мгновенно все происходит. Получил transferId из базы, с этим transferId сделали внешние запросы. Получили результат — пробуем записать в базу — а уже все, база не доступна. Что делать?


Сохранить состояние локально для сервиса, чтобы потом попробовать или, если перезапустят,
завершить операцию.
Кодом людям нужно помогать!
Re[5]: Повторный запуск операции после exception...
От: Sinclair Россия https://github.com/evilguest/
Дата: 16.11.21 05:19
Оценка: 7 (2)
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, Sinclair, Вы писали:


S>>Откуда он взял параметр для вызова Fun1? Этот сервис не должен обладать никаким внутренним состоянием, помимо хранимого в базе. По очевидным причинам.


S>Успел взять из базы, пока она еще работала. Потом вызвал метод с этим параметром — уже все, база не работает. Что делать?

Как обычно — переходим к следующей стейт-машине. Если база всё ещё недоступна — спим, повторяем п.1.

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

S>Почему не доходит? Оно же не мгновенно все происходит. Получил transferId из базы, с этим transferId сделали внешние запросы. Получили результат — пробуем записать в базу — а уже все, база не доступна. Что делать?
Смотрите: у Worker-а нет долговременной памяти. Он, конечно, может внутри основного цикла сделать ещё один цикл "давайте подолбим подольше", но никакого смысла в этом нет. Только усложнение реализации и увеличение риска сделать ошибку.
Дело в том, что у worker-а нет никаких оснований надеяться на то, что базу починят до момента его перезапуска.
Поэтому он в любом случае должен быть готов к тому, что будет стартован с момента "читаем transferID из базы".
Эта готовность покрывает следующие сценарии:
— мы обратились ко внешнему сервису, он ответил 500 и мы даже не знаем, успел ли тот сервис что-то у себя изменить до отправки нам 500, или нет
— мы обратились ко внешнему сервису, ничего не дождались и отвалились по тайм-ауту. Мы не знаем, получил ли вообще сервис наш запрос или нет — возможно, в глобальном роутинге временный косяк, и пакеты "туда" доезжают, а пакеты "обратно" — нет.
— мы обратились ко внешнему сервису, дождались результата, но не успели сделать коммит в локальную базу до того, как из-за сбоя наш worker был перезапущен
— мы обратились ко внешнему сервису, дождались результата, но не смогли сделать коммит в локальную базу из-за сбоя сервера данных или коммуникации с ним
— мы обратились ко внешнему сервису, дождались результата, успели сделать коммит в локальную базу, но не получили подтверждение коммита из-за сбоя коммуникации с СУБД.

Все эти случаи рулятся ровно одним и тем же кодом, который прост и надёжен, как угол дома.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: Повторный запуск операции после exception...
От: Sharov Россия  
Дата: 18.11.21 13:53
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Все эти случаи рулятся ровно одним и тем же кодом, который прост и надёжен, как угол дома.


Так и что за код, а то я упустил этот момент? Сохранять что-то локально, т.к. в какое-то
локальное хранилище(бд)?
Кодом людям нужно помогать!
Re[7]: Повторный запуск операции после exception...
От: Sinclair Россия https://github.com/evilguest/
Дата: 18.11.21 15:11
Оценка:
Здравствуйте, Sharov, Вы писали:
S>Так и что за код, а то я упустил этот момент? Сохранять что-то локально, т.к. в какое-то локальное хранилище(бд)?
Тут не очень важно, насколько хранилище "локально". Важно, что весь стейт живёт в этом хранилище. Хранилище должно обеспечивать ACID, чтобы состояние было гарантированно консистентным.
Этот стейт обрабатывается стейт машиной. То есть каждый workflow — это отдельная стейт-машина.
Мы в цикле делаем LoadState->Transition->SaveState, пока workflow не достигнет финального состояния.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.