Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 22 из 25 ~16 мин +20 XP

Наблюдаемость: очередь как метрика

Код может безупречно логировать успех, а мониторинг rabbitmq покажет правду: глубина очереди растёт третий час, потребители отвалились, и пользователи ждут письма, которые никто не отправляет. Разберём, какие цифры брокера и приложения стоит собирать, как посчитать возраст сообщений, где проходит граница честных алертов и что делать, когда очередь пустая, а инцидент всё равно идёт.

Содержание урока · 9 разделов

Очередь — самый честный датчик распределённой системы: она физически копит то, что система не успевает съесть, и врать ей незачем. Пока сервис отвечает на health-check кодом 200, глубина notify-очереди может расти часами — именно она первой скажет, что воркеры не справляются, что внешний SMTP-шлюз лежит, что издатель умер и трафика нет вовсе. Наблюдаемость очереди строится из двух слоёв. Первый — метрики брокера: глубины очередей, неподтверждённые доставки, число потребителей, скорости публикаций и раздач — их отдаёт management API и плагин rabbitmq_prometheus. Второй — метрики приложения: счётчики обработанных и провалившихся задач, in-flight воркеров и возраст сообщений, который считает только ваш код, потому что брокер про бизнес-граничные минуты не знает. Разберём оба слоя, соединим их в экспортёр и превратим цифры в алерты, которые будят дежурного по делу, а не на каждый пик трафика.

Мониторинг rabbitmq: какие цифры смотреть#

Начинается мониторинг rabbitmq с четырёх величин, которые видны и в management UI из второго урока, и в API. Глубина очереди — сообщения в состоянии ready: сколько работы ждёт; её считает и QueueDeclarePassive из кода ниже. Неподтверждённые доставки — unacked: сколько задач прямо сейчас держат воркеры; при стабильной работе это число колеблется вокруг суммы окон prefetch из десятого урока. Число потребителей: ноль при живом сервисе означает мёртвую подписку или упавший инстанс — это критическое значение, а не предостережение. Наконец, скорости: публикации в секунду против раздач — их разрыв и есть скорость накопления хвоста. Отдельный показатель — доля redelivered: растёт при обрывах соединений и вечных requeue, сигналя о проблемах транспорта из двадцатого урока или о ядах из шестнадцатого. Источники цифр: management API по пути /api/queues, панель Overview, а с версии 3.8 — плагин rabbitmq_prometheus, который отдаёт метрики на отдельном порту 15692 для скрейпинга.

Экспортёр глубины и счётчиков воркера

GO
package main

import (
        "log"
        "net/http"
        "time"

        amqp "github.com/rabbitmq/amqp091-go"
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/promauto"
        "github.com/prometheus/client_golang/prometheus/promhttp"
)

var queueDepth = promauto.NewGauge(prometheus.GaugeOpts{
        Namespace: "notify",
        Name:      "queue_depth",
        Help:      "сообщений в состоянии ready",
})

var queueConsumers = promauto.NewGauge(prometheus.GaugeOpts{
        Namespace: "notify",
        Name:      "queue_consumers",
        Help:      "подписанных потребителей",
})

// опрос глубины: пассивное объявление не меняет топологию
func pollDepth(ch *amqp.Channel) {
        q, err := ch.QueueDeclarePassive("notify", true, false, false, false, nil)
        if err != nil {
                log.Println("passive:", err)
                return
        }
        queueDepth.Set(float64(q.Messages))
        queueConsumers.Set(float64(q.Consumers))
}

func main() {
        conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
        if err != nil {
                log.Fatal(err)
        }
        ch, err := conn.Channel()
        if err != nil {
                log.Fatal(err)
        }

        go func() {
                for range time.Tick(5 * time.Second) {
                        pollDepth(ch)
                }
        }()
        http.Handle("/metrics", promhttp.Handler())
        log.Fatal(http.ListenAndServe(":9090", nil))
}

QueueDeclarePassive проверяет очередь без объявления: вернёт amqp.Queue с полями Messages и Consumers, а при несуществующей очереди даст ошибку, по которой удобно алертить о пропавшей топологии. Глубину и число потребителей снимают по таймеру в Gauge — они мгновенные значения; счётчики обработанных задач наращивают сами воркеры. Честный нюанс: unacked пассивное объявление не отдаёт — его берут из management API по пути /api/queues или из метрик плагина, в management UI это отдельная колонка.

Сигналы и реакции дежурного#

СигналЧто значитРеакция
depth растёт при ровном приходеворкеры не справляются с потокоммасштабировать пул или инстансы
unacked упёрся в сумму оконзадачи зависли или стали тяжёлымисмотреть теги потребителей и тайм-ауты
consumers = 0подписка умерла, инстанса неткритический алерт немедленно
возраст p95 выше SLAсообщения ждут дольше обещанногоразбирать очередь, а не логи
redelivered-доля растётобрывы, реконнекты или вечный requeueлоги транспорта и яды

Метрики очереди go: возраст сообщений#

Глубина отвечает на вопрос «сколько», а метрики очереди go дополняют вопросом «как долго». Возраст сообщения — разница между текущим временем и моментом публикации: воркер считает time.Since(d.Timestamp) и наблюдает в Histogram. Честный нюанс: поле Timestamp у amqp.Delivery заполняется из свойства timestamp сообщения, которое проставляет издатель в amqp.Publishing — если издатель его не заполнил, поле нулевое, и считать придётся по собственному заголовку вроде x-created-at. Возраст раскладывает инциденты быстрее глубины: глубина двенадцать сообщений при возрасте в минуты — терпимо; глубина двенадцать при возрасте в два часа — что-то держит голову очереди. Для SLA смотрят не среднее, а верхние перцентили — девяносто пятый и девяносто девятый: средний возраст легко спрятать за свежим потоком. Возраст сообщений в DLQ — отдельная величина: он показывает, сколько времени письмо мертво и сколько ещё можно потерять до разбора из шестнадцатого урока.

Возраст сообщения в воркере

GO
var lag = promauto.NewHistogram(prometheus.HistogramOpts{
        Namespace: "notify",
        Name:      "message_lag_seconds",
        Help:      "возраст сообщения в момент обработки",
        Buckets:   []float64{1, 5, 15, 60, 300, 900, 3600},
})

func worker(tag string, msgs <-chan amqp.Delivery) {
        for d := range msgs {
                // Timestamp проставлен издателем в amqp.Publishing
                age := time.Since(d.Timestamp).Seconds()
                lag.Observe(age)
                if err := send(d); err != nil {
                        d.Nack(false, false)
                        continue
                }
                d.Ack(false)
        }
}

Ведро гистограммы выровнено по здравым порогам задержки: до секунды — штатно, до минуты — терпимо, до пятнадцати минут — повод посмотреть, до часа — алерт. Наблюдение выполняется в момент взятия доставки: так метрика отвечает на вопрос, сколько сообщение ждало в очереди, а не сколько оно обрабатывалось. Издатель обязан заполнять Timestamp — иначе метрика будет измерять не задержку, а нули.

Алерты, которые будят по делу#

Сигнал превращается в алерт по правилам из курса по Prometheus: будит устойчивое состояние, а не пик. Глубина выше порога в течение десяти минут подряд — алерт; одиночный скачок при пересчёте ночного батча — тишина. Число потребителей равное нулю — алерт немедленный, без выжидания: подписка мертва, и каждое сообщение увеличивает хвост. Возраст p95 выше SLA — алерт по бюджету задержки: если обещали доставку за минуту, порог ставят на шестидесятой секунде, а не на среднем значении. Глубина DLQ больше нуля — тикет на разбор, не обязательно ночной пейджинг, но счётчик обязан быть виден. И обязательная пара к порогам сверху — порог снизу из warn-врезки: нулевой поток публикаций при ожидаемом трафике будит дежурного так же честно, как переполнение. Хорошая практика — дублировать каждый алерт ссылкой на панель с этим графиком: дежурный видит не текст «depth > 5000», а картину и тренд.

Дашборд очереди: пять панелей#

Панели дашборда собирают в пары, потому что поодиночке цифры обманчивы. Первая пара: глубина и unacked на одном графике — рост первой при спокойной второй означает нехватку воркеров, рост обеих — затык обработчиков. Вторая: скорости публикаций и раздач наложением — их расхождение видно раньше, чем глубина пробьёт порог. Третья: возраст p95 с линией SLA поверх — она честнее процентиля девяносто девять, потому что реагирует раньше. Четвёртая: потребители по тегам из семнадцатого урока — исчезновение одного тега на дашборде видно мгновенно. Пятая: глубина DLQ и доля redelivered — хвост аварий. Каждая панель отвечает на один вопрос, и если панель невозможно описать вопросом, она не нужна.

Чек-лист наблюдаемости очереди#

  • Глубина ready и unacked снимаются регулярно: QueueDeclarePassive или брокерские метрики плагина.
  • Число потребителей с алертом на ноль: мёртвая подписка копит хвост незаметно.
  • Скорости публикаций и раздач наложением: расхождение — опережающий сигнал.
  • Возраст сообщений с Timestamp от издателя: p95 против линии SLA.
  • Счётчики приложения: processed, failed, in-flight из семнадцатого урока.
  • Порог снизу на отсутствие трафика: пустая очередь при ожидаемом потоке — инцидент.
  • Проверка политики max-length: молчаливое выбрасывание головы не должно прятаться за зелёным дашбордом.

Цифры урока опираются на соседей: пятый урок про QueueDeclare дал поля q.Messages и флаги очереди, десятый про prefetch — сумму окон, вокруг которой колеблется unacked, а семнадцатый про worker pool — счётчики processed, failed и in-flight. Глубина DLQ — сигнал от шестнадцатого урока про dead letter, management UI описан во втором уроке. Типы метрик, алерты и скрейпинг разобраны в курсе по Prometheus: Counter и Gauge, Gauge состояния, правила алертов и странице курса. Карта курса — на странице курса.

Проверка: что считает q.Messages

Экспортёр сделал QueueDeclarePassive и прочитал поле q.Messages. Что именно оно показывает?

Проверка: глубина стабильна, возраст растёт

Глубина очереди держится около пятидесяти сообщений уже час, но возраст p95 за тот же час вырос с минуты до двадцати. Что вероятнее всего происходит?

Какой сигнал сработает?

Прочитайте код и выберите, что он выведет:

type queueState struct {
        depth     int
        consumers int
}

q := queueState{depth: 12, consumers: 0}

switch {
case q.consumers == 0:
        fmt.Println("критический: подписки нет")
case q.depth > 10:
        fmt.Println("масштабируем пул")
default:
        fmt.Println("штатно")
}

Соберите опрос глубины

Допишите пассивное объявление, чтение глубины и подсчёт возраста.

q, err := ch.("notify", true, false, false, false, nil) if err != nil { log.Println("passive:", err) return } depth.Set(float64(q.)) age := time.(d.Timestamp).Seconds() lag.Observe(age)

Соберите экспортёр метрик

Расставьте строки сборки: соединение, канал, пассивное объявление, значение gauge, HTTP-обработчик, запуск сервера.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: наблюдаемость очереди

Термин

глубина очереди

нажмите, чтобы перевернуть
Определение

Число ready-сообщений, которые ждут воркеров. Снимается QueueDeclarePassive или брокерскими метриками; равна скорости прихода, умноженной на ожидание.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Наблюдаемость очереди двухслойная: брокерские метрики глубин, потребителей и скоростей плюс счётчики и возраст из приложения.
  • q.Messages из QueueDeclarePassive — только ready; unacked считается отдельно и колеблется вокруг суммы окон.
  • consumers равное нулю — критический алерт: мёртвая подписка копит хвост незаметно.
  • Возраст сообщения считает воркер по Timestamp от издателя; смотрят верхние перцентили против SLA.
  • Пустая очередь при ожидаемом трафике — тоже инцидент: ставьте порог снизу.
  • max-length выбрасывает голову молча: глубина идеальна, а сообщения потеряны — следите за удалениями.
  • Алерты будят устойчивые состояния: порог с выдержкой, ноль потребителей — немедленно, DLQ — тикет.

Домашнее задание

Соберите экспортёр из урока против контейнера из второго урока: пассивный опрос глубины по таймеру, гистограмма возраста с Timestamp от издателя из шестого урока и счётчики processed и failed из семнадцатого. Погоните нагрузку и уроните воркеры: убедитесь, что consumers равное нулю видно раньше, чем глубина пробьёт порог. Затем настройте три алерта — ноль потребителей, глубина выше порога десять минут, возраст p95 выше SLA — и искусственно вызовите каждый, проверив, что выдержка отсекает пики от реальных инцидентов.

Личные заметкиПусто
Хранятся только в вашем браузере (RabbitMQ · Наблюдаемость: очередь как метрика)

Вопросы и ответы по уроку

Опрашивать брокер через QueueDeclarePassive или брать метрики плагина?

Оба пути рабочие и не исключают друг друга. Плагин отдаёт всё оптом и не требует кода — его берут как основу; пассивный опрос полезен, когда нужна глубина конкретной очереди с минимальными задержками или отдельный эндпоинт для лёгкой проверки живости топологии. Помните: пассивное объявление создаёт сетевые вызовы, поэтому интервал опроса ставят разумным — раз в несколько секунд.

Почему возраст сообщения не считает брокер?

Брокер знает момент публикации на уровне свойства timestamp, но не знает бизнес-границы: для писем это SLA в минуту, для аналитики — час, и только приложению известны эти пороги. Воркер измеряет задержку в момент взятия доставки и наблюдает её в гистограмму — так метрика получается уже с разбиением по типам задач и каналам, что для разбора инцидентов ценнее общего числа.

Как не захлебнуться в алертах от десятков очередей?

Алерты на команды устанавливают на очереди, где промедление стоит денег: задачи, уведомления, DLQ. Технические и одноразовые очереди — RPC-reply из восемнадцатого урока, временные fanout-подписки — обходятся дашбордом без пейджинга. Помогает и агрегация: вместо отдельного алерта на каждую очередь — правило с лейблом имени очереди и выдержкой, чтобы всплеск по десяти очередям не дал десять звонков.

Термины урока

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

Следующая ступень учебного пути

Excelize25 уроков~10 чпрактика25 уроков уже доступны

Excel в Go: чтение и генерация xlsx на excelize

Курс excelize на Go: создание xlsx, чтение, стили, формулы, диаграммы и StreamWriter для больших файлов. 25 уроков с примерами кода и проектом.

  • Создавать и читать xlsx-файлы средствами excelize
  • Работать с листами, ячейками, строками и столбцами
  • Оформлять таблицы: стили, шрифты, границы, форматы чисел и дат
  • Вставлять формулы, диаграммы и изображения