Как обеспечить достоверность и надёжность доставки уведомлений в мониторинговой системе?

Ответ

Чтобы уведомления не терялись и не дублировались, я строю pipeline на основе идемпотентности, подтверждений и dead letter queues.

1. Архитектура с гарантированной доставкой:

  • В качестве брокера сообщений для алертов использую RabbitMQ (с поддержкой подтверждений) или Apache Kafka (с семантикой "at least once" и оффсетами).
  • Alertmanager от Prometheus может отправлять алерты в вебхук, который я пишу сам. Этот вебхук не просто ретранслирует, а помещает алерт в очередь.

2. Обработка с подтверждением (Acknowledgement):

  • Потребитель (worker, отправляющий в Slack/Telegram/Email) берёт сообщение из очереди, обрабатывает и отправляет явное подтверждение (ACK) только после успешной доставки во внешнюю систему. Пока ACK не отправлен, сообщение остаётся в очереди и может быть обработано другим воркером в случае сбоя.
  • Пример логики на Python с Pika (RabbitMQ):
    
    import pika

def callback(ch, method, properties, body): alert_data = json.loads(body) try:

Попытка отправить уведомление в Slack

    send_to_slack(alert_data)
    # Если успешно — подтверждаем обработку
    ch.basic_ack(delivery_tag=method.delivery_tag)
    print(f"Ack sent for alert: {alert_data['alertname']}")
except Exception as e:
    print(f"Failed to send alert: {e}")
    # При неудаче — отрицательное подтверждение с requeue=True
    ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)

connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq')) channel = connection.channel() channel.queue_declare(queue='alerts', durable=True) channel.basic_consume(queue='alerts', on_message_callback=callback, auto_ack=False) channel.start_consuming()



**3. Идемпотентность обработчика:**
*   Мой обработчик уведомлений идемпотентен. Если один и тот же алерт (идентифицируемый по labels и timestamp) пришёл дважды, второй вызов не создаст дублирующего сообщения для пользователя. Для этого можно использовать простой in-memory кэш (с TTL) или запись ID обработанных алертов в Redis.

**4. Dead Letter Queue (DLQ) для сбойных сообщений:**
*   Настраиваю в RabbitMQ политику DLQ. Если сообщение было отклонено (NACK) или не обработано после N попыток ретрая (например, 5), оно автоматически перемещается в отдельную очередь `alerts.dlq`.
*   Сообщения в DLQ я мониторю отдельным алертом. Это позволяет вручную разобраться с проблемными алертами, которые не могут быть доставлены (например, из-за неверного формата или недоступности конечной системы).

**5. Мониторинг самого pipeline:**
*   Сбор метрик: размер очереди алертов, количество сообщений в DLQ, latency обработки.
*   Алерт на застревание очереди (если её размер растёт и не уменьшается), что сигнализирует о проблеме в воркерах уведомлений.

Ответ 18+ 🔞

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

1. Сначала — надёжный фундамент, ёпта. Вместо того чтобы слать алерты напрямую в Slack, где они могут потеряться в пизду, я ставлю между ними брокер. RabbitMQ — отличный выбор, он умеет подтверждать получение. Или Kafka, если у тебя там овердохуища алертов. Alertmanager просто шлёт вебхук в мой сервис, а тот уже аккуратно кладёт сообщение в очередь. Без этой прослойки — никуда.

2. Главный принцип: не подтвердил — не съел. Мой воркер, который шлёт сообщения в мессенджеры, берёт алерт из очереди, но не удаляет его просто так. Сначала он пытается доставить. Получилось? Отлично, шлёт явный сигнал «окей, я всё сделал» (это ACK). Только тогда RabbitMQ удаляет сообщение. Не получилось? Тогда он говорит «не, братан, не вышло, дай другому попробовать» (это NACK), и сообщение возвращается в очередь. Вот пример, как это выглядит в коде:

import pika

def callback(ch, method, properties, body):
    alert_data = json.loads(body)
    try:
        # Попытка отправить уведомление в Slack
        send_to_slack(alert_data)
        # Если успешно — подтверждаем обработку
        ch.basic_ack(delivery_tag=method.delivery_tag)
        print(f"Ack sent for alert: {alert_data['alertname']}")
    except Exception as e:
        print(f"Failed to send alert: {e}")
        # При неудаче — отрицательное подтверждение с requeue=True
        ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)

connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq'))
channel = connection.channel()
channel.queue_declare(queue='alerts', durable=True)
channel.basic_consume(queue='alerts', on_message_callback=callback, auto_ack=False)
channel.start_consuming()

Видишь auto_ack=False? Это ключ! Без него сообщения теряются на раз-два, если воркер упал в процессе.

3. Делаем обработчика умным, чтобы не дублировал. Представь, один и тот же алерт прилетел дважды. Мой воркер не должен слать два одинаковых сообщения в чат, а то все взвоют. Поэтому он идемпотентный. Можно закешировать ID обработанных алертов в памяти (с очисткой через час) или в Redis записывать. Главное — второй раз на одно и то же событие он не среагирует.

4. А что с совсем безнадёжными случаями? Бывает же, что алерт кривой или Slack вообще лёг. Если сообщение пять раз подряд не обработалось и ушло в NACK, то терпения ебать ноль. Нафиг таскать его по кругу? Для этого есть Dead Letter Queue (DLQ). Настраиваешь в RabbitMQ правило, и после N неудач сообщение автоматически летит в отдельную очередь alerts.dlq. Туда я потом заглядываю вручную, разбираюсь, что за хитрая жопа там застряла. И, конечно, на саму DLQ тоже вешаю алерт — чтобы знать, если там что-то скопилось.

5. И последнее — следим за своим хозяйством. Само собой, я мониторю всю эту конструкцию. Собираю метрики: сколько алертов в очереди, сколько в DLQ, как быстро они обрабатываются. Если очередь начала расти как на дрожжах, а воркеры ничего не успевают — это повод для срочного алерта. Чтобы не получилось, что вся система накрылась медным тазом, а мы об этом узнаем последними.

Вот такая, блядь, схема. С ней алерты не теряются и не дублируются, а если что-то пошло не так — ты сразу в курсе. И спокойно спишь, а не бздишь, что пропустил какую-нибудь критическую хуйню.