Уменьшите побочные эффекты в вашей системе

Поскольку каждый вызов сетевого API больше походил на функции, мы часто не замечали, насколько сложно было создать атомарную операцию в распределенной среде. Например, запись в очередь SQS в AWS может быть такой же простой, как импорт клиента SQS и вызов sendMessage. Эти сетевые вызовы инкапсулируются в функцию. Следовательно, основное приложение будет вызывать функцию и рассматривать ее как атомарную операцию.

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

Простая служба уведомлений

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

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

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

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

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

Проблема

Этот дизайн может столкнуться с двойной публикацией. Давайте посмотрим на эти два сценария:

На последнем этапе написания сообщения DONE может произойти сбой.

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

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

В приведенном выше примере нисходящий поток будет асинхронно опрашивать сообщения из очереди. Поскольку характеристики брокера сообщений инвариантны по крайней мере один раз, нисходящий поток №2 может получить Dup Msg 1, и оба нисходящего потока могут использовать одно и то же значение.

Одна из проблем с дизайном - множественные побочные эффекты. Запись в очередь нисходящего потока и обновление базы данных - два побочных эффекта.

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

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

Однако они не поддерживают транзакции, если вы используете AWS, например SQS или DynamoDB. Таким образом, их использование может вызвать некоторые атомарные проблемы, если не спроектировано тщательно.

Ищите единственный побочный эффект

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

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

По возможности ищите единичные побочные эффекты. Мы можем использовать потоковый подход и создать систему отслеживания измененных данных в базе данных. Вместо записи в очередь нижестоящего и последующего обновления в базе данных, запишите в базу данных и используйте захват измененных данных (CDC), чтобы инициировать запрос на отправку в очередь ниже по потоку. В AWS вы можете использовать потоки DynamoDB и подключать поток к SQS или очереди Kinesis.

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

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

Проблема с разделом

Чтобы решить вторую проблему, масштабирование вниз по потоку вызывает несколько процессов. Мы можем гарантировать, что одна и та же транзакция должна перейти в один и тот же раздел, разделив транзакцию на основе ее идентификатора. С точки зрения AWS, использование Kinesis вместо SQS и установка ключа раздела в Kinesis на идентификатор уведомления.

В этом сценарии в нисходящих экземплярах невозможно получить уведомление номер 2, потому что идентификатор уведомления 2 всегда направляется на сегмент номер 2, а не в сегмент номер 1.

Закрытие

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

Различные облачные сервисы, такие как AWS, позволяют сразу же предоставлять множество ресурсов. Однако многие из их ресурсов не поддерживают транзакции и идемпотентность (в некоторых случаях) из коробки.

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

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

Первоначально опубликовано на https://edward-huang.com.

Больше контента на plainenglish.io