---
title: "Очередь: QueueDeclare и свойства"
description: "QueueDeclare в Go: как объявить rabbitmq очередь идемпотентно, флаги durable, exclusive и autoDelete, аргументы amqp.Table и ловушка PRECONDITION_FAILED."
course: "RabbitMQ в Go: очереди сообщений на amqp091-go"
url: "https://golangmaster.ru/courses/rabbitmq/lessons/05-ochered-prostaya/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# Очередь: QueueDeclare и свойства

*Курс «RabbitMQ в Go: очереди сообщений на amqp091-go», урок 5 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/rabbitmq/lessons/05-ochered-prostaya/*

Объявление rabbitmq очереди через QueueDeclare — первый вызов, который меняет состояние брокера: до него можно Dial хоть сто раз, после него у брокера появляется именованный буфер с правилами. Разберём сигнатуру queuedeclare go, четыре флага, идемпотентность и аргументы amqp.Table — всё, что отличает учебный пример от очереди, которую не стыдно завести в продакшене.

В первом уроке мы отправили сообщение в несуществующую очередь — и брокер молча выбросил его. Теперь сделаем очередь настоящей: именованной, с понятными свойствами и счётчиками, которую видно в management UI и не стыдно упоминать в ревью. Очередь — единственное место в RabbitMQ, где сообщения живут: буфер с именем, правилами жизни и потребителями, — и это первая сущность уровня канала, которую ваш код создаёт. Хорошая новость: вся политика умещается в один вызов QueueDeclare с четырьмя булевыми флагами и картой аргументов. Плохая: те же флаги становятся контрактом — поменять их на живой очереди нельзя, только удалить и создать заново, потеряв содержимое. Поэтому сегодняшний урок не про «как объявить» — это одна строка, — а про то, какие флаги выбрать и почему объявление превращается в контракт между продюсером и потребителем.

### QueueDeclare: объявление очереди в Go

Сигнатура ch.QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args amqp.Table) умещает политику очереди в один вызов, поэтому разбор queuedeclare go стоит начинать с возвращаемого значения. Функция возвращает Queue с тремя полями: Name — как очередь названа на сервере, что важно при пустом имени; MessageCount — сколько сообщений готово к доставке прямо сейчас; ConsumerCount — сколько потребителей подписано. Оговорка честности: счётчики достаются бесплатно только потому, что объявление заодно запрашивает состояние; при noWait брокер ответа не шлёт, и поля останутся нулями — сделаем об этом отдельный вопрос ниже. Ошибка возвращается в двух главных случаях: имя занято очередью с другими флагами — про это честный warn ниже — или соединение уже закрыто. Имя по конвенции пишут без пробелов: точки и дефисы разрешены, слэш не используют, чтобы не путать с путями vhost. Пустое имя — валидный случай: брокер сгенерирует уникальное имя с префиксом amq.gen-, типичное для эксклюзивных очередей ответов.

### Очередь — это буфер, а не канал Go

Очередь брокера похожа на буферизованный канал Go ровно до первой разницы. Канал живёт в памяти процесса: процесс умер — буфер исчез вместе с несчитанными значениями. Очередь живёт в отдельном процессе со своей памятью и опционально на диске: сервис падает, очередь продолжает копить сообщения, воркер перезапускается — и догребает недообработанное. Канал знает только свои значения; очередь ведёт счётчики готовых сообщений и потребителей, помнит, что кому выдано без подтверждения, и возвращает недоставленное при обрыве соединения. Канал безымянный и не переживает деплой; очередь — именованный долгожитель, чьи свойства фиксируются при создании. Обратная сторона: канал отдаёт значение горутине мгновенно и без обещаний, а очередь берёт на себя обязательства доставки — потому и требует подтверждений, что стоит отдельного протокольного обмена. Похоже на одно, а живёт совсем по-другому; это честный способ запомнить разницу, прежде чем сравнивать брокер с решениями на Redis.

### Объявляем очередь и читаем счётчики

```go
package main

import (
        "fmt"
        "log"

        amqp "github.com/rabbitmq/amqp091-go"
)

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)
        }

        fmt.Println("очередь:", q.Name)
        fmt.Println("сообщений:", q.Messages, "потребителей:", q.Consumers)
}
```

Один вызов — и очередь durable с именем orders существует на брокере. При первом запуске счётчики вернутся нулями, при повторном — настоящими: объявление заодно читает состояние. args передана как nil, значит особых свойств нет; четыре флага после имени разбираем по одному ниже. Зайдите в management UI после запуска: очередь появится на вкладке Queues с пометкой durable.

### Четыре флага: durable, autoDelete, exclusive, noWait

Флаг durable=true делает очередь переживаемой: определение сохраняется на диск, и после рестарта брокера очередь снова готова принимать. Важно не путать с сохранностью сообщений: durable-очередь не делает сообщения долговечными — у сообщений свой флаг DeliveryMode, и разбор у девятого урока. Флаг autoDelete=true удаляет очередь, когда отключается последний потребитель: вариант для трансляций, где подписчики приходят и уходят, а копить нечего. Флаг exclusive=true ограничивает очередь одним соединением: другой клиент не объявит её и не подпишется, а при закрытии соединения брокер удалит очередь сам — так работают очереди ответов в RPC. Флаг noWait=true просит брокера не отвечать на объявление: экономится один сетевой рейс, но вместе с ним уходят счётчики и мгновенные ошибки — флаг ставят, когда уверены в параметрах. Флаги независимы, но сочетания осмысленны: durable вместе с exclusive — странная пара, ведь эксклюзивная очередь умрёт вместе с соединением, чего бы ни обещал durable. Выбирайте флаги под ответ на один вопрос: сколько должна жить очередь и кто имеет право её видеть.

### Флаги QueueDeclare одним взглядом

| Флаг | Что делает | Типовое применение |
| --- | --- | --- |
| durable | определение очереди переживает рестарт брокера | рабочие очереди задач |
| autoDelete | удаление, когда ушёл последний потребитель | трансляции и подписки |
| exclusive | очередь только одного соединения, удаление при его закрытии | очереди ответов RPC |
| noWait | не ждать ответа брокера на объявление | стартовые объявления с известными параметрами |
| args | карта особых свойств вида x-... | TTL, лимиты длины, dead letter |

> **PRECONDITION_FAILED: имя занято другими флагами.** Кейс: локально очередь объявили с durable=false, а продюсер в тестовом контуре объявляет то же имя с durable=true. Брокер отвечает ошибкой 406 PRECONDITION_FAILED со словами inequivalent arg и закрывает канал — публикации падают разом. Чинится двумя путями: удалить очередь и дать создаться с новыми флагами (потеряв содержимое) или взять новое имя. Отсюда привычка: параметры очереди держат в одном месте кода, а не у каждого сервиса свои.

### Объявление идемпотентно: декларируйте перед использованием

QueueDeclare идемпотентен: если очередь с таким именем и эквивалентными параметрами уже существует, брокер вернёт её текущие счётчики и не создаст ничего заново. Из этого следует рабочая привычка — объявляйте очередь перед использованием: продюсер перед публикацией, потребитель перед подпиской. Кто встал первым, тот и создал очередь с финальными флагами; второй просто подключается. Так исчезает гонка старта, когда воркер запускается раньше продюсера и падает на публикации в несуществующую очередь. Но идемпотентность строгая по параметрам: флаги и аргументы должны совпасть один в один, любое расхождение — PRECONDITION_FAILED с закрытым каналом. Отсюда вторая привычка: параметры очереди выносят в общий пакет или константы, чтобы продюсер и потребитель не разъехались в правках. Отдельно про политики: часть свойств в продакшене задают не кодом, а policy на стороне брокера — те же TTL и лимиты; совпадающие свойства из кода и политики конфликтуют, поэтому прежде чем объявлять аргументы, посмотрите, что уже навешено политикой.

### Аргументы amqp.Table: TTL и лимит длины

```go
args := amqp.Table{
        "x-message-ttl": int32(60000),
        "x-max-length":  int32(1000),
}

q, err := ch.QueueDeclare("notifications",
        true, false, false, false, args)
if err != nil {
        log.Fatal(err)
}
```

Аргументы — карта amqp.Table с ключами вида x-...: здесь каждому сообщению отводится минута жизни, а очередь ограничена тысячей сообщений. Значения передаются строго типизированно — числа идут как int32, иначе брокер отвергнет объявление. Полный каталог аргументов — dead letter, лимиты, режимы — пригодится по ходу курса; сейчас важно, что args становятся частью контракта очереди и меняются так же болезненно, как флаги.

### q.Messages и q.Consumers: очередь как диагноз

Счётчики из QueueDeclare — бесплатная диагностика, встроенная в самый первый вызов. MessageCount показывает сообщения в состоянии ready: лежат и ждут потребителя. Не вошедшие в это число unacknowledged — выданные, но неподтверждённые — в объявлении не видны; полную картину ready и unacked даёт management UI на вкладке Queues. ConsumerCount — число подписанных потребителей: ноль при растущей глубине означает, что воркеры не запустились или умерли, и очередь превращается в накопитель. Пара «глубина растёт, потребителей ноль» — самый частый сигнал инцидента, поэтому её же превращают в метрики: урок про наблюдаемость настраивает алерты на этих числах. В коде счётчики удобно логировать при старте: сервис печатает, сколько недоработки унаследовал, — и дежурный сразу видит, было ли затишье или копилось за ночь. Только не стройте на этих числах бизнес-логику: они снимаются в момент вызова и мгновенно устаревают; решение «брать ли задачу» по глубине на живой очереди всегда запаздывает.

> **Код урока — локально, интерактивы — на странице.** Брокер из второго урока продолжает жить в Docker, и объявление очереди выполняется через go run на вашей машине: песочница сайта умеет только подмножество стандартной библиотеки без сети и сторонних пакетов, поэтому ch.QueueDeclare в браузере не запустится. Интерактивы ниже проверяют именно то, что обычно забывается: флаги, идемпотентность и порядок вызовов.

### Какую очередь заводить

- Задачная очередь воркеров: durable=true, остальные флаги false — переживает рестарты и не удаляется сама.
- Эксклюзивная очередь ответов RPC: exclusive=true и пустое имя — брокер сам сгенерирует amq.gen-...
- Очередь трансляции: autoDelete=true — живёт, пока есть подписчики, исчезает вместе с последним.
- Прототип для экспериментов: все флаги false — очередь, которую не жалко удалить вечером.
- Очередь с политикой жизни: аргументы вроде x-message-ttl в amqp.Table.
- DLQ: обычная durable-очередь для неудачных сообщений — узнаете её в уроке про dead letter.

Очередь объявлена — но пуста, пока в неё никто не пишет. В [шестом уроке](/courses/rabbitmq/lessons/06-publikaciya-soobschenij/) отправим первые сообщения через PublishWithContext и разберёмся, почему публикация без потребителя всё равно возвращает nil; седьмой — про чтение из очереди через ConsumeWithContext. Если флаг durable показался важным, [девятый урок](/courses/rabbitmq/lessons/09-durable-persistent/) полностью про долговечность очередей и сообщений — там же найдёте, чем persistent-сообщение отличается от durable-очереди. По механике всё знакомо: как и в [списках Redis](/courses/go-redis/lessons/11-spiski/), здесь тот же producer-consumer, но с подтверждениями и гарантиями; близкий родственник — [Streams](/courses/go-redis/lessons/15-potoki-streams/), а карта всего курса — на [его странице](/courses/rabbitmq/). Домашнее задание ниже фиксирует рефлекс, который спасает от утренних сюрпризов с флагами.

### Проверка: идемпотентность

**Вопрос:** ch.QueueDeclare("tasks", true, false, false, false, nil) вызывают второй раз — очередь уже есть с теми же флагами. Что произойдёт?

- ✅ Вернётся та же очередь с текущими счётчиками — вызов идемпотентен
- Брокер вернёт ошибку двойного объявления
- Очередь пересоздастся с нуля
- Программа заблокируется до снятия блокировки

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

### Проверка: noWait

**Вопрос:** Что вернёт QueueDeclare, вызванный с noWait=true?

- ✅ Queue с пустыми полями: брокер не отвечает, объявление считается принятым
- Полную статистику очереди, как обычный вызов
- Ошибку: noWait доступен только у публикации
- Ничего: функция вернёт nil без структуры Queue

noWait просит брокера не слать ответ, поэтому Queue возвращается с пустым именем и нулевыми счётчиками. Экономия одного сетевого рейса оплачивается обратной связью: ошибка объявления проявится позже — на другом вызове или через NotifyClose.

### Что выведет программа?

```go
q1, err1 := ch.QueueDeclare("orders",
        false, false, false, false, nil)
q2, err2 := ch.QueueDeclare("orders",
        true, false, false, false, nil)

fmt.Println(q1.Name, err1, q2.Name, err2)
```

**Что выведет программа?**

- ✅ orders <nil>  PRECONDITION_FAILED — inequivalent arg
- orders <nil> orders <nil>
- orders <nil> orders ошибка durable
- паника на втором вызове

Первое объявление создаёт недолговечную очередь orders. Второе — те же имя, но durable=true: брокер отвечает 406 PRECONDITION_FAILED про inequivalent arg и закрывает канал, поэтому q2.Name пуст — в выводе между <nil> и PRECONDITION виден лишний пробел. Ошибка приходит вторым вызовом, первый безупречен.

### Соберите объявление с аргументами

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

```go
args := amqp.___{"x-message-ttl": int32(30000)}
q, err := ch.___("tasks", true, false, false, false, args)
fmt.Println("очередь:", q.___)
```

Подсказка: Карта особых свойств — amqp.Table, очередь объявляет QueueDeclare, а имя живёт в поле Name.

Правильные ответы в пропусках: `Table`, `QueueDeclare`, `Name`

### Соберите объявление очереди по строкам

Расставьте строки в порядке выполнения: соединение, проверка, закрытие, канал, объявление, вывод счётчиков.

Правильный порядок строк:

1. `conn, err := amqp.Dial(url)`
2. `if err != nil { log.Fatal(err) }`
3. `defer conn.Close()`
4. `ch, err := conn.Channel()`
5. `q, err := ch.QueueDeclare("orders", true, false, false, false, nil)`
6. `fmt.Println(q.Name, q.Messages, q.Consumers)`

### Карточки: QueueDeclare

- **QueueDeclare** — Объявление очереди на канале: имя, четыре флага, аргументы. Возвращает Queue с именем, числом готовых сообщений и потребителей. Идемпотентен при эквивалентных параметрах.
- **durable** — Флаг переживаемости: определение очереди сохраняется на диск и живёт после рестарта брокера. Сохранность самих сообщений — отдельный флаг DeliveryMode.
- **exclusive** — Очередь одного соединения: недоступна другим клиентам, удаляется при закрытии соединения. Классика очередей ответов RPC.
- **autoDelete** — Очередь удаляется, когда отключается последний потребитель. Вариант для трансляций и подписок без накопления.
- **noWait** — Не ждать ответа брокера: экономится сетевой рейс, но Queue вернётся пустым, а ошибки объявлений проявятся позже.
- **PRECONDITION_FAILED** — Ошибка 406: очередь с таким именем уже объявлена другими флагами или аргументами. Канал закрывается брокером.

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

- QueueDeclare — один вызов с четырьмя флагами и картой args; вся политика очереди живёт в нём.
- Флаги: durable переживает рестарт, autoDelete умирает с последним потребителем, exclusive принадлежит соединению, noWait не ждёт ответа.
- Объявление идемпотентно при совпадающих параметрах — объявляйте очередь и на стороне продюсера, и потребителя.
- Расхождение флагов даёт 406 PRECONDITION_FAILED и закрытый канал; параметры держат в общем пакете.
- q.Messages и q.Consumers — мгновенная диагностика: растущая глубина при нуле потребителей — сигнал инцидента.
- Аргументы amqp.Table с ключами x-... задают TTL и лимиты и становятся частью контракта очереди.

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

Объявите очередь orders с durable=true, запустите программу дважды и сравните счётчики, затем попробуйте изменить durable на false и получите PRECONDITION_FAILED. После этого вынесите параметры очереди в отдельный пакет с константами и убедитесь, что продюсер и потребитель объявляют очередь одинаково.

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

### Можно ли поменять флаги у существующей очереди?

Нет: флаги и аргументы фиксируются при создании, попытка объявить имя с другими параметрами даёт 406 PRECONDITION_FAILED. Путь один: удалить очередь и создать заново с потерей содержимого, либо завести новое имя и перевести на него продюсеров и потребителей.

### Кто должен объявлять очередь — продюсер или потребитель?

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

### Зачем объявлять очередь с пустым именем?

Брокер сгенерирует уникальное имя с префиксом amq.gen- и вернёт его в поле Name. Смысл есть только вместе с exclusive=true: закрылось соединение — очередь исчезла. Так делают временные очереди ответов в RPC, где имя должно быть уникальным на каждый запрос.
