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

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

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

Урок 9 из 25 ~18 мин +20 XP

Долговечность: durable и persistent

Долговечность в RabbitMQ собрана из двух выключателей: флаг rabbitmq durable у очереди и DeliveryMode у сообщения — и чтобы сообщения пережили рестарт брокера, оба должны быть включены. Разберём, что именно сохраняется на диск, почему persistent-сообщение в недолговечной очереди обречено, сколько стоит запись и когда персистентность не нужна вовсе.

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

К этому уроку у нас уже собрана система возвратов: продюсер проверяет маршруты через 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 продюсер не узнает, что брокер успел принять и записать сообщение до аварии. Персистентность и подтверждения решают разные звенья цепи и в продакшене идут в паре.

Продюсер с двумя выключателями

GO
package 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=falseTransientничего: очередь и сообщения исчезнут
durable=falsePersistentничего: очередь не восстанавливается, сообщениям некуда вернуться
durable=trueTransientочередь жива, содержимое потеряно
durable=truePersistentочередь и сообщения восстановлены из метаданных и хранилища

Сколько стоит долговечность и когда она не нужна#

Персистентность — платная услуга: каждая публикация уходит на диск, и классические очереди на тяжёлых объёмах начинают заметно отличаться от сценариев в памяти; масштаб цены зависит от железа, но направление одно. Аргумент x-queue-mode со значением lazy переводит классическую очередь в ленивый режим: сообщения как можно раньше уходят на диск, память держит лишь головку очереди — вариант для больших накопителей, где очередь по часу догоняет после пиков. На кластерах современный ответ — quorum-очереди: они реплицируют содержимое на большинстве узлов, переживают падение отдельного узла и не требуют флага DeliveryMode как условия выживания; выбор между классикой и quorum — тема продакшен-настройки, а не этого урока. Важнее граница, о которой забывают чаще: долговечность нужна не всем задачам. Инвалидация кэша, телеметрия в реальном времени, уведомления с TTL в секунды — транзитные сообщения, потеря которых при рестарте стоит дешевле, чем каждодневная плата за диск. Включайте durable там, где сообщение представляет несделанную работу, — заказ, письмо, платёж.

Спорное мнение: durable не должен быть выбором по умолчанию#

В командах договариваются так: делаем все очереди durable и все сообщения persistent — чтобы не думать. Я против дефолта «всё подряд». Во-первых, транзит дорожает: телеметрия и инвалидация кэша тащат за собой запись на диск, которая ничего не покупает, — при рестарте такие данные устарели бы ещё до восстановления. Во-вторых, растёт иллюзия надёжности: дежурный видит метку durable и перестаёт проверять volume, а именно пересоздание контейнера без тома теряет всё, включая метаданные. Граница, по которой провожу черту я: долговечность получает то, что является несделанной работой с внешними последствиями — заказ, письмо, платёж, уведомление о событии; всё, что дешевле пересчитать, чем хранить, остаётся транзитным. Если в проекте правит стандарт «durable для всех», воевать не буду — но попрошу оставить одну пару очередей в памяти и сравнить латентность публикаций до и после; разница обычно нагляднее любых аргументов из статьи.

Ленивая очередь для больших накопителей

GO
args := 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-копией закрывает страх от «а как это было настроено».

Чеклист долговечности#

  • Боевая очередь объявлена с 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.

q, err := ch.QueueDeclare("orders", true, false, false, false, nil) err = ch.PublishWithContext(ctx, "", q.Name, false, false, amqp.Publishing{ : amqp.Persistent, ContentType: "application/json", : payload, }) fmt.Println("очередь:", q.)

Соберите долговечную публикацию по шагам

Расставьте строки в порядке выполнения: объявление очереди, проверка, маршалинг, структура сообщения, публикация, вывод.

Строки

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

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

Карточки: durable и persistent

Термин

durable

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

Флаг очереди: определение — имя, флаги, аргументы — сохраняется в метаданных на диске и восстанавливается после рестарта брокера.

1 / 6

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

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

Вывод

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

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

  • Долговечность — пара выключателей: durable у очереди и DeliveryMode равный amqp.Persistent у сообщения.
  • Матрица: недолговечная очередь не восстанавливается вовсе, durable-очередь без persistent-сообщений встречает рестарт пустой.
  • Персистентность платная: запись на диск, планирование объёма; скорость меряют на своей инсталляции.
  • lazy-режим и quorum-очереди — ответы для больших накопителей и кластеров.
  • Персистентность без publisher confirms не закрывает окно аварии в момент публикации.
  • Транзитным задачам — телеметрии, кэшу, секундным TTL — долговечность не нужна; включайте её для несделанной работы.
  • Рестарт брокера руками — пятиминутный тест матрицы, который стоит провести до аварии, а не после.

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

Соберите матрицу выживания на своём брокере: две очереди с durable=true и durable=false, в каждой по одному persistent и одному транзитному сообщению, затем docker restart и зафиксируйте, что осталось. После этого найдите в management UI очередь своего рабочего проекта и проверьте метку durable и режим сообщений; если пара выключателей не полная — запишите план переезда под новым именем из warn-врезки.

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

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

Можно ли сделать существующую очередь durable?

Нет: флаги фиксируются при создании, объявление того же имени с durable=true даст PRECONDITION_FAILED и закроет канал. Путь — новая durable-очередь под другим именем, переключение продюсеров и потребителей, дожим старой очереди потребителем; либо удалить очередь, потеряв содержимое.

Почему сообщения пропали, хотя я ставил amqp.Persistent?

Проверьте очередь: если она объявлена без durable, брокер не восстановил её после рестарта вместе с содержимым. Персистентность сообщения работает только в паре с долговечной очередью. Второй возможный виновник — контейнер без volume: пересоздание, в отличие от рестарта, теряет данные целиком.

Нужна ли персистентность всем сообщениям подряд?

Нет: каждое persistent-сообщение стоит записи на диск, и для телеметрии, инвалидации кэша или секундных TTL эта цена бессмысленна — при рестарте такие данные уже устарели. Долговечность включают там, где сообщение означает несделанную работу: заказ, письмо, платёж.

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

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

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

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

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

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

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