Slide 1

Slide 1 text

Асинхронность неизбежна: как мы проектировали сервис уведомлений Алексей Ситка Senior Go developer

Slide 2

Slide 2 text

● Программирую с прошлого века ● 13 лет в коммерческой разработке ● Создавал и распиливал монолиты ● Проектировал микросервисные приложения из десятков подсистем ● Основной стек Golang, PHP, Node.JS Обо мне

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

быстро подключаться к любому значимому доменному событию иметь возможность гибкой настройки уведомлений изолировать синхронные запросы в соседние системы от основных процессов легко встраивать новые транспорты сообщений рассылать сообщения в определенное время часового пояса пользователя возможность экономии денег через каскад транспортов с эскалацией их стоимости Требования к новому сервису: 1 2 3 4 5 6

Slide 5

Slide 5 text

Стек

Slide 6

Slide 6 text

Давайте попробуем спроектировать сервис

Slide 7

Slide 7 text

Элементарный сервис уведомлений получили событие → создали уведомление → отправили сообщение Консьюмер Рендеринг Отправка Консьюмер топика

Slide 8

Slide 8 text

Элементарный сервис уведомлений получили событие → создали уведомление → отправили сообщение Рендеринг Отправка Консьюмер топика

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Модуль 1. Собираем потоки входящих событий Нужна модель реакции на события из внешних систем, позволяющая определять необходимость этих событий без доработок или с минимальными доработками в мастер-системах

Slide 11

Slide 11 text

Читатель топика Читатель топика Модуль 1. Собираем потоки входящих событий Рендеринг Отправка Консьюмер топика

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

компонент, где разные события превращаются в унифицированное событие уведомления Читатель топика Рендеринг Отправка Читатель топика Консьюмеры топиков Модуль 2. Унификация потока внутренних событий Диспетчер внутренних событий Идентификатор Тип (триггер) Набор параметров

Slide 14

Slide 14 text

Требование #2 — необходим функционал настройки уведомлений по разным параметрам: ● стране ● типу заказа ● типу доставки ● селлеру ● методу доставки ● etc

Slide 15

Slide 15 text

Модуль 3. Блок маппинга — сопоставление потока событий с настройками уведомлений Рендеринг Отправка Консьюмер топика Диспетчер внутренних событий Маппинг

Slide 16

Slide 16 text

Отправка Рендеринг Маппинг Модуль 4. Рендеринг — гибкая шаблонизация сообщений Рендеринг предусмотрен, но для подготовки текста сообщения могут понадобиться дополнительные данные, доступные по API. Консьюмер топика Диспетчер внутренних событий

Slide 17

Slide 17 text

Требование #3 — необходимо архитектурно изолировать тяжелые синхронные запросы от основных процессов

Slide 18

Slide 18 text

Модуль 5. External Data Provider (XDP) External Data Provider Отправка Рендеринг Маппинг результат запрос Консьюмер топика Диспетчер внутренних событий

Slide 19

Slide 19 text

Требование #4 — возможность достаточно легко встраивать новые транспорты сообщений

Slide 20

Slide 20 text

Модуль 6. Отдельный сервис для транспортной инфраструктуры уведомлений Inbox Push SMS A P I G a t e w a y Постановка на отправку Отправка

Slide 21

Slide 21 text

Схема сервиса на данный момент проектирования Постановка на отправку External Data Provider Рендеринг Маппинг результат запрос Консьюмер топика Диспетчер внутренних событий

Slide 22

Slide 22 text

Требование #5 — рассылку сообщений можно осуществлять только в определенное время

Slide 23

Slide 23 text

Модуль 7. Шедулинг - отправка по расписанию Шедулинг Постановка на отправку External Data Provider Рендеринг Маппинг Консьюмер топика Диспетчер внутренних событий результат запрос

Slide 24

Slide 24 text

Буфер со скользящим указателем База недостаточно быстрая Поэтому используем структуру ZRangeByScore в Redis Ключ сортировки - временная метка timestamp Значение — маршализованное событие 1733900000 1733900000 1734000000 1734005000 []byte… []byte… []byte… []byte… now выбираемые при запросе 1734000000 1734100000 []byte… []byte… 1734100000

Slide 25

Slide 25 text

Расширяем использование External Data Provider Шедулинг Постановка на отправку External Data Provider Рендеринг Маппинг Консьюмер топика Диспетчер внутренних событий

Slide 26

Slide 26 text

External Data Provider - незаменимый компонент Шедулинг Постановка на отправку External Data Provider Рендеринг Маппинг Консьюмер топика Диспетчер внутренних событий

Slide 27

Slide 27 text

Требование #6 — возможность экономии денег через каскад транспортов с эскалацией стоимости

Slide 28

Slide 28 text

Требование #6 — возможность экономии денег через каскад транспортов с эскалацией стоимости Как может выглядеть план отправки: ● Одно СМС ● Пуш + инбокс (сообщение в мобильном приложении) ● Каскад из пуша + инбокса, затем СМС

Slide 29

Slide 29 text

Модуль 8. Реализация плана отправки Шедулинг Постановка на отправку External Data Provider Рендеринг Маппинг Обработка статусов доставки Диспетчер флоу Таймаут ожидания статуса Консьюмер топика Диспетчер внутренних событий

Slide 30

Slide 30 text

Как выглядит архитектура в конечном итоге

Slide 31

Slide 31 text

Постой, Алексей, а при чём тут Golang? ● Способ организации инстансов облегчает компоновку процессов # консьюмеры кафки # основной конвейер обработки # демоны фоновых процессов # обработчики первичной статистики ● Пул воркеров с семафором для скоростной вычитки буфера шэдулера

Slide 32

Slide 32 text

Немного фактуры Количество инстансов по группам: ● api админки - 1 ● демоны фоновой активности - 4 ● группа консьюмеров кафки А - 20 ● группа консьюмеров кафки В - 12 ● группа детекторов событий - 10 ● основной конвейер сообщений - 20 ● группа первичной статистики - 8 Статистика: ● в среднем влетает из разных топиков более 1.5М событий в сутки ● в пике в минуту бывает по 3к ● этот поток порождает около 600к внутренних событий в сутки ● которые превращаются в 450к уведомлений пользователям ежедневно

Slide 33

Slide 33 text

● Нужен паттерн transactional messaging на внутренние очереди ● Для детекторов событий понадобился отдельный инстанс, т.к. им нужен XDP ● Нужна система ретраев на отправку сообщений ● Для отправки сообщений нужен TTL, который обозначает актуальность события ● Сервис накапливает данные, чтобы не разбухала БД, нужна подсистема их очистки О чём мы забыли и что обнаружили, когда всё было готово

Slide 34

Slide 34 text

● Простая задача может оказаться сложной, будьте к этому готовы Выводы

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Q&A 40 Алексей Ситка Senior developer @alexeysitka [email protected]

Slide 41

Slide 41 text

Telegram-канал Lamoda Tech Поделитесь обратной связью на наш митап :)