Перейти к основному содержимому

8.05. Push, Pull, Webhooks

Разработчику Архитектору Инженеру

Push, Pull, Webhooks

Модели доставки данных определяют инициатора передачи информации между системами. Архитектурное различие лежит в том, кто управляет моментом и инициативой передачи: отправитель (источник) или получатель (потребитель).

Существуют Push, Pull и гибридные модели. Эффективная архитектура часто комбинирует модели, например, внутренние микросервисы взаимодействуют через брокер (push), а внешние интеграции реализуются через вебхуки с адаптером, преобразующим сообщения брокера во входящие HTTP-запросы.


Push-модель

Push-модель (от англ. «push» — «толкать») — это подход, при котором сервер отправляет данные клиенту без явного запроса от клиента. Клиент подписывается на события или уведомления, а сервер автоматически передаёт данные, как только они становятся доступны.

Примерами являются вебхуки, WebSockets, и SSE.

Push-модель:

image-14.png

Потребитель периодически инициирует запрос к источнику для получения данных. Источник пассивен — возвращает данные только в ответ на запрос.

Интервал опроса (polling interval) задаётся клиентом и влияет на задержку обнаружения изменений и нагрузку на источник.

Технические характеристики

  • Протоколы: HTTP/1.1, HTTP/2, gRPC streaming (long polling), WebSocket (в гибридных сценариях)
  • Состояние соединения: кратковременное (stateless запросы) или долгоживущее (long polling)
  • Метрики эффективности:
    • Задержка обнаружения: от 0 (при мгновенном опросе) до интервала опроса
    • Избыточный трафик: запросы без изменений данных (пустые ответы)
    • Нагрузка на источник: линейно растёт с количеством потребителей

Сценарии применения

  • Системы с низкой частотой изменений (конфигурации, справочники)
  • Ограниченные клиенты без возможности принимать входящие соединения (IoT-устройства за NAT)
  • Ситуации, требующие строгого контроля клиентом момента получения данных
  • Совместимость с системами, не поддерживающими push-механизмы

Long polling (Python)

import requests
import time

def poll_for_updates(endpoint: str, timeout: int = 30):
while True:
try:
response = requests.get(
endpoint,
params={"timeout": timeout}, # сервер удерживает соединение до появления данных
timeout=timeout + 5
)
if response.status_code == 200 and response.content:
yield response.json()
# При 204 No Content или пустом теле — немедленный повтор
except requests.exceptions.Timeout:
continue
except Exception as e:
time.sleep(5) # экспоненциальная задержка при ошибках

Кратковременный опрос с экспоненциальной задержкой (C#)

public async Task PollAsync(string endpoint, CancellationToken ct)
{
var delay = TimeSpan.FromSeconds(1);
var maxDelay = TimeSpan.FromSeconds(60);

while (!ct.IsCancellationRequested)
{
try
{
var response = await _httpClient.GetAsync(endpoint, ct);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadFromJsonAsync<Data[]>(ct);
if (data?.Length > 0) Process(data);
delay = TimeSpan.FromSeconds(1); // сброс задержки при успехе
}
}
catch (HttpRequestException)
{
delay = TimeSpan.FromSeconds(Math.Min(delay.TotalSeconds * 2, maxDelay.TotalSeconds));
}

await Task.Delay(delay, ct);
}
}

Ограничения

  • Задержка обнаружения изменений до следующего интервала опроса
  • Избыточные запросы при отсутствии изменений (особенно критично при коротких интервалах)
  • Сложность горизонтального масштабирования источника при большом числе потребителей
  • Проблемы с балансировкой: все потребители опрашивают один и тот же эндпоинт, создавая «гребёнку» нагрузки

Pull

Pull-модель (от англ. «pull» — «тянуть») — это подход, при котором клиент периодически запрашивает данные с сервера. Сервер не инициирует передачу данных, пока клиент не сделает запрос.

Примерами является как раз polling, когда клиент регулярно отправляет запросы на сервер, и REST API.

Pull-модель:

image-15.png

Apache Kafka использует Pull-модель (потребители самостоятельно запрашивают данные с сервера, подписываясь на топики и «тянут» данные из топиков), а RabbitMQ использует Push-модель (брокер отправляет сообщения потребителям, «толкает» в потребителей, когда они готовы их обработать).

Источник инициирует передачу данных получателю сразу после возникновения события. Получатель пассивен — ожидает входящие соединения. Требует от получателя доступности по сети и способности обрабатывать асинхронные сообщения.

Технические характеристики

  • Протоколы: HTTP POST/PUT, gRPC unary/streaming, MQTT, AMQP (RabbitMQ), WebSocket (бидирекционный)
  • Состояние соединения: кратковременное (HTTP) или постоянное (WebSocket, MQTT persistent session)
  • Гарантии доставки: зависят от протокола (MQTT QoS 0/1/2, AMQP acknowledgements)

Сценарии применения

  • Высокочастотные события с требованием минимальной задержки (финансовые транзакции, мониторинг)
  • Системы с известным и стабильным множеством получателей
  • Сценарии публикации-подписки с множественными подписчиками (через брокер сообщений)
  • Мобильные приложения с фоновыми сервисами (через платформенные механизмы: FCM, APNs)

Реализация через брокер сообщений (Java + RabbitMQ)

// Подписка на очередь с обработкой push-событий
Channel channel = connection.createChannel();
channel.queueDeclare("notifications", true, false, false, null);
channel.basicQos(1); // fair dispatch

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
try {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
processNotification(message);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
} catch (Exception e) {
// Возврат в очередь с ограничением попыток через DLX
channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
}
};

channel.basicConsume("notifications", false, deliverCallback, consumerTag -> {});

Ограничения

  • Требование к получателю: должен быть доступен по сети и иметь статический адрес
  • Проблема «громкого молчания»: источник не знает о недоступности получателя без механизма подтверждений
  • Сложность управления скоростью передачи (backpressure) при несоответствии скоростей продюсера и консьюмера
  • Риск потери событий при недоступности получателя без брокера с персистентностью

Вебхуки

Сценарии-вебхуки (Webhooks) — это механизм, который позволяет одной системе уведомлять другую систему о событиях в реальном времени. Вместо того чтобы клиентская система периодически запрашивала обновления (например, через API), серверная система отправляет HTTP-запрос на предопределённый URL клиента, когда происходит какое-либо событие.

Как работают вебхуки:

  • клиентская система регистрирует свой URL (конечную точку) на серверной системе.
  • когда на сервере происходит событие (например, новая транзакция или изменение данных), он отправляет HTTP-запрос (обычно POST) на этот URL.
  • клиентская система получает данные и обрабатывает их.

Вебхуки используются, для уведомлений, к примеру, о завершении платежа, о создании лида в CRM-системе. При таком подходе, данные передаются сразу после события, нет необходимости в постоянных запросах к API (такое явление называется polling). Вебхуки используют стандартный HTTP-протокол.

image-13.png

Гибридная модель: получатель регистрирует у источника свой публичный эндпоинт (callback URL). При наступлении события источник выполняет HTTP-запрос (обычно POST) к зарегистрированному эндпоинту. Фактически представляет собой push-механизм, реализованный через запросы инициированные сервером.

Технические характеристики

  • Протокол: HTTP/HTTPS с обязательной аутентификацией колбэка
  • Формат данных: JSON, XML или произвольный бинарный (указывается в Content-Type)
  • Безопасность: подписи запросов (HMAC), секретные токены в заголовках, проверка источника по IP
  • Надёжность: механизмы повторных попыток (retry with exponential backoff), dead-letter для необработанных хуков

Регистрация и управление хуками

POST /api/webhooks
Content-Type: application/json

{
"url": "https://consumer.example.com/webhook/orders",
"events": ["order.created", "order.updated"],
"secret": "hmac-sha256-secret-key",
"active": true
}

Обработка входящего webhook (Python + FastAPI)

from fastapi import FastAPI, Header, HTTPException
import hmac
import hashlib

app = FastAPI()

@app.post("/webhook/orders")
async def handle_webhook(
payload: dict,
x_signature: str = Header(None)
):
# Проверка подлинности через HMAC
expected = hmac.new(
key=WEBHOOK_SECRET.encode(),
msg=await request.body(),
digestmod=hashlib.sha256
).hexdigest()

if not hmac.compare_digest(f"x-sha256={expected}", x_signature or ""):
raise HTTPException(status_code=401, detail="Invalid signature")

# Идемпотентная обработка
event_id = payload.get("event_id")
if event_id and is_event_processed(event_id):
return {"status": "duplicate"}

process_event(payload)
mark_event_processed(event_id)

return {"status": "accepted"} # 200 OK немедленно для подтверждения доставки

Паттерны обеспечения надёжности

  1. Подтверждение доставки: ответ 2xx в течение таймаута (обычно 3–10 секунд)
  2. Повторные попытки: экспоненциальная задержка (1с, 2с, 4с, 8с...) с ограничением по времени (24–72 часа)
  3. Идемпотентность: обработка дубликатов через уникальный идентификатор события (event_id)
  4. Dead-letter queue: перенаправление необработанных хуков после исчерпания попыток

Сценарии применения

  • Интеграция с внешними SaaS-сервисами (платёжные системы, CRM, GitHub)
  • Уведомления о событиях в распределённых системах без общего брокера
  • Сценарии с динамическим набором получателей (каждый клиент регистрирует свой эндпоинт)
  • Системы с ограничениями на исходящие соединения у источника (редко, но возможно)

Ограничения

  • Требование публично доступного эндпоинта у получателя (проблема для внутренних систем)
  • Необходимость реализации защиты от подделки запросов (подписи, секреты)
  • Сложность отладки: асинхронная природа, необходимость логирования входящих запросов
  • Риск DoS при компрометации эндпоинта (требуется рейт-лимитинг на стороне получателя)