Содержание урока · 10 разделов
- Флаг durable: что именно переживает рестарт
- Персистентность rabbitmq: DeliveryMode и amqp.Persistent
- Матрица выживания после рестарта брокера
- Сколько стоит долговечность и когда она не нужна
- Спорное мнение: durable не должен быть выбором по умолчанию
- Проверка на своей машине: рестарт руками
- Чеклист долговечности
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
К этому уроку у нас уже собрана система возвратов: продюсер проверяет маршруты через mandatory, воркер подтверждает обработку ручными ack, и падение потребителя ничего не теряет. Осталась незакрытой самая тяжёлая авария — падение самого брокера: перезапуск контейнера, обновление версии, пропажа питания на хосте. Против этого у RabbitMQ два независимых механизма, и в этом их коварство: флаг durable у очереди из пятого урока и поле DeliveryMode у сообщения из шестого включаются в разных местах кода и друг друга не подразумевают. Очередь может пережить рестарт и встретить рассвет пустой; сообщения могут быть готовы к записи на диск, но лежать в очереди, которой самой не суждено воскреснуть. Разберём матрицу сочетаний, цену персистентности и честные границы: задачи, которым долговечность не нужна и только мешает.
Флаг durable: что именно переживает рестарт#
Durable=true означает, что определение очереди — имя, флаги, аргументы — сохраняется в метаданные брокера на диск, и после рестарта очередь снова существует и готова принимать сообщения. Из этого не следует судьба содержимого: durable-очередь хранит и долговечные, и транзитные сообщения, а переживают рестарт только долговечные. Обратная комбинация ещё коварнее: сообщение с DeliveryMode равным двум в очереди без durable не спасётся — сама очередь после рестарта не будет восстановлена, ей вместе с содержимым нет места в метаданных. Брокер при перезапуске восстанавливает durable-очереди из метаданных, а затем дозаписывает в них persistent-сообщения из хранилища сообщений. На кластерной инсталляции картина шире: реплицированные quorum-очереди хранят содержимое на большинстве узлов, и для них durability — не опция, а базовое свойство. Для классических очередей правило остаётся простым, как чекбокс: хотите пережить рестарт — включите durable у очереди и persistent у сообщения, одно без другого не работает.
Персистентность rabbitmq: DeliveryMode и amqp.Persistent#
За персистентность rabbitmq отвечает одно поле структуры amqp.Publishing: DeliveryMode типа uint8, для которого библиотека держит две константы — amqp.Transient со значением 1 и amqp.Persistent со значением 2. Единица означает транзитное сообщение: живёт в памяти, в диск не пишется, рестарт не переживёт. Двойка ставит сообщение в хранилище сообщений брокера: запись идёт на диск, и скорость публикации заметно зависит от подсистемы хранения — точные цифры сильно варьируются от железа и настроек, поэтому честнее мерить на своей инсталляции, чем верить чужим бенчмаркам. Плата за запись — не только латентность: диск под хранилище надо планировать по объёму, а безлимитная накопительная очередь упрётся в место, о чём предупреждает урок двадцать третий. Отдельный нюанс: persistent-сообщение в момент публикации — ещё не страховка; без publisher confirms продюсер не узнает, что брокер успел принять и записать сообщение до аварии. Персистентность и подтверждения решают разные звенья цепи и в продакшене идут в паре.
Продюсер с двумя выключателями
GOpackage main
import (
"context"
"encoding/json"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
type Order struct {
ID int
Email string
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
log.Fatal(err)
}
q, err := ch.QueueDeclare("orders", true, false, false, false, nil)
if err != nil {
log.Fatal(err)
}
payload, err := json.Marshal(Order{ID: 42, Email: "user@example.com"})
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = ch.PublishWithContext(ctx, "", q.Name, false, false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "application/json",
Body: payload,
})
if err != nil {
log.Fatal(err)
}
log.Println("опубликовано durable-сообщение")
}Оба выключателя рядом: QueueDeclare с durable=true и DeliveryMode равным amqp.Persistent. Структура маршируется в JSON без тегов — имена полей станут ключами, для примера этого достаточно. Перезапустите брокер и откройте management UI: очередь orders останется с сообщением, а очередь без durable из прошлых экспериментов исчезнет вместе с содержимым.
Матрица выживания после рестарта брокера#
| Очередь | Сообщение | Что останется после рестарта |
|---|---|---|
| durable=false | Transient | ничего: очередь и сообщения исчезнут |
| durable=false | Persistent | ничего: очередь не восстанавливается, сообщениям некуда вернуться |
| durable=true | Transient | очередь жива, содержимое потеряно |
| durable=true | Persistent | очередь и сообщения восстановлены из метаданных и хранилища |
Кейс: сервис публиковал задачи с DeliveryMode равным amqp.Persistent, но очередь объявляли с durable=false — из примера, на котором писали прототип. Ночная перезагрузка хоста: брокер поднялся, а очереди нет. Продюсеры публиковали как ни в чём не бывало — дефолтный обменник не находил очередь, без mandatory выбрасывал сообщения и возвращал nil, поэтому ошибки в логах отсутствовали, пока дежурный не сверил глубину очереди с ожидаемой. Вторая ловушка ждала при исправлении: очередь с живым содержимым нельзя перекрасить из недолговечной в durable — пятый урок рассказывал про PRECONDITION_FAILED. Спасает переезд: новая durable-очередь под другим именем, переключение продюсеров, дожим старой через потребителя. Чек урока один: найдите в management UI свою боевую очередь и проверьте метку durable — теперь, а не после аварии.
Сколько стоит долговечность и когда она не нужна#
Персистентность — платная услуга: каждая публикация уходит на диск, и классические очереди на тяжёлых объёмах начинают заметно отличаться от сценариев в памяти; масштаб цены зависит от железа, но направление одно. Аргумент x-queue-mode со значением lazy переводит классическую очередь в ленивый режим: сообщения как можно раньше уходят на диск, память держит лишь головку очереди — вариант для больших накопителей, где очередь по часу догоняет после пиков. На кластерах современный ответ — quorum-очереди: они реплицируют содержимое на большинстве узлов, переживают падение отдельного узла и не требуют флага DeliveryMode как условия выживания; выбор между классикой и quorum — тема продакшен-настройки, а не этого урока. Важнее граница, о которой забывают чаще: долговечность нужна не всем задачам. Инвалидация кэша, телеметрия в реальном времени, уведомления с TTL в секунды — транзитные сообщения, потеря которых при рестарте стоит дешевле, чем каждодневная плата за диск. Включайте durable там, где сообщение представляет несделанную работу, — заказ, письмо, платёж.
Спорное мнение: durable не должен быть выбором по умолчанию#
В командах договариваются так: делаем все очереди durable и все сообщения persistent — чтобы не думать. Я против дефолта «всё подряд». Во-первых, транзит дорожает: телеметрия и инвалидация кэша тащат за собой запись на диск, которая ничего не покупает, — при рестарте такие данные устарели бы ещё до восстановления. Во-вторых, растёт иллюзия надёжности: дежурный видит метку durable и перестаёт проверять volume, а именно пересоздание контейнера без тома теряет всё, включая метаданные. Граница, по которой провожу черту я: долговечность получает то, что является несделанной работой с внешними последствиями — заказ, письмо, платёж, уведомление о событии; всё, что дешевле пересчитать, чем хранить, остаётся транзитным. Если в проекте правит стандарт «durable для всех», воевать не буду — но попрошу оставить одну пару очередей в памяти и сравнить латентность публикаций до и после; разница обычно нагляднее любых аргументов из статьи.
Ленивая очередь для больших накопителей
GOargs := amqp.Table{"x-queue-mode": "lazy"}
q, err := ch.QueueDeclare("archive", true, false, false, false, args)
if err != nil {
log.Fatal(err)
}
err = ch.PublishWithContext(ctx, "", q.Name, false, false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte("export-2025-01"),
})Аргумент задаётся картой amqp.Table при объявлении и становится частью контракта очереди: поменять режим на живой очереди не выйдет, только переезд под новым именем. Ленивая очередь пишет сообщения на диск как можно раньше и держит в памяти лишь минимальную головку — для очередей-архивов, которые копят миллионы записей, это выбор по умолчанию.
Проверка на своей машине: рестарт руками#
Теория матрицы проверяется за пять минут на брокере из второго урока. Объявите две очереди: durable=true и durable=false, опубликуйте в обе по два сообщения — одно с amqp.Persistent, другое транзитное. Выполните docker restart для контейнера брокера и откройте вкладку Queues: долговечная очередь на месте, в ней лежит persistent-сообщение; транзитное из неё исчезло, недолговечная очередь пропала целиком. Заодно проверьте, что volume для данных настроен, как во втором уроке: контейнер без тома после удаления и пересоздания теряет всё, включая durable-метаданные — брокер честно переживает рестарт, но не пересоздание без диска. Тот же сценарий удобно повторить перед обновлением версии RabbitMQ: маленькая репетиция миграции стоит дешевле внезапного знакомства с восстановлением. Определения очередей и обменников удобно выгружать в файл через rabbitmqctl export_definitions или вкладку экспорта в management UI — снимок метаданных вместе с volume-копией закрывает страх от «а как это было настроено».
Персистентность проверяется только рестартом настоящего брокера: в браузерной песочнице работают лишь fmt, strings, strconv, math, sort, errors и time — ни Docker, ни диска там нет. Прогоните матрицу выживания на контейнере из второго урока и зафиксируйте результат для своей боевой очереди; интерактивы ниже проверят комбинации флагов и поля Publishing.
Чеклист долговечности#
- Боевая очередь объявлена с durable=true — метка видна в management UI.
- Сообщения, которые представляют несделанную работу, публикуются с DeliveryMode равным amqp.Persistent.
- Параметры очереди живут в общем пакете кода: расхождение durable между сервисами даёт PRECONDITION_FAILED.
- Проверен volume для данных брокера: рестарт и пересоздание контейнера — разные аварии.
- Большие накопители — под lazy-режим или quorum-очереди; диск спланирован по объёму.
- Транзитные задачи — телеметрия, кэш, секундные TTL — оставлены без персистентности осознанно.
Соседние уроки по теме: пятый урок рассказывал, где у очереди появился флаг durable и почему параметры становятся контрактом, а восьмой — что происходит с сообщениями при падении воркера, второй половине той же аварии. Очередь неудачников, которая обязана быть durable, собирается в шестнадцатом уроке про dead letter, способы потерять сообщения целиком — в двадцать третьем про антипаттерны. Сроки жизни сообщений через TTL родственны истечению ключей в Redis, а глубину очереди как метрику удобно вести через Gauge из курса по Prometheus. Карта всего курса — на странице курса.
Проверка: матрица выживания
Очередь объявлена с durable=true, сообщения публиковались без DeliveryMode. Брокер перезапустили. Что вы увидите?
Проверка: amqp.Persistent
Что верно про amqp.Persistent?
Что станет после рестарта брокера?
Прочитайте код и выберите, что он выведет:
ch.QueueDeclare("tmp", false, false, false, false, nil)
err := ch.PublishWithContext(ctx, "", "tmp", false, false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
Body: []byte("x"),
})
fmt.Println(err)
// ...брокер перезапустили...Соберите durable-публикацию
Допишите объявление долговечной очереди и поля Publishing.
Соберите долговечную публикацию по шагам
Расставьте строки в порядке выполнения: объявление очереди, проверка, маршалинг, структура сообщения, публикация, вывод.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: durable и persistent
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Долговечность — пара выключателей: durable у очереди и DeliveryMode равный amqp.Persistent у сообщения.
- Матрица: недолговечная очередь не восстанавливается вовсе, durable-очередь без persistent-сообщений встречает рестарт пустой.
- Персистентность платная: запись на диск, планирование объёма; скорость меряют на своей инсталляции.
- lazy-режим и quorum-очереди — ответы для больших накопителей и кластеров.
- Персистентность без publisher confirms не закрывает окно аварии в момент публикации.
- Транзитным задачам — телеметрии, кэшу, секундным TTL — долговечность не нужна; включайте её для несделанной работы.
- Рестарт брокера руками — пятиминутный тест матрицы, который стоит провести до аварии, а не после.
Домашнее задание
Соберите матрицу выживания на своём брокере: две очереди с durable=true и durable=false, в каждой по одному persistent и одному транзитному сообщению, затем docker restart и зафиксируйте, что осталось. После этого найдите в management UI очередь своего рабочего проекта и проверьте метку durable и режим сообщений; если пара выключателей не полная — запишите план переезда под новым именем из warn-врезки.
Личные заметкиПусто
Вопросы и ответы по уроку
Можно ли сделать существующую очередь durable?
Нет: флаги фиксируются при создании, объявление того же имени с durable=true даст PRECONDITION_FAILED и закроет канал. Путь — новая durable-очередь под другим именем, переключение продюсеров и потребителей, дожим старой очереди потребителем; либо удалить очередь, потеряв содержимое.
Почему сообщения пропали, хотя я ставил amqp.Persistent?
Проверьте очередь: если она объявлена без durable, брокер не восстановил её после рестарта вместе с содержимым. Персистентность сообщения работает только в паре с долговечной очередью. Второй возможный виновник — контейнер без volume: пересоздание, в отличие от рестарта, теряет данные целиком.
Нужна ли персистентность всем сообщениям подряд?
Нет: каждое persistent-сообщение стоит записи на диск, и для телеметрии, инвалидации кэша или секундных TTL эта цена бессмысленна — при рестарте такие данные уже устарели. Долговечность включают там, где сообщение означает несделанную работу: заказ, письмо, платёж.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Excel в Go: чтение и генерация xlsx на excelize
Курс excelize на Go: создание xlsx, чтение, стили, формулы, диаграммы и StreamWriter для больших файлов. 25 уроков с примерами кода и проектом.
- Создавать и читать xlsx-файлы средствами excelize
- Работать с листами, ячейками, строками и столбцами
- Оформлять таблицы: стили, шрифты, границы, форматы чисел и дат
- Вставлять формулы, диаграммы и изображения