Содержание урока · 10 разделов
- Asynq установка: go get и структура проекта
- Asynq redis подключение: RedisClientOpt
- Поля RedisClientOpt, которые реально пригодятся
- Воркер: asynq.NewServer и 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
GOpackage 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 закрыет эту дыру параллельно с третьим уроком. Разная топология 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. Ошибка Run означает, что сервер прервался нештатно, и в main её логируют через log.Fatal.
Воркер: NewServer, mux и Run
GOpackage 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 из конфига: разделённые конфигурации клиента и воркера — источник незаметных рассинхронов, который мы разбираем в предупреждении ниже.
Классика первой недели: клиент пишет в 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 — подъём окружения перестаёт быть ритуалом из команд и становится частью первого запуска проекта.
Никогда не указывайте 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
Какая настройка задаёт число параллельных хендлеров воркера?
Проверка: ленивое соединение
Что произойдёт при asynq.NewClient(...), если Redis ещё не запущен?
Что делает Run?
Прочитайте код и выберите, что он выведет:
srv := asynq.NewServer(opt, asynq.Config{Concurrency: 10})
mux := asynq.NewServeMux()
if err := srv.Run(mux); err != nil {
log.Fatal(err)
}Восстановите клиентский main
Допишите конструктор клиента, тип опций, адрес и закрытие соединения.
Соберите каркас из шести строк
Расставьте строки так, чтобы получился корректный файл: опции соединения, клиент, воркер и запуск.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: каркас asynq
Каркас готов: модуль с двумя точками входа, Redis в Docker, клиент с пулом соединений и воркер, который молча ждёт работу. Дальше — самое интересное: в третьем уроке напишем первую задачу с письмом, от конструктора NewTask до хендлера и строки «отправлено» в логах, а ServeMux наконец перестанет быть пустым. В четвёртом — payload и собственные идентификаторы задач, и общий пакет tasks из этого урока заработает по назначению. Если что-то из чек-листа не сходится, вернитесь к нему сейчас: каждый следующий урок опирается на рабочее окружение, и разбирать payload без живого Redis — всё равно что учиться плавать по книге. Если воркер молчит, а задачи не идут, начинайте диагностику с redis-cli ping: в девяти случаях из десяти дальше идти не придётся. Формат запуска прежний: локальный Go и Redis из Docker, песочница показывает только тематическую программу на чистом Go.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- 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 — поэтому его ошибку проверяют всегда.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Kafka в Go: продюсеры и консьюмеры на kafka-go
Курс Kafka для Go: топики и партиции, producer и consumer, группы, оффсеты, гарантии доставки, rebalance, Admin API и мониторинг. 25 уроков.
- Понимать топики, партиции, оффсеты и группы консьюмеров
- Писать продюсеры с ключами, батчами и ретраями
- Читать сообщения группой с коммитами оффсетов
- Выбирать гарантии: at-most-once, at-least-once