---
title: "Установка: клиент, воркер и Redis"
description: "Asynq установка по шагам: go get hibiken/asynq, asynq redis подключение через RedisClientOpt, воркер NewServer с Config и Redis в Docker одной командой."
course: "Asynq в Go: очередь фоновых задач на Redis"
url: "https://golangmaster.ru/courses/asynq/lessons/02-ustanovka-podklyuchenie/"
published: "2026-09-19"
updated: "2026-09-23"
minutes: 18
---

# Установка: клиент, воркер и Redis

*Курс «Asynq в Go: очередь фоновых задач на Redis», урок 2 из 25. ~18 минут. Обновлено 23.09.2026. Источник: https://golangmaster.ru/courses/asynq/lessons/02-ustanovka-podklyuchenie/*

Asynq установка — самый короткий шаг курса: одна команда go get, один адрес Redis и два маленьких main.go — клиент, который ставит задачи, и воркер, который их забирает. В этом уроке соберём оба процесса, разберём RedisClientOpt и Config, поднимем Redis в Docker и подготовим структуру проекта для всего курса.

Первый запуск из README обычно выглядит так: скопировали код, go run main.go — и в терминале гора dial tcp 127.0.0.1:6379: connect: connection refused. Библиотека исправна, код исправен — не запущен Redis, единственная инфраструктурная зависимость asynq. Это лучший момент, чтобы понять устройство связки: клиент и воркер — два обычных процесса Go, они не знают друг о друге и общаются только через Redis. Из этого устройства следуют и требования: Redis должен быть запущен до первого запуска, а его адрес известен обоим процессам. Ошибка connection refused — не баг asynq, а честный ответ на вопрос «с кем говорить?». Пока Redis пуст, воркеру нечего делать; пока клиент не подключился, задачи не появятся. В этом уроке мы соберём окружение один раз и больше к нему не вернёмся: библиотека через go get, Redis в Docker, два main-файла в одном модуле. Всё это живёт локально: песочница сайта сеть не исполняет, поэтому реальный запуск — ваш терминал, и на него уйдёт минут десять.

### Asynq установка: go get и структура проекта

Asynq установка в проект начинается с модуля: если в папке ещё нет go.mod, выполните go mod init, затем go get github.com/hibiken/asynq — один импорт, без cgo и системных пакетов. Команда зафиксирует версию в go.mod и go.sum, и обе стороны связки будут собираться ровно на ней; обновлять библиотеку в клиенте и воркере лучше одним коммитом, чтобы представления о настройках не разъехались. Дальше вопрос организации: у вас будет минимум две точки входа. Я держу три пакета: pkg/tasks — общие типы задач и payload-структуры, cmd/client — API-часть, которая только ставит задачи, cmd/worker — процесс с хендлерами. Из одного модуля собираются два бинарника: go build ./cmd/client и go build ./cmd/worker. Разделение выглядит как бюрократия ровно до первого продакшена: клиенту не нужны SMTP-библиотеки и доступ к внутренним системам, воркеру не нужны роутеры API — у каждой половины свой минимум зависимостей и свой профиль нагрузки. Отдельный бонус: воркер можно гонять на слабой машине, а API — на сильной, потому что разным профилям нагрузки нужен разный железный бюджет. Общий пакет задач гарантирует, что клиент и воркер не разъедутся в представлениях о данных: тип один, импортируют его обе стороны. Если вы пишете монолит, схема легко сворачивается обратно: клиент и воркер могут жить в одном main.go, и в следующем уроке мы именно так и сделаем для простоты.

### Клиент: NewClient и Close

```go
package main

import (
    "log"

    "github.com/hibiken/asynq"
)

func main() {
    client := asynq.NewClient(asynq.RedisClientOpt{
        Addr: "localhost:6379",
    })
    defer client.Close()

    log.Println("клиент готов к постановке задач")
}
```

Весь клиентский каркас — конструктор и закрытие. NewClient сохраняет настройки соединения, но ни к чему не подключается: пул соединений откроется на первой операции, поэтому здесь нет и не может быть проверки связи. Close закрывает пул — defer гарантирует его освобождение при любом выходе из main. Первую задачу этот main получит в третьем уроке.

### Asynq redis подключение: RedisClientOpt

Asynq redis подключение описывается значением RedisClientOpt — причём это тот же тип, который принимают клиент, воркер и Inspector, поэтому конфигурация задаётся один раз и разъехаться не может. Главное поле — Addr: адрес вида host:port, по умолчанию 127.0.0.1:6379, как у стандартного Redis без настроек. Дальше по необходимости: DB выбирает логическую базу (по умолчанию нулевую), Username и Password — аутентификацию, которая в Redis 6 и новее живёт в ACL. Для TLS есть поле TLSConfig со стандартной конфигурацией crypto/tls — понадобится, если Redis закрыт шифрованием. Пул соединений asynq создаёт сам: PoolSize по умолчанию считается от числа CPU, и трогать его стоит только под измеренной нагрузкой. Если вы настраивали клиент go-redis, всё покажется знакомым: asynq использует его внутри, и семантика полей та же: если Redis для вас в новинку, [курс по go-redis](/courses/go-redis/) закрыет эту дыру параллельно с третьим уроком. Разная топология Redis тоже ложится на тот же opt: для Sentinel существует RedisFailoverClientOpt с MasterName и SentinelAddrs, для кластера — RedisClusterClientOpt с Addrs; все три реализуют один интерфейс RedisConnOpt, поэтому клиент и воркер не знают о топологии ничего сверх этого значения. Соединение при этом ленивое — первый контакт с Redis случится на первой операции, а не в конструкторе. Поэтому клиентский main из примера выше запускается мгновенно даже без Redis — это нормальное поведение, а не лазейка: проверяйте ошибки там, где реальная работа.

### Поля RedisClientOpt, которые реально пригодятся

| Поле | Что задаёт | По умолчанию |
| --- | --- | --- |
| Addr | адрес Redis вида host:port | 127.0.0.1:6379 |
| DB | номер логической базы Redis | 0 |
| Username | пользователь Redis 6+ (ACL) | пусто |
| Password | пароль из requirepass или ACL | пусто |
| PoolSize | число соединений в пуле | 10 × число CPU |
| DialTimeout | таймаут установки соединения | 5 секунд |

### Воркер: asynq.NewServer и Config

Воркер собирается конструктором asynq.NewServer: первый аргумент — те же RedisClientOpt, второй — Config, единственная структура настроек сервера. Главная настройка — Concurrency: сколько горутин одновременно исполняют хендлеры. По умолчанию это число CPU машины — разумный старт для тяжёлой вычислительной работы, но если задачи в основном ждут сеть, смело ставьте больше. Вторая важная группа настроек — Queues: карта «имя очереди — вес», о которой подробно поговорим в седьмом уроке; пока достаточно дефолтной очереди default, которая создаётся сама. Имена очередей — просто строки, но лучше объявить их константами рядом с типами задач: опечатка в имени молча создаст вторую очередь, которую воркер никогда не читает, и задачи будут ждать в ней вечно. ServeMux — маршрутизатор: он связывает строку-тип задачи с вашей функцией, работает как http.ServeMux из стандартной библиотеки и в этом уроке остаётся пустым. Метод Run запускает бесконечный цикл воркера: он подписан на SIGTERM и SIGINT, а по получении сигнала аккуратно дожидается текущих задач — поведение остановки разбираем в [уроке про graceful shutdown](/courses/asynq/lessons/19-graceful-shutdown/). Ошибка Run означает, что сервер прервался нештатно, и в main её логируют через log.Fatal.

### Воркер: NewServer, mux и Run

```go
package main

import (
    "log"

    "github.com/hibiken/asynq"
)

func main() {
    srv := asynq.NewServer(
        asynq.RedisClientOpt{Addr: "localhost:6379"},
        asynq.Config{Concurrency: 10},
    )

    mux := asynq.NewServeMux() // хендлеры появятся в третьем уроке

    log.Println("воркер слушает Redis…")
    if err := srv.Run(mux); err != nil {
        log.Fatalf("воркер остановился с ошибкой: %v", err)
    }
}
```

NewServer принимает знакомые опции соединения и Config с числом параллельных хендлеров. ServeMux создаётся пустым: маршрутизация появится вместе с первым хендлером. Run блокирует main до сигнала остановки и вернёт ошибку, если воркер прервался нештатно. Запустите этот файл — и процесс будет молча держаться в ожидании задач: так выглядит здоровый воркер без работы.

### Один процесс или два: топология проекта

Клиент и воркер могут жить в одном процессе или в двух — asynq не делает различий, потому что оба работают через один и тот же Redis. Для разработки удобнее всего один main.go: поставили задачу и тут же её исполнили, цикл обратной связи занимает секунды. В продакшене схема почти всегда разделяется: API-сервис только ставит задачи, воркеры масштабируются отдельно и переживают пики без участия веб-трафика. У разделения есть приятный побочный эффект: перезапуск API больше не роняет выполняющиеся задачи, а рестарт воркеров не трогает приём трафика. Практическое правило, к которому я пришёл на нескольких проектах: начинайте с одного процесса, разделяйте, когда воркеры впервые съедят ресурс API — обычно это совпадает с первым отчётом на миллион строк. Отдельный вопрос — версии: клиент и воркер в одном бинаре обновляются синхронно, а в двух деплоях — по очереди, и это плюс для отката, но требует обратной совместимости задач, о которой поговорим в четвёртом уроке. Кстати, о деплое: два процесса — это и два потока логов, и два набора метрик; заложите мониторинг сразу, а не после первого «воркер молчит». И в обеих топологиях используйте одно значение RedisClientOpt из конфига: разделённые конфигурации клиента и воркера — источник незаметных рассинхронов, который мы разбираем в предупреждении ниже.

> **Клиент и воркер смотрят в разные Redis.** Классика первой недели: клиент пишет в localhost:6379, воркер в контейнере — в redis:6379, и никто не замечает. Enqueue возвращает info без ошибок: задача честно лежит в своём Redis. Воркер молчит, потому что читает другой: ни ошибки, ни повтора, ни лога. Лечится одним значением opt на оба процесса из общего конфига; проверка простая — если задача не выполняется, первым делом убедитесь, что оба процесса смотрят в один инстанс.

### Redis в Docker и проверка окружения

Redis поднимается одной командой Docker: docker run --name asynq-redis -p 6379:6379 -d redis:7. Контейнер отдаёт стандартный порт 6379, пароль не установлен — для локальной разработки ровно то, что нужно. Проверка связи живёт в самом контейнере: docker exec -it asynq-redis redis-cli ping должен вернуть PONG — это ваша первая операция с Redis, дальше их будет много. В compose-мире той же цели служит сервис redis с образом redis:7: воркер и клиент будут ходить на хост redis, и этот адрес должен попасть в их общий конфиг. Пройдитесь по чек-листу ниже; когда все шесть пунктов зелёные, окружение готово до конца курса. Остановить и удалить контейнер можно docker stop и docker rm, но не спешите глушить его между уроками: задачи и расписание живут именно там. Для compose-проектов удобно сразу закоммитить сервис redis в docker-compose.yml — подъём окружения перестаёт быть ритуалом из команд и становится частью первого запуска проекта.

> **Чужой Redis — только с паролем.** Никогда не указывайте Addr с публичным IP и без пароля: Redis без requirepass в интернете находят боты за минуты, а asynq безжалостно кладёт туда задачи, которые прочитает кто угодно. Для продакшена — Username и Password из ACL, для облаков — TLSConfig; обе настройки живут в том же RedisClientOpt, ничего дополнительного подключать не нужно. И держите адрес Redis в переменных окружения: менять хост на проде через правку кода — ровно та история, которая заканчивается в warn-блоке выше.

### Чек-лист окружения одним списком

- Свежий Go с go.dev: go version отвечает без ошибок.
- Redis в Docker: docker run --name asynq-redis -p 6379:6379 -d redis:7.
- Проверка связи: docker exec -it asynq-redis redis-cli ping возвращает PONG.
- Библиотека в модуле: go get github.com/hibiken/asynq прошёл без конфликтов.
- Две точки входа: cmd/client и cmd/worker собираются через go build.
- Один RedisClientOpt из общего конфига используется и клиентом, и воркером.

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

**Вопрос:** Какая настройка задаёт число параллельных хендлеров воркера?

- ✅ Concurrency в asynq.Config
- PoolSize в RedisClientOpt
- Workers в NewServer
- DB в RedisClientOpt

Concurrency — размер пула горутин, исполняющих хендлеры; по умолчанию он равен числу CPU. PoolSize — про соединения с Redis, а не про обработку задач; поля Workers у asynq нет вовсе.

### Проверка: ленивое соединение

**Вопрос:** Что произойдёт при asynq.NewClient(...), если Redis ещё не запущен?

- Ошибка: клиент сразу подключается к Redis
- ✅ Ничего: соединение ленивое, первый контакт — на первой операции
- Паника внутри библиотеки
- Клиент попробует поднять Redis сам

NewClient лишь сохраняет настройки; пул соединений открывается на первой операции. Поэтому ошибка связи проявится не в конструкторе, а в первом Enqueue — и её надо проверять там, где работа реально делается.

### Что делает Run?

```go
srv := asynq.NewServer(opt, asynq.Config{Concurrency: 10})
mux := asynq.NewServeMux()

if err := srv.Run(mux); err != nil {
    log.Fatal(err)
}
```

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

- Запускает воркер в фоне и сразу возвращает управление
- ✅ Блокируется до сигнала SIGTERM или SIGINT
- Блокируется на минуту и выходит
- Ждёт первой задачи и возвращается после неё

Run — блокирующий запуск: он держит процесс живым, крутит цикл обработки и ловит сигналы остановки. Запуск в фоне делает другой метод, а дожидание задач по сигналу — тема урока про graceful shutdown.

### Восстановите клиентский main

Допишите конструктор клиента, тип опций, адрес и закрытие соединения.

```go
client := asynq.___(asynq.___Opt{
    Addr: "___:6379",
})
defer client.___()
```

Подсказка: Конструктор — NewClient, тип опций — RedisClientOpt, локальный адрес — localhost, пул закрывает Close.

Правильные ответы в пропусках: `NewClient`, `Redis`, `localhost`, `Close`

### Соберите каркас из шести строк

Расставьте строки так, чтобы получился корректный файл: опции соединения, клиент, воркер и запуск.

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

1. `opt := asynq.RedisClientOpt{Addr: "localhost:6379"}`
2. `client := asynq.NewClient(opt)`
3. `defer client.Close()`
4. `srv := asynq.NewServer(opt, asynq.Config{Concurrency: 10})`
5. `mux := asynq.NewServeMux()`
6. `srv.Run(mux)`

### Карточки: каркас asynq

- **RedisClientOpt** — Значение с настройками соединения: Addr, DB, Username, Password, PoolSize, TLSConfig. Один тип для клиента, воркера и Inspector — конфигурация задаётся один раз.
- **asynq.NewClient** — Конструктор клиента. Соединение ленивое: реальный контакт с Redis происходит на первой операции, а не в конструкторе.
- **asynq.NewServer** — Конструктор воркера: опции соединения плюс Config. Запускается методом Run, который блокирует процесс до сигнала остановки.
- **Config.Concurrency** — Число горутин, одновременно исполняющих хендлеры. По умолчанию — число CPU машины; для сетевых задач ставьте больше.
- **ServeMux** — Маршрутизатор задач: связывает строку-тип с функцией-хендлером, как http.ServeMux из стандартной библиотеки.
- **client.Close()** — Закрывает пул соединений с Redis. Держите в defer рядом с конструктором клиента.

Каркас готов: модуль с двумя точками входа, Redis в Docker, клиент с пулом соединений и воркер, который молча ждёт работу. Дальше — самое интересное: в третьем уроке напишем [первую задачу](/courses/asynq/lessons/03-pervaya-zadacha/) с письмом, от конструктора NewTask до хендлера и строки «отправлено» в логах, а ServeMux наконец перестанет быть пустым. В четвёртом — [payload и собственные идентификаторы](/courses/asynq/lessons/04-payload-i-id/) задач, и общий пакет tasks из этого урока заработает по назначению. Если что-то из чек-листа не сходится, вернитесь к нему сейчас: каждый следующий урок опирается на рабочее окружение, и разбирать payload без живого Redis — всё равно что учиться плавать по книге. Если воркер молчит, а задачи не идут, начинайте диагностику с redis-cli ping: в девяти случаях из десяти дальше идти не придётся. Формат запуска прежний: локальный Go и Redis из Docker, песочница показывает только тематическую программу на чистом Go.

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

- Asynq ставится одной командой: go get github.com/hibiken/asynq — без cgo и системных пакетов.
- Соединение описывает RedisClientOpt: Addr, DB, Username, Password, PoolSize, TLSConfig — одно значение для клиента, воркера и Inspector.
- Для Sentinel и кластера есть RedisFailoverClientOpt и RedisClusterClientOpt: тот же opt, тот же интерфейс соединения.
- NewClient и NewServer не подключаются сразу: первый контакт с Redis происходит на первой операции.
- Concurrency задаёт число параллельных хендлеров; по умолчанию — число CPU машины.
- Run блокирует процесс до SIGTERM/SIGINT и вернёт ошибку при нештатной остановке.
- Для разработки — клиент и воркер в одном main.go; в продакшене воркеры масштабируются отдельно.
- Redis в Docker одной командой: docker run -p 6379:6379 -d redis:7; проверка — redis-cli ping, ответ PONG.

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

Соберите структуру из урока: cmd/client с клиентским main, cmd/worker с воркером и общий opt в конфиге. Запустите воркер, убедитесь, что он держится в ожидании задач, остановите его Ctrl+C и посмотрите в лог — аккуратное завершение без паники. Затем выполните redis-cli ping из контейнера и запишите команду в шпаргалку: она пригодится в каждом следующем уроке.

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

### Какая версия Redis нужна asynq?

Библиотека работает с Redis начиная с третьей версии, но на практике берите седьмую из Docker — все уроки курса проверены на ней. Старые сборки тоже заведутся, однако ACL и пользователь с паролем из шестой версии пригодятся вам в продакшене уже в первый месяц.

### Клиент и воркер в одном бинарнике — плохая идея?

Для разработки — нормальная и удобная: один процесс, секунды обратной связи. В проде разделение даёт независимое масштабирование и изоляцию сбоев: рестарт API не роняет задачи. Разделяйте, когда воркеры впервые конкурируют с API за CPU или память.

### Почему NewClient не подключается к Redis сразу?

Соединение ленивое: конструктор лишь сохраняет настройки, а пул открывается на первой операции. Так быстрее стартуют утилиты и тесты, но ошибка связи обнаружится не в NewClient, а в первом Enqueue — поэтому его ошибку проверяют всегда.
