что такое идемпотентные запросы

Почему важна идемпотентность и как писать идемпотентные bash-скрипты

что такое идемпотентные запросы. Смотреть фото что такое идемпотентные запросы. Смотреть картинку что такое идемпотентные запросы. Картинка про что такое идемпотентные запросы. Фото что такое идемпотентные запросы

Идемпотентность помогает проектировать более надёжные системы. Это математическая концепция, которую должен понимать каждый разработчик. Операция считается идемпотентной, если её многократное выполнение приводит к тому же результату, что и однократное выполнение. Например, умножение на 1 — идемпотентная операция.

Умножение на ноль — тоже идемпотентная операция.

Ключевая концепция, которую нужно запомнить: однократное выполнение операции может иметь побочные эффекты, но повторное выполнение этой же операции не приведёт ни к чему другому, кроме того, что было сделано при первом выполнении. То есть присваивание — идемпотентная операция.

Вы можете присваивать x значение 4 сколько угодно раз, но x всё равно будет иметь значение 4. При этом присваивание x значения 4 один раз отличается от нуля.

Идемпотентность HTTP-методов

HTTP-методы могут быть идемпотентными или нет.

DELETE — идемпотентный метод. Вы можете сколько угодно раз использовать DELETE, но результат будет всегда таким же, как после первого выполнения операции. Например, DELETE /users/4/contacts/3 удаляет контакт с ID 3. Если вы выполните эту же операцию ещё раз, ничего не произойдёт, так как контакт уже удалён.

GET — тоже идемпотентный метод. Это даже не просто идемпотентный, но ещё и безопасный метод. А безопасные методы можно сравнить с умножением на единицу. Умножать на 1 можно сколько угодно раз, результат всегда будет одинаковым. GET просто получает ресурс. Например, никогда не стоит использовать нормальные ссылки для удаления ресурсов.

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

Потребители и провайдеры используют эту концепцию, когда речь идёт об API. Дизайн с учётом этой концепции позволяет соблюдать правило наименьшего удивления.

NB! Правило наименьшего удивления или principle of least astonishment гласит, что результат выполнения операции должен быть очевидным и предсказуемым по названию операции. Полезно ознакомиться со статьями «Именование в программировании» и «Ошибки именования в программировании».

Очереди сообщений

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

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

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

Здесь в игру вступает идемпотентность.

Убедитесь в идемпотентности задач, которые выполняете через воркер

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

Увидели, что что-то идёт не так? Можно снова вызвать функцию, которая отправит все приглашения на все мероприятия. Снова проблемы, когда половина писем отправлена? Исправьте проблему и вызовите функцию снова. Достаточно просто выяснить, какие письма не были отправлены. В качестве бонуса: вы можете понять, сколько писем в день рассылается, когда шлётся больше всего писем и так далее.

Помните, некоторые очереди не гарантируют отправку одного письма один раз. К ним относится Amazon SQS. Поэтому ваши воркеры должны выполнять только идемпотентные задачи.

SQL-миграции

Когда вы выполняете SQL-миграции, позаботьтесь об их идемпотентности.

Например, вам нужно разделить таблицу пользователя на две. Одна таблица будет для пользователей (users), а вторая для деталей, которые не всегда важны (profiles). Вы помещаете внешний ключ user_id в в таблицу profiles. Получаете миграцию, которая берёт каждый ряд в users (SELECT * FROM users) и вставляет ряд с пользовательскими данными в profiles. Запускаете миграцию, но она крашится через час. Это происходит из-за значений NULL, которые вы не учли. Вы исправляете ошибку, снова запускаете миграцию, но вдруг понимаете, что для некоторых пользователей созданы по два профиля.

Этого можно избежать с помощью идемпотентного решения. Вместо SELECT * FROM users можно выбрать пользователей, у которых ещё нет ряда в profiles. С таким решением миграцию можно запускать сколько угодно раз. В ходе каждого запуска будут обработаны данные тех пользователей, у которых ещё нет профиля. Огромное преимущество этого подхода — вам не нужно останавливать приложение при выполнении миграции. Как только вы готовы развернуть новую версию, в которой используется profiles, можно вызвать функцию, которая обеспечит миграцию новых пользователей. Это не самый лучший пример, так как пользователь во время миграции может изменить какие-то данные в таблице users. Учитывайте этот момент.

Денормализованные данные

У вас есть приложение, в котором у каждого пользователя есть много документов. Пользователи могут искать документы по тегам. Теги приходят из разных источников: названия документов, директорий, имена авторов, собственно теги и так далее. Вы решаете держать все теги для всех документов в таблице tags. Это выглядит так:

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

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

Вместо функции add_tag_for_new_author вам нужно изначально сделать функцию update_tags_for_document. Эта функция не просто добавляет тег автора. Она проверяет документ, перестраивает список тегов и убеждается, что в базу данных попала корректная информация. При таком подходе таблица tags обрабатывается корректным способом: с помощью кэша. Вы можете удалить все ряды из таблицы и запустить update_tags_for_document. Требуется 2 секунды, чтобы обновить теги? Пусть этим занимается воркер, добавьте сообщение в очередь.

Как писать идемпотентные bash-скрипты

Иногда случается такое: вы пишете bash-скрипт, запускаете его, но через какое-то время он завершается из-за ошибки. Вы фиксите ошибку в системе и снова запускаете скрипт. Но часть шагов вашего скрипта падает с ошибкой, так как эти шаги уже были выполнены при первом запуске. Чтобы создавать отказоустойчивые системы, нужно писать идемпотентные программы.

Bash-идиомы

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

Создание пустого файла

Это простая задача. Команда touch по умолчанию идемпотентная. Вы можете вызывать её много раз без проблем. Повторный вызов не повлияет на контент файла. Но он изменит время модификации файла. Если вы его используете, будьте осторожны.

Создание директории

Никогда не используйте команду mkdir как есть. Применяйте её с флагом -p. Этот флаг гарантирует отсутствие ошибки при запуске mkdir, если директория уже существует.

Создание символической ссылки

Для создания символьных ссылок используется такая команда:

Но эта команда генерирует ошибку, если вы повторно вызовите её с существующей целью. Чтобы сделать команду идемпотентной, добавьте флаг -f.

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

Для безопасности всегда используйте такую команду:

Удаление файла

Следующую ниже команду нежелательно использовать для удаления файлов:

Чтобы команда игнорировала несуществующие файлы, надо использовать флаг -f.

Изменение файла

Иногда необходимо добавить новую строку в файл, например, /etc/fstab. Нужно убедиться, что повторный запуск скрипта не приводит к добавлению строки ещё раз. Представьте, что используете такой скрипт:

Если вы запустите скрипт повторно, получите дублирующуюся запись в /etc/fstab. Один из способов сделать скрипт идемпотентным — проверить существование конкретных плейсхолдеров с помощью grep.

В данном случае -q обозначает тихий режим, а -F — режим фиксированной строки. Grep в фоновом режиме завершится с ошибкой, если /mnt/dev не существует, поэтому echo не вызовется.

Проверка существования переменной, файла или директории

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

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

В данном случае -f — только пример. Есть много других флагов, в том числе:

Например, если вы хотите установить бинарный файл, но только если его ещё не существует, можно использовать флаг -x таким способом:

Это устанавливает файл op в /usr/local/bin. Повторный запуск скрипта не приведёт к повторной установке бинарного файла. В данном случае есть ещё одно преимущество. Вы можете легко обновить бинарный файл. Для этого достаточно удалить его из системы, обновить OP_VERSION env и повторно запустить скрипт. Список флагов и операторов можно получить с помощью man test.

Форматирование устройства

Для форматирования можно использовать такую команду:

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

Монтирование устройства

Попытка монтировать том в существующий каталог может выполняться с помощью такой команды:

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

Эта команда проверяет, является ли файл или каталог точкой монтирования. Флаг -q гарантирует, что она ничего не выводит и завершается в фоновом режиме. Если точка монтирования не существует, команда монтирует устройство.

Завершение

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

Адаптированный перевод статей The Importance of Idempotence by Antoine Leclair и How to write idempotent Bash scripts by Fatih Arslan.

Источник

Как REST-архитектура влияет на скорость и надежность работы сайта

В основе REST-архитектуры лежит несколько важных базовых принципов, которые часто упускаются из вида начинающими программистами. Между тем, эти принципы имеют критическое значение для скорости и надежности работы веб-сайта. В некотором смысле REST — это архитектура, концентрирующаяся на совместимости и эффективном взаимодействии с другими узлами сети и клиентским ПО. Для них веб-сайт — черный ящик, реализующий HTTP интерфейс.

Унифицированный программный интерфейс

Ключевой момент: совместимость с HTTP-методами в плане безопасности и идемпотентности.

Безопасный запрос — это запрос, который не меняет состояние приложения.

Идемпотентный запрос — это запрос, эффект которого от многократного выполнения равен эффекту от однократного выполнения.

Таблица соответствия HTTP-метода безопасности и идемпотентности:

HTTP-МетодБезопасныйИдемпотентый
GETДаДа
HEADДаДа
OPTIONSДаДа
PUTНетДа
DELETEНетДа
POSTНетНет

Что мы видим? GET-запрос не должен менять состояние ресурса, к которому применяется. PUT и DELETE запросы могут менять состояние ресурса, но их можно спокойно повторять, если нет уверенности, что предыдущий запрос выполнился. В принципе, это логично: если многократно повторять запрос удаления или замены определенного ресурса, то результатом будет удаление или замена ресурса. Но POST запрос, как мы видим из таблицы, небезопасный и неидемпотентный. То есть мало того, что он меняет состояние ресурса, так и многократное его повторение будет иметь эффект, зависимый от количества повторений. Ему по смыслу соответствует операция добавления новых элементов в коллекцию: выполнили запрос Х раз, и в коллекцию добавилось Х элементов.

Опираясь на понятия безопасности и идемпотентности легче понять, какие именно методы соответствуют операциям в терминах CRUD:

HTTP-методОперация
POSTCreate
GETRead
PUTUpdate
DELETEDelete

Что из этого следует? При проектировании REST интерфейса надо в первую очередь думать, не о том, как будет выглядеть структура URL, а соответствует ли суть выполняемых операций безопасности и идемпотентности выбранного HTTP метода.

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

Отказ хранить состояние клиента и кэширование

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

Более того, внутренняя логика усложняется: приходится использовать сессии. При каждой генерации странички приходится инициировать загрузку сессионных данных из хранилища, сохранение сессионных данных в хранилище. Все это выливается в активное чтение и запись на диск или в БД. Часто веб-сайт спроектирован таким образом, что при посещении поисковым роботом, он все равно стартует для него сессию, хотя смысла в этом нет никакого.

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

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

REST предлагает нам использовать HTTP-заголовки для управления кэшированием. Это позволяет перенести бремя хранения кэша на клиента, на промежуточные узлы между сервером и клиентом или на специализированное ПО, например, Squid.

Не совсем очевидно, но кэшировать можно не только успешные HTTP-ответы (200 OK), но и другие:

Источник

Как сделать хорошую интеграцию? Часть 2. Идемпотентные операции – основа устойчивой интеграции

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

Потому что процессы, которые обрабатывают сообщения и удаленные вызовы, могут падать из-за собственных ошибок в середине обработки. Или по другим причинам. Например, при исчерпании места на диске, которое обнаружилось при попытке вставить очередную запись в журнал обработки. Или по таймауту или deadlock обращения к базе данных. Или без всяких ошибок процессы убивает наблюдатель за работоспособностью сервера в целом, выбрав их в качестве жертвы. Может быть, просто потому, что процесс активно работал с памятью, а сборщик мусора вызваться не успел, вот наблюдатель и решил, что процесс съел памяти слишком много… То есть даже если разработчик написал обработчик аккуратно и позаботился о надлежащей обработке ошибок, она все равно может оказаться невозможной.

А сами сообщения? Теряются, приходят в неверном порядке, дублируются. А у нас не всегда есть выбор по использованию конкретного протокола. Exactly one — большая редкость, и в ее реализации обычно есть большая серая зона, связанная со заложенным в транспортный уровень алгоритмами определения: было ли сообщение успешно доставлено, обработал ли его получатель? Важно не просто доставить сообщение, а успешно его обработать в условиях, когда гарантий безошибочной обработки не существует.

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

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

что такое идемпотентные запросы. Смотреть фото что такое идемпотентные запросы. Смотреть картинку что такое идемпотентные запросы. Картинка про что такое идемпотентные запросы. Фото что такое идемпотентные запросыКаждый сервис-гномик обозначен своим цветом

На схеме изображена часть реализации интернет-магазина:

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

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

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

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

Теперь рассмотрим подробнее, как такой протокол реализовывать и использовать.

Как реализовывать идемпотентные операции

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

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

Это аналогично UPSERT

Замечу, что такой протокол, если его применять для операций с объектами, очень похож на оператор UPSERT или MERGE — гибрид из INSERT и UPDATE, который обновляет записи, а при их отсутствии — создает новые. Это — относительно свежее расширение SQL, оно появилось в 2003 году и далеко не везде реализовано, поэтому в учебники в качестве основного шаблона работы с СУБД не попало: пятнадцать лет — вовсе не срок для обновления учебников. А зря.

Потому что именно здесь лежит проблема. Протокол идемпотентных операций не часто реализуют в принимающих системах. А даже если он реализован, пользователи протокола не всегда умеют его правильно использовать. Учились все мы от простого к сложному, и работу с объектами постигали именно на приложениях работы с базой данных. В которых для создания объекта (например, покупателя) надо выполнить INSERT и получить от базы данных уникальный ключ, который далее идентифицирует объект и позволяет с ним работать — изменять при указании адреса, или ссылаться на него из других объектов, создавая заказы для покупателя.

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

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

Впрочем, некоторые современные базы данных, рассчитанные на работу в нагруженных системах, например, Cassandra, могут воспроизводить шаблон идемпотентных операций — в них UPDATE создает запись при ее отсутствии, а INSERT отсутствует. Мне, кстати, это как-то здорово помогло, когда я отстаивал протокол идемпотентных операций перед разработчиками, которым он казался каким-то чересчур сложным и непривычным — они осознали: «А, это же так, как в Кассандре сделано, только нам придется вручную написать! OK, это понятно».

Создание уникальных идентификаторов

Таким образом, идемпотентные операции, прежде всего, должна обеспечивать принимающая система. Но и передающая должна понимать такую организацию протокола и корректно его использовать. Сложный момент в этом случае – уникальная идентификация объектов, например, платежей или покупателей, если они могут создаваться из разных систем. А еще одна система может их создавать из нескольких агентов, процессов или потоков, запущенных параллельно и обрабатывающих различные входящие потоки. И здесь появляется вопрос — откуда взять уникальный номер?

Есть два варианта. В-первых, можно использовать номера из последовательности или guid. Но тогда важно сначала записать этот номер в собственную БД и завершить транзакцию, и только потом — отправлять сообщение. А в следующий раз отправлять сообщение с использованием этого номера. Почему? Потому что процесс мог упасть сразу после отправки сообщения, которое, тем не менее, успело уйти адресату и было обработано. Об этой тонкой разнице — что изменения в БД сбрасываются и фиксируются только по завершении транзакции, в то время как сообщение в очередь улетает по другим каналам и сразу после обращения, — часто забывают, что приводит к дубликатам, причем не всегда на уровне кода, который разработчик пишет явно. Это может быть скрыто в недрах фреймворка, который облегчил труд разработчика, но создал проблемы для службы поддержки, разбирающейся с инцидентами.

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

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

Отдельно нужно решать задачу, когда мы проектируем централизованный сервис ведения сотрудников или покупателей, а заведение объектов может выполняться не через собственные интерфейсы, а через интеграцию из других систем. Различайте ситуации online-интеграции — когда сервис обеспечивает уникальность обозначения через специальные вызовы API и периодическую интеграцию, когда новый клиент уже заведен в передающей системе и для него зарегистрированы заказы и договора, связанные ссылками. Таких систем может быть несколько, и нам нужно различать, занесен ли в базу данных один и тоже клиент дважды или совершенно случайно и практически одновременно пришли делать заказ однофамильцы. В этом случае мы можем использовать дополнительную идентификацию — по номеру телефона, электронной почте или номеру паспорта, — только опять же учитывая, что у человека может быть несколько телефонов и паспортов, а еще муж может делать заказ для жены, указывая при этом свой email, но ее телефон — для курьера (или наоборот).

Так что идентификация сущностей — нужна, и очень плохо, когда в какой-то системе она невозможна. Как-то, разрабатывая интеграцию с одной легаси-системой, мы столкнулись с тем, что таблица платежей при выгрузке не имеет первичного ключа — платежи были привязаны к договорам, имели платежные реквизиты, дату и сумму. И оказалось, что платеж на крупную сумму клиент технически мог разбить на несколько из-за каких-то своих соображений, например, прислав два одинаковых платежа на 50 тысяч вместо одного на 100. Или пять по 20. В одной выгрузке могли прийти четыре одинаковых платежа по 30 тысяч, а в следующей одна из сумм менялась — приходило 20 + 30 + 30 + 30, — потому, что при вводе платежей сумму последнего забыли поправить, а потом ошибку заметили. Полная витрина выгружалась несколько раз в день, и приходилось сопоставлять поступившие данные с уже существующими, учитывая эту особенность платежей.

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

Потери и дубли сообщений

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

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

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

Идемпотентные операции в этом случае хороши тем, что сообщение действительно можно попробовать обработать повторно, и если проблема была временной и связана, например, с блокировкой ресурсов, приводившей к отказу по timeout или deadlock, то повторная обработка будет безопасна и успешна. А вот в случае повторяемых ошибок это не поможет, и надо принять меры, чтобы после запуска очередного задания на обработку не начиналась обработка 100500 сообщений с воспроизводимыми ошибками, с которыми служба поддержки еще не успела разобраться по каким-то причинам.

Пример – передача документов

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

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

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

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

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

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

Публичные протоколы

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

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

На этом я завершаю вторую статью про интеграцию. Продолжение следует…

28 января в 19:00 МСК ждём вас на онлайн-митап «DevOps Life Cycle», который вместе проводят Онтико и Deutsche Telekom IT Solutions.

Инженеры и технические менеджеры Deutsche Telekom IT Solutions поделятся опытом, как они находят узкие места, используют метрики, ищут пути решения для разных кейсов и готовят IAC. Поговорим о жизненных циклах создания продукта и обсудим проблемы, возникающие на разных итерациях жизненных циклов, pipelines и workflows и подходы к их решению.

Участие бесплатное, необходима регистрация.

А на конференцию DevOpsConf 2021 открыт прием заявок на доклады. Конференция пройдет 20 и 21 мая в Radisson Slavyanskaya. Это хорошая возможность предложить‎ свои инсайты экспертному сообществу и в то же время обсудить идеи и предложения по развитию своих процессов и помочь единомышленникам в их задачах. А еще можно получить тренинги и консультации от ведущих специалистов отрасли и гуру публичных выступлений, да и просто засветиться на всю страну 🙂

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *