Семантики доставки событий в распределенных системах

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Привет! Меня зовут Павел Агалецкий, я ведущий разработчик в юните Platform as a Service в Авито. 

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

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

Какие бывают семантики доставки событий

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

Все процессы будем рассматривать на примере системы из двух микросервисов и посредника между ними (брокера). 

Брокер принимает событие от сервиса-отправителя (продюсера), сохраняет и обрабатывает его, а затем передает сервису-получателю (консьюмеру)
Брокер принимает событие от сервиса-отправителя (продюсера), сохраняет и обрабатывает его, а затем передает сервису-получателю (консьюмеру)

Гарантии доставки

Гарантия доставки одного конкретного события определяет, получит ли сервис-получатель (консьюмер) сообщение. 

Есть 3 типа гарантии доставки:

  • at least once — событие точно будет доставлено хотя бы один раз, но может и больше;

  • at most once — событие будет доставлено один раз, но может не быть доставлено вообще;

  • exactly once — событие будет доставлено строго один раз. 

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

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

Гарантии сохранения последовательности

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

Выделяют два типа гарантий последовательности: полное сохранение порядка и возможное нарушение.

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

Как сила семантик влияет на систему

Семантики различаются по силе: чем больше и строже гарантии доставки, тем они сильнее. Например, сильные семантики — это гарантированная однократная доставка (exactly once) и строгое соблюдение порядка событий. Слабые — полное отсутствие гарантий доставки и возможное нарушение порядка.

Гарантии не всегда абсолютно сильные или абсолютно слабые, могут быть и промежуточные состояния. Например, гарантия at most once, при которой событие будет доставлено один раз или не доставлено вовсе, слабая гарантия, но не абсолютно

Сила гарантий влияет на разные факторы:

  • Скорость разработки. Чем слабее гарантии, тем сложнее разрабатывать систему: приходится учитывать больше факторов, чтобы обеспечить доставку событий. 

  • Скорость работы системы. При слабых гарантиях не нужно выполнять дополнительные проверки условий, и процессы проходят быстрее. А вот сильные гарантии расходуют все ресурсы инфраструктуры — процессорное время, сеть, память, и в итоге замедляют обмен.

Чем больше гарантий, тем более сложным становится протокол взаимодействия отправителей, получателей и брокера. Простой и быстрый брокер умеет доставлять события в случайном порядке, но не гарантирует, что все они будут переданы. Работать с таким брокером сложно: непонятно, все ли события доставлены, корректные ли они. Надёжный брокер с сильными гарантиями написать сложнее, и работать он будет медленнее. 

Слабые гарантии

Сильные гарантии

✔️ Ускоряют обмен событиями

✖️ Замедляют обмен событиями

✖️ Усложняют разработку

✔️ Делают разработку проще

✔️ Требуют простого брокера

✖️ Требуют сложного брокера

Почему гарантии не работают

В распределённых системах есть только две проблемы: 2. Доставка строго один раз.1. Гарантированный порядок событий.2. Доставка строго один раз.
В распределённых системах есть только две проблемы: 2. Доставка строго один раз.1. Гарантированный порядок событий.2. Доставка строго один раз.

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

Добиться выполнения сильных гарантий не так просто — есть факторы, которые могут мешать их работе. Чтобы понять, какие факторы мешают семантикам выполняться, разберемся в работе брокера и во взаимодействии в парах «продюсер–брокер» и «брокер–консьюмер».

Взаимодействие между продюсером и брокером

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

1. Брокер не получает события. Например, при обмене событиями используется протокол UDP, и брокер по каким-то причинам не получает пакеты. В итоге события, которые отправил продюсер, не доходят до него. 

2. Брокер не сохраняет события. Допустим, мы используем протокол, который гарантирует доставку сообщений. Но произошел сбой и брокер не сохраняет событие, а пересылает сообщения в dev/null. В итоге до консьюмера сообщения не доходят.

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

4. Продюсер не уверен в порядке событий. Допустим, у нас есть два инстанса сервиса А, каждый отправляет своё сообщение. Предполагается, что они синхронизированы между собой и событие 2 должно отправляться после события 1. Однако этому могут помешать задержки инстансов в отправке событий или особенности работы брокера. Представим, что в нашем примере брокер по какой-то причине обрабатывал событие 1 в два раза дольше положенного. В результате консьюмер получит его позже, чем событие 2 — порядок сообщений нарушится.

Все эти проблемы можно преодолеть, если ввести дополнительные механики. Например, убедиться, что брокер получил сообщение, можно с помощью подтверждения (ack). Однако у каждой дополнительной механики есть недостатки: так, добавив подтверждение, мы получим задержку публикации из-за двустороннего обмена.

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

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

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

Взаимодействие между брокером и консьюмером

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

1. Консьюмер не получает события. Например, мы не знаем, получил ли консьюмер событие и обработал ли его.

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

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

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

3. Неверный порядок отправки. Представим, что брокер передает события в несколько потоков для разных консьюмеров. 

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

Работа брокера

Базовые составляющие брокера: API для продюсера и консьюмера, слой хранения (persistence layer)
Базовые составляющие брокера: API для продюсера и консьюмера, слой хранения (persistence layer)

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

Одна из возможных схем внутреннего устройства брокера на примере Apache Kafka
Одна из возможных схем внутреннего устройства брокера на примере Apache Kafka

Одна из возможных схем внутреннего устройства брокера на примере Apache KafkaНа примере Kafka видно, что слой хранения состоит из нескольких партиций для каждого топика событий. Разберёмся, как можно распределять эти события при получении и отправке. 

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

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

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

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

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

3. Отдельные компоненты брокера недоступны. Например, сломался один из брокеров, к которому были подключены клиенты. Если при этом брокер вообще один — система будет неработоспособной.

Или, например, сломался сервер, на котором хранятся данные партиций — это тоже может быть проблемой.

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

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

Как обеспечить работоспособность всех семантик

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

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

  • Организовать окна дедупликации. Это поможет убирать дубли сообщений.

  • Настроить брокер и консьюмер так, чтобы они отправляли сообщения о получении.

  • Настроить шардирование по ключу на брокере и консьюмере. Так брокер будет определять нужную партицию, а консьюмер — восстанавливать верный порядок событий при получении.

  • Реплицировать данные, чтобы не потерять сообщения.

  • Отслеживать таймаут обработки.

  • Отслеживать обработанные события на консьюмере.

  • Планировать алгоритм миграции топологий.

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

Как найти баланс, между простотой системы и выполнением гарантий

Я советую искать компромисс и исходить из реальных потребностей системы:

  • Скольким сервисам нужны exactly once события? В большинстве случаев повторная обработка события не несет никаких последствий.

  • Может ли получатель события самостоятельно реализовать нужные ему гарантии? Часто достаточно добавить небольшой блок кода для обработки события при получении. В таком случае получатель станет сложнее, но система в целом останется простой.

  • Во сколько обойдется реализация? Бывает, что дешевле получить событие несколько раз или не получить вообще, чем поддерживать сильные гарантии.

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

В Авито мы даем гарантии нарушения порядка и доставки at least once. При этом порядок обычно соблюдается, нарушения — это скорее исключения
В Авито мы даем гарантии нарушения порядка и доставки at least once. При этом порядок обычно соблюдается, нарушения — это скорее исключения

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

Такие гарантии мы обеспечиваем с помощью четырёх условий: 

  • продюсер события всегда ожидает ответ от брокера;

  • данные реплицируются;

  • консьюмер всегда отправляет ответ о получении брокеру;

  • время обработки события регулируется таймаутом в несколько часов.

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

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

Предыдущая статья: Go's Garbage Collection: как работает и почему это важно знать

Источник: https://habr.com/ru/companies/avito/articles/755014/


Интересные статьи

Интересные статьи

Знаем, что HR-отделы всегда ищут новые возможности для улучшения качества работы и развития сотрудников компании. Если вы сейчас в поиске таких способов, то эта статья может быть полезной для вас. Под...
Мы привыкли к линейным алгоритмам. Нас учили строить их на информатике в школах, затем на младших курсах в колледжах и институтах. Это был бейсик или паскаль в консоли. Часто учителя просто выдава...
В этом выпуске подкаста «ITMO Research_» — Иван Иорш, доктор физико-математических наук, профессор Нового физтеха и руководитель лаборатории «Фотопроцессы в мезоскопических системах» Университета ИТМО...
Кто мог подумать, что апрельский дайджест выйдет уже в условиях строгого карантина, когда организаторы будут вынуждены переносить мероприятия в онлайн или откладывать до осени! Но трудности з...
Недавно я разбирался с API Web Workers. Очень жаль, что я не уделил время этому отлично поддерживаемому инструменту раньше. Современные веб-приложения очень требовательны к возможностям главного ...