Синхронизация Различных Хранилищ Данных

Всем привет! Я Станислав Бушуев, инженер-программист Semrush. Сегодня я хочу поделиться идеями, как можно реализовать синхронизацию данных между разными хранилищами.

Такие задачи иногда возникают на работе, например, при удалении пользовательских данных в рамках Общего регламента по защите данных ( GDPR ) и Закон Калифорнии о конфиденциальности потребителей ( CCPA ).



Синхронизация различных хранилищ данных

Следовать этим законам несложно, если вы поддерживаете средний веб-сайт или небольшой продукт. Скорее всего, в этих случаях используется один из популярных фреймворков, а пользовательская таблица хранится в известной базе данных (MySQL, PostgreSQL).

Вы получили запрос на удаление всех пользовательских данных? Без проблем! Удалите строку в таблице, и все готово.

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

В нашем случае ситуация немного сложнее.

В течение 13 лет в Семраш создали более 50 инструментов, каждый из которых поддерживается десятками команд разработчиков.

Несколько лет назад каждая команда вынесла код своего инструмента в отдельный микросервис; мы получаем пользовательские данные от пользовательского сервиса.

Но мы так или иначе храним всю информацию, которая необходима для работы инструмента.

Таким образом, перед нами встал вопрос: как синхронизировать хранилища данных микросервиса и пользовательского сервиса.



Методы синхронизации

1. Время от времени мы можем обращаться в службу поддержки пользователей в соответствии с ОТДЫХ API :
  
  
  
  
   

$ curl '< http://user-service.internal.net/api/v1/users/42 >' [ { "id": 42, "registration_date": "2015-03-08 01:00:00", "email": "[email protected]", "name": "John", .



Можно даже сразу отправить список пользователей на проверку, но это как-то странно.

Удаление пользователей происходит не так часто, чтобы вызвать DDoS-атаки на сервис.

2. Событийно-ориентированная архитектура - еще один подход к решению нашей проблемы.

Здесь появляются две сущности: генератор сообщений (Publisher) и подписчик (Subscriber), который читает канал событий (тема).

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

В этом случае вам необходимо согласовать протоколы взаимодействия API: REST, JSON-RPC, gRPC, GraphQL, OpenAPI или что-то еще.

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

  • Асинхронная автоматическая синхронизация хранилищ данных.

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

  • Синхронизация данных из различных хранилищ осуществляется отдельно от (уже загруженного) пользовательского сервиса.

Минусы:
  • Недостаток, вытекающий из первого преимущества: несогласованность данных между пользовательскими сервисами и другими микросервисами.

  • Никаких транзакций: генерируются простые сообщения.

  • Обратите внимание, что сообщения в очереди могут повторяться.

В целом все перечисленные преимущества и недостатки весьма условны и зависят от конкретных задач.

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



Реализация событийно-ориентированной архитектуры на примере Pub/Sub из Google Cloud

Альтернатив много: Kafka, RabbitMQ, но наша команда выбрала решение от Pub/Sub Google Cloud, так как мы уже используем Google Cloud (подробнее можно прочитать в статье моего коллеги Никиты Шальнова: Что такое неизменяемая инфраструктура ), и проще настроить тот же Kafka или RabbitMQ.

Синхронизация различных хранилищ данных

В нашем случае Publisher — это пользовательский сервис, а Subscriber — микросервис команды конкретного инструмента.

Подписчиков может быть любое количество (например, Подписка).

Учитывая, что у Semrush большое количество инструментов и команд, очередь с подписчиками нам подойдет идеально:

  • Каждый читает очередь с той частотой, которая ему нужна.

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

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

  • Абонент предоставляет протокол REST, но для упрощения разработки также имеются клиенты для различных языков программирования: Go, Java, Python, Node.js, C#, C++, PHP, Ruby.
Вы можете использовать одну тему с разными сообщениями, например, у нас есть несколько типов сообщений: смена пользователя, смена доступа пользователя и так далее.



Пример реализации

Создание темы и подписчика:

gcloud pubsub topics create topic gcloud pubsub subscriptions create subscription --topic=topic

Более подробную информацию можно найти в документация .

Создание пользователя для чтения темы:

gcloud iam service-accounts create SERVICE_ACCOUNT_ID \ --description="DESCRIPTION" \ --display-name="DISPLAY_NAME" gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com" \ --role="pubsub.subscriber" gcloud iam service-accounts keys create key-file \ [email protected]

Скачанный ключ в формате json необходимо сохранить и отправить в сервис.

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

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

Пользователь для публикации сообщений создается аналогично, за исключением роли: --role="pubsub.subscriber" → --role="pubsub.publisher".

Например, давайте возьмем один из наших микросервисов, работающий на Python с Celery. Для сообщений от пользовательского сервиса существует схема, описанная с помощью протокольных буферов.



import json import os import celery from google.cloud import pubsub_v1 from google.oauth2 import service_account from user_pb2 import UserEventData PUBSUB_SERVICE_ACCOUNT_INFO = json.loads(os.environ.get('PUBSUB_SERVICE_ACCOUNT', '{}')) PUBSUB_PROJECT = 'your project' PUBSUB_SUBSCRIBER = 'subscription' @celery.shared_task def pubsub_synchronisation() -> None: credentials = service_account.Credentials.from_service_account_info( PUBSUB_SERVICE_ACCOUNT_INFO, scopes=['< https://www.googleapis.com/auth/pubsub >'] ) with pubsub_v1.SubscriberClient(credentials=credentials) as subscriber: subscription_path = subscriber.subscription_path(PUBSUB_PROJECT, PUBSUB_SUBSCRIBER) response = subscriber.pull(request={"subscription": subscription_path, "max_messages": 10000}) ack_ids, removed_user_ids = [], [] for msg in response.received_messages: user_event_data = UserEventData() user_event_data.ParseFromString(msg.message.data) removed_user_ids.append(user_event_data.Id) ack_ids.append(msg.ack_id) # Here you can do everything with removed users :) subscriber.acknowledge(request={"subscription": subscription_path, "ack_ids": ack_ids})

А задачу запускаем раз в пять минут, так как удаление пользователей — не такая уж и частая операция:

CELERY_BEAT_SCHEDULE = { 'pubsub_synchronisation': { 'task': 'tasks.pubsub_ubs_synchronisation', 'schedule': timedelta(minutes=5) },

Аналогичным образом реализован пример публикации сообщений в теме на Python. Используйте PublisherClient вместо SubscriberClient и вызывайте публикацию вместо извлечения.

В результате происходит синхронизация удаления пользователей в соответствии с законами GDPR/CCPA. Например, в 7:37 утра произошло массовое удаление учетных записей из службы хранения учетных записей пользователей.

В 7:40 сработало задание на получение данных из Топика.

Все задачи выбраны, локальная база данных синхронизирована.



Синхронизация различных хранилищ данных

В статье мы рассмотрели две архитектуры и остановились на событийно-ориентированной.

Вполне вероятно, что в вашем случае можно обойтись ручной синхронизацией.

Надеюсь, этот материал будет полезен.

Спасибо за внимание! Теги: #Хранилища данных #python #Микросервисы

Вместе с данным постом часто просматривают: