Меня зовут Никита, я бэкенд-разработчик в антифрод-команде Ситимобил.
Сегодня я поделюсь с вами историей о том, как мы перевели наш сервис из монолита в отдельный сервис, как мы вообще пришли к этому решению и с какими проблемами столкнулись.
Сначала расскажу немного о нашем сервисе.
Антифрод 101 Наш антифрод — это свод правил выявления заказов, содержащих признаки мошенничества и закономерности мошенничества.
Пример мошенничества с вождением Водители, пользующиеся услугами агрегаторов такси, имеют возможность получать бонусы за короткие поездки, на чем и пытаются заработать нечестным путем деньги водители-мошенники.
Например, мы видим, что у одного водителя есть n заказов подряд с одним и тем же клиентом.
Нам непонятно, что они там делали, но мы можем с большой уверенностью сказать, что это мошенничество и отменить эти приказы.
Пример мошенничества с клиентом Клиенты получают бонусы, если приглашают в приложение новых клиентов.
Клиенты-мошенники регистрируют на своем устройстве несколько новых клиентов, за что могут потерять все начисленные бонусы.
Для проверки водителя на мошенничество мы запускаем все проверки и в результате получаем события, каждое из которых указывает на наличие нужного паттерна в поданных на вход заказах.
Чеки можно разделить на несколько типов:
- Проверка клиента/драйвера на случай каких-либо изменений (например, добавления новой кредитной карты).
- Проверяем 1.n последних заказов.
- Специальное: проверка корректности работы водителей, участвующих в конкретных акциях.
А для визуального контроля срабатывания правил мы создали веб-страницу с различными отчетами и большим набором фильтров.
Добавление новой проверки происходит следующим образом: опишите схему мошенничества, закодируйте ее в сервисе, запустите новое правило в тестовом режиме и наблюдайте.
При необходимости настройте правило и включите его.
Проблемы предыдущей архитектуры Раньше компания-партнер могла получить деньги только в том случае, если все ее водители были проверены на мошенничество.
Антифрод работал внутри PHP в одном потоке.
Он не масштабировался без костылей; в часы пик были очереди на проверки.
Сами проверки никак не распараллеливались, и добавление каждого нового правила неизбежно увеличивало время обработки.
Старый антифрод «перерос» свою модель базы данных, и работать с базой данных стало невозможно: во время работы периодически откладывали базу данных, что в монолитной архитектуре приводило не только к проблемам антифроду, но и весь бизнес в целом.
Отчеты строились медленно.
Чтобы вручную посмотреть в базе данных казалось бы простые вещи, иногда приходилось ОБЪЕДИНЯТЬ пять и более таблиц, не говоря уже о более сложных вещах.
Бизнес рос, и эти проблемы требовали быстрого решения.
Еще мне хотелось проверять водителей на мошенничество «на лету» (после каждой поездки).
Какие варианты у нас были:
- Вспомните, что у вас есть.
Переработать модель данных на новую.
- Напишите сервис с нуля, с возможностью масштабирования «из коробки».
В качестве основного инструмента распараллеливания был выбран Golang, в котором компания имеет хороший опыт. Было решено двигаться в два этапа.
Этап первый: переход на новый язык Мы остались со старой моделью данных (да, она скрипит, но пока работает).
Мы начали строить сервис с нуля и за пару месяцев перенесли основной функционал и большую часть проверок.
Мы добились полной функциональности сервиса.
Теперь каждую проверку одного заказа можно запускать параллельно, что существенно сокращает время обработки.
Сравнительные характеристики скорости обработки: раньше на анализ всех драйверов уходило 6 часов, сейчас — 25 минут. Этап второй: выбор модели хранения Для текущей работы нам понадобилась как OLTP-подобная база данных для анализа мошенничества, так и OLAP-база данных для формирования отчетов.
Текущая схема данных не поддерживала антифрод-сценарии, от слова «никак».
Выбор был между:
- Новая модель SQL (правильно денормализованная) для текущей работы, а также ClickHouse для отчетов.
- Эластичный.
Он легко масштабируется, «из коробки» имеет индексы по любому полю, что позволяет настраивать фильтры в отчетах по своему усмотрению.
Мы денормализовали модель, чтобы не было необходимости выполнять JOIN между индексами Elastic. Предупреждение Если вы также решите выбрать Elastic в качестве базы данных, будьте осторожны.
При настройках по умолчанию Elastic под нагрузкой может начать возвращать частичные результаты поиска.
Например, время ожидания запроса на нескольких шардах истечет, а код ответа будет 2xx. Если вас не устраивает такое поведение и вы предпочитаете получить ошибку поиска (например, чтобы вы могли отследить ее позже), вы можете настроить это поведение с помощью параметра allow_partial_search_results .
Действующая схема антифрод работы Напомню, что основная логика, связанная с поездками, живет в монолите, а вся информация о заказах по-прежнему находится в MySQL. Когда поездка завершена, монолит перемещает заказ из таблицы активных заказов в таблицу закрытых заказов и отправляет сообщение нашему сервису через RabbitMQ, чтобы мы проверили конкретный заказ.
При получении сообщения от RabbitMQ можно было бы сразу вызвать горутину для обработки сообщения и перейти к получению следующего сообщения, но этот подход никак не контролирует количество горутин.
Поэтому в сервисе количество обработчиков регулируется динамически с помощью рабочий пул .
Приступая к обработке сообщения, антифрод-сервис заходит на слейв MySQL, считывает все нужные нам для заказа данные из разных таблиц, записывает их себе в Elastic, а затем отправляет себе сообщение для проверки того же заказа.
Захватывает, когда отмечено распределенная блокировка on Redise, чтобы предотвратить параллельную обработку объекта во время особенно интенсивных запросов, таких как частые обновления драйверов или клиентов.
Если мошенничество обнаружено, сервис отправляет сообщение об этом в монолит.
При формировании отчетов в админке сервис вызывается через REST API. Все это позволяет оказывать минимальное влияние на монолит.
Теперь более подробно Читатель, возможно, заметил пару проблем:
- Elastic не гарантирует, что записанные данные будут доступны для поиска сразу, а только после обновления индекса, что сам Elastic делает в фоновом режиме через определенные промежутки времени.
Как тогда мы можем проверить только что добавленный порядок?
- Что делать, если слейв MySQL отстает и там еще нет порядка?
Наш RabbitMQ внутренне состоит не просто из двух очередей (входящей и исходящей), но и из третьей — очереди повторов.
У этой очереди есть производитель, но нет потребителя.
У него настроена политика недоставленных сообщений: после истечения срока его TTL сообщение возвращается обратно во входящую очередь, и мы это сообщение обработаем.
Другими словами, если мы получили сообщение о проверке ордера, но у слейва этого ордера еще нет, то мы просто будем каждый раз помещать это сообщение в очередь повторов, пока ордер не появится.
Используя такой подход, вы можете отследить временные ошибки, а в случае превышения количества попыток обработки данного сообщения отбросить его с записью об ошибке в журнале.
Теперь вернемся к первой проблеме.
Самый быстрый и худший вариант — обновлять индекс во время любой операции записи.
Разработчики Elasticsearch советовать Будьте предельно осторожны с этим подходом, так как это может привести к снижению производительности.
Есть еще один вариант: сразу передать всю информацию о заказе в сообщении, а не читать ее со слейва.
Но тогда размер сообщения увеличится на несколько порядков, что увеличит нагрузку на нашего кролика, а мы пытаемся его защитить.
Кроме того, структура считываемых данных меняется довольно часто, и хотелось бы избежать изменения модели как в монолите, так и в сервисе.
Может, проверишь заказ, как только прочитаешь его с раба? Можно, но большинство наших проверок все равно делают вывод по нескольким заказам, то есть по остальным заказам все равно придется заходить в базу данных.
Зачем усложнять логику, если можно использовать тот же механизм очереди повторов? Установив TTL сообщений в очереди повторов больше интервала обновления индекса Elastic, мы раз и навсегда забудем о первой проблеме.
Подробнее о механизме недоставленных сообщений можно прочитать, например.
здесь .
Немного о наших тестах Допускать ошибки в логике антифрод-правил опасно: это может привести к массовым списаниям.
Именно поэтому мы стремимся к 100% покрытию важных участков кода.
Для этого мы используем библиотеку свидетельствовать , мокаем внешние зависимости и проверяем правила на работоспособность.
У нас также есть функциональные тесты, которые проверяют основной процесс обработки и проверки заказов.
Вместо выводов Переписав весь антифрод, мы получили уверенность в нашем сервисе для дальнейшего роста бизнеса в течение следующих нескольких лет. Мы решили важную бизнес-задачу, благодаря которой честный водитель уверен в получении своих честных денег сразу после совершенной поездки.
Конечно, некоторые задачи, которые выполняет наш сервис, остаются за завесой NDA. А некоторые задачи просто не уместились бы в одну статью.
Возможно, в следующий раз я вернусь с рассказом о том, как мы анализируем действия пользователей на предмет мошенничества, где нагрузка на порядки выше.
Теги: #мессенджеры #Анализ и проектирование систем #rabbitmq #дизайн #elasticsearch
-
Мини-Совет: Калибровка Дисплея В Linux
19 Oct, 24 -
Бонусы За Рекомендации: Дайте Два!
19 Oct, 24 -
Почему Я Пишу Игры На C (Да, На C)
19 Oct, 24 -
Харбоспам
19 Oct, 24 -
Канбан Команды Pvs-Studio. Часть 1: Agile
19 Oct, 24