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

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

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

Урок 2 из 25 ~18 мин +50 XP

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

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

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

Первый запуск из 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 закрыет эту дыру параллельно с третьим уроком. Разная топология Redis тоже ложится на тот же opt: для Sentinel существует RedisFailoverClientOpt с MasterName и SentinelAddrs, для кластера — RedisClusterClientOpt с Addrs; все три реализуют один интерфейс RedisConnOpt, поэтому клиент и воркер не знают о топологии ничего сверх этого значения. Соединение при этом ленивое — первый контакт с Redis случится на первой операции, а не в конструкторе. Поэтому клиентский main из примера выше запускается мгновенно даже без Redis — это нормальное поведение, а не лазейка: проверяйте ошибки там, где реальная работа.

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

ПолеЧто задаётПо умолчанию
Addrадрес Redis вида host:port127.0.0.1:6379
DBномер логической базы Redis0
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

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 в 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 — подъём окружения перестаёт быть ритуалом из команд и становится частью первого запуска проекта.

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

  • Свежий 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

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

client := asynq.(asynq.Opt{ Addr: ":6379", }) defer client.()

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

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

Строки

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

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

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

Термин

RedisClientOpt

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

Значение с настройками соединения: Addr, DB, Username, Password, PoolSize, TLSConfig. Один тип для клиента, воркера и Inspector — конфигурация задаётся один раз.

1 / 6

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

Личные заметкиПусто
Хранятся только в вашем браузере (Asynq · Установка: клиент, воркер и Redis)

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

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

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

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

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

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

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

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

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

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

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

Kafka в Go: продюсеры и консьюмеры на kafka-go

Курс Kafka для Go: топики и партиции, producer и consumer, группы, оффсеты, гарантии доставки, rebalance, Admin API и мониторинг. 25 уроков.

  • Понимать топики, партиции, оффсеты и группы консьюмеров
  • Писать продюсеры с ключами, батчами и ретраями
  • Читать сообщения группой с коммитами оффсетов
  • Выбирать гарантии: at-most-once, at-least-once