Что Такое Идемпотентность в Программных Системах? Начало
Вы часто встретите термин «идемпотентный» в ПО, особенно при разработке распределённых облачных систем. На первый взгляд эта концепция кажется простой для понимания, но важно знать тонкости идемпотентности, если вы хотите, чтобы ваши системы были масштабируемыми и надёжными.
Что это?
Возьмём для примера пульт ДУ от телевизора, который наверняка валяется у вас в гостиной. На нём обычно есть кнопки вкл./выкл., кнопки перемещения по каналам вперёд/назад и кнопки с цифрами, переключающие на конкретный канал. Так вот, независимо от того, сколько раз вы будете нажимать кнопку с цифрой канала, например, 5, вы всегда будете попадать на 5й канал (отбросим для простоты возможность включения двузначных каналов). Поэтому эта операция идемпотентна. Однако, нажатие кнопки вкл./выкл. или кнопок перемещения по каналам вверх/вниз каждый раз будет приводить к разному результату, в зависимости от текущего состояния системы, и многократное нажатие приведёт в систему в другое (неизвестное) состояние. Поэтому эта операция не идемпотентна.
Почему это важно?
Концепция идемпотентности важна в распределённых системах, поскольку трудно получить действительно надежные гарантии того, сколько раз команда будет вызываться или обрабатываться.
Сети по своей сути ненадёжны, поэтому большинство распределённых систем не могут гарантировать однократную доставку или обработку сообщений, даже при использовании брокера сообщений, вроде RabbitMQ, Azure Service Bus или Amazon SQS. Большинство брокеров предлагают доставку «хотя бы раз», полагаясь на то, что логика повторяет обработку столько раз, сколько необходимо, пока не будет подтверждено, что обработка сообщения завершена.
Это означает, что, если сообщение не может быть обработано по какой-либо причине, оно будет отправлено повторно. Допустим, у нас есть обработчик сообщений, как ТВ, описанный выше. Если сообщение однозначно, как кнопка 5, то легко написать код обработки, независимо от того, сколько раз получено сообщение. Но если это сообщение «следующий канал», ситуация усложняется. Поэтому, если каждый обработчик сообщений в нашей системе идемпотентен, мы можем повторять любое сообщение столько раз, сколько захотим, и это не повлияет на общую корректность системы.
Почему бы не сделать все обработчики идемпотентными?
Это сложно. Допустим, нужно создать нового пользователя в БД и опубликовать событие UserCreated, чтобы другие части системы знали, что произошло. Псевдокод будет примерно таким:
Handle(CreateUser message)
{
DB.Store(message.User);
Bus.Publish(new UserCreated());
}
Теоретически - ОК, но что, если брокер сообщений не поддерживает транзакции? (Спойлер: большинство не поддерживают!) Если между этими двумя строками кода произойдет сбой, запись в базе данных будет создана, но сообщение UserCreated не будет опубликовано. При повторной отправке сообщения будет записана новая запись в БД, а затем сообщение будет опубликовано.
Эти дополнительные записи-зомби создаются в БД, большую часть времени дублируя действительные записи, без какого-либо сообщения в остальную часть системы. Это может быть трудно даже заметить, и ещё труднее потом навести порядок.
А если изменить порядок, и сначала отправлять сообщение, а потом сохранять пользователя? Теперь у нас обратная проблема. Мы создаём призрачное сообщение — объявление остальной части системы о событии, которое на самом деле не произошло. Если кто-нибудь попытается найти этого пользователя, то не найдёт, поскольку он не был создан. Но другие процессы продолжат работать на основе этого сообщения, возможно, выставляя счета, но не доставляя заказов!
При проектировании надёжной системы нужно думать, что произойдёт, если на какой-либо строке кода кто-то выдернет кабель питания сервера.
Окончание следует…
Источник: https://particular.net/blog/what-does-idempotent-mean