Содержание урока · 11 разделов
- Установка: go get github.com/redis/go-redis/v9
- Подключение к Redis из Go: redis.NewClient
- Основные поля redis.Options
- Типичная ошибка: клиент go-redis на каждый запрос
- Спорное мнение: паника при старте — честное решение
- Ленивое подключение и Close
- Кейс из продакшена: Redis поднялся, а Ping не проходит
- Контекст в каждом вызове: отличие v9
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Точка старта: пустая папка, установленный Go и желание к вечеру положить в Redis первую строку. Чего не хватает — сервера Redis и клиентской библиотеки в модуле. Сервер собирать из исходников не будем: официальный образ в Docker поднимает полноценный Redis одной командой, и для разработки этого достаточно почти всегда — отдельная установка как системный сервис нужна, только если вы хотите жить без Docker. С клиентом есть ловушка, на которой спотыкаются все, кто пришёл по старым статьям: имя модуля менялось дважды, поэтому в сети легко найти импорты вида github.com/go-redis/redis, github.com/go-redis/redis/v8 и github.com/redis/go-redis/v9 — живым из них сейчас только последний. В этом уроке поставим правильную версию, напишем первое подключение с Ping и научимся читать ошибки, которые выдаёт клиент, когда сервер недоступен. И сразу честно о формате: запускать код будем локально — браузерная песочница сайта сеть не умеет, ей нужен чистый Go без пакетов.
Установка: go get github.com/redis/go-redis/v9#
Установка клиента — одна команда go get, как для любой библиотеки Go. Если модуля в папке ещё нет, сначала go mod init с именем модуля, например example.com/cache: без go.mod менеджер зависимостей не знает, куда записывать пакет. Сам путь импорта читается так: домен redis на GitHub — организация, в которую переехал проект в 2022 году; go-redis — репозиторий; /v9 — мажорная версия по правилам семантического версионирования Go, без неё компилятор не найдёт пакет. Имя пакета при этом осталось прежним — redis: импортируете github.com/redis/go-redis/v9, а в коде обращаетесь redis.NewClient, без алиасов и переименований. После go get в go.mod появится строка с версией вида v9.x.x, а в коде — готовый к использованию пакет. Старые туториалы с восьмой версией компилируются редко и учат API без контекстов, поэтому копировать импорты из статей до 2022 года — плохая идея.
Сервер в Docker и клиент в модуле
GOdocker run --name go-redis-course -p 6379:6379 -d redis:7
go mod init example.com/cache
go get github.com/redis/go-redis/v9Первая команда поднимает официальный образ Redis седьмой версии и пробрасывает порт 6379 на localhost — после неё сервер уже принимает подключения. go mod init создаёт модуль, go get скачивает клиент v9 и фиксирует его в go.mod. Живость сервера проверяют без Go: docker exec -it go-redis-course redis-cli ping ответит PONG.
Подключение к Redis из Go: redis.NewClient#
Подключение к Redis в go-redis собирается из двух частей: конфигурация redis.Options и фабрика redis.NewClient, которая возвращает клиент. Клиент потокобезопасен и держит пул соединений внутри, поэтому в приложении его создают один раз и переиспользуют, а не строят заново на каждый запрос — это не httputil-клиент из туториалов, где за соединением следишь сам. Полный скелет с проверкой связи — ниже; он же станет шаблоном для всех примеров курса.
main.go с Ping целиком
GOpackage main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // пароль не задан
DB: 0,
})
ctx := context.Background()
pong, err := rdb.Ping(ctx).Result()
if err != nil {
panic(err)
}
fmt.Println(pong) // PONG
}redis.NewClient принимает указатель на redis.Options: Addr — адрес и порт сервера, Password — пароль, если он задан, DB — номер логической базы от 0 до 15. Ping отправляет серверу одноимённую команду и возвращает StatusCmd, из которой метод Result достаёт строку и ошибку: живой сервер отвечает PONG. Если err не nil — связи нет, работать дальше бессмысленно. Импорт обязательно с /v9 на конце.
Основные поля redis.Options#
- Addr — адрес и порт сервера, по умолчанию localhost:6379.
- Password — пароль авторизации; пустая строка значит работать без него.
- DB — номер логической базы: по умолчанию Redis держит 16 изолированных нумерованных баз.
- DialTimeout — сколько ждать установления соединения, прежде чем сдаться.
- ReadTimeout и WriteTimeout — дедлайны на чтение и запись в сокет.
- PoolSize — максимум соединений в пуле; подробный разбор пула ближе к двадцатому уроку.
Типичная ошибка: клиент go-redis на каждый запрос#
Типичная ошибка, которая проявляется не сразу: redis.NewClient вызывается в хендлере при каждом обращении. Несколько дней ничего не происходит, потом сервис начинает жить странно — растёт число открытых сокетов, в логах мелькают задержки на установку связи, а в пики соединения упираются в лимит операционной системы. Причина в том, что каждый клиент тащит собственный пул, и большинство его соединений простаивает: на сотню одновременных запросов приходит сотня пулов по несколько связей. Клиент в go-redis потокобезопасен именно затем, чтобы его создавали один раз — на старте приложения, в main или в конструкторе зависимостей — и передавали всем, кому нужен доступ. Отдельный клиент оправдан разве что для второго сервера или изолированной задачи с другими таймаутами. Увидели NewClient вне инициализации — вопрос на code review: зачем?
Спорное мнение: паника при старте — честное решение#
Спорное мнение из моих привычек: panic на провале первого Ping — нормально, и убирать его из скелета не нужно. Скрипт или утилита без Redis бессмысленны, и честнее упасть сразу с понятной ошибкой, чем отрабатывать впустую и падать дальше с запутанным стеком: фейл-фаст экономит время разбора. Граница применимости — долгоживущие сервисы: там паника на старте превращается в цикл рестартов, поэтому вместо неё ставят ретраи с дедлайном, а после порога всё-таки паникуют, чтобы оркестратор перезапустил контейнер. В HTTP-хендлерах панике вообще не место: живость проверяет Ping на /healthz, и он же даёт балансировщику сигнал вывести под из ротации. Одно правило объединяет все три мира: ошибки старта нельзя глотать — сервис, который притворяется живым, вредит больше упавшего.
Ленивое подключение и Close#
redis.NewClient не устанавливает соединение в момент вызова: клиент создаётся мгновенно, а первое реальное подключение произойдёт при первой команде. Это удобно — конфигурацию можно строить заранее, а живость сервера проверять отдельно; для этого и существует Ping, дешёвый health-check, который часто вешают на эндпоинт /healthz. Пул соединений клиент открывает и закрывает сам: если сервер ночью перезапустился, он восстановит связи без вашего участия — переподключение его работа, а не ваша. Ваш долг один — закрыть клиент при завершении приложения через rdb.Close(), чаще всего с defer сразу после создания. В тестах этот же факт спасает: NewClient с несуществующим адресом не упадёт до первой команды, поэтому конструктор спокойно живёт в setup-функции.
Самая первая ошибка, которую видит каждый: dial tcp 127.0.0.1:6379: connect: connection refused. Читается она буквально: порт никто не слушает — Redis не запущен, контейнер поднят без -p 6379:6379, порт занят другой службой или адрес в Addr указывает не туда. Порядок действий: docker ps — контейнер жив; redis-cli ping внутри контейнера — сервер отвечает; проверка порта снаружи — доступен ли localhost:6379. В девяти случаях из десяти виноват забытый проброс порта.
Кейс из продакшена: Redis поднялся, а Ping не проходит#
Кейс, который ломает уверенность в скелете урока: docker-compose поднимает приложение и Redis вместе, контейнер базы уже зелёный, а Ping в приложении всё равно падает с connection refused. Причина — два разных события: контейнер запущен и сервер готов принимать команды. Redis инициализирует свои структуры не мгновенно, и в этот промежуток порт либо ещё не слушается, либо слушается с отказами. Лечится по-разному в зависимости от окружения: в docker-compose есть depends_on с условием service_healthy и healthcheck, в Kubernetes — readiness-пробы и init-контейнеры, в простом скрипте — цикл с ретраями и паузой. Смысл один: старт приложения не должен зависеть от того, успел ли сосед приготовиться. Пара ретраев на Ping с задержкой в полсекунды почти всегда закрывает вопрос и стоит три строки кода. Без них старт иногда проходит, а иногда падает — самая неприятная разновидность бага, воспроизводимая только на деплое.
Контекст в каждом вызове: отличие v9#
В v9 каждый метод клиента первым параметром принимает context.Context — принципиальное отличие от восьмой версии, где контекста не было вовсе. Контекст даёт единую механику отмены и таймаутов: если клиент запроса ушёл, отменяется и обращение к Redis; если сервер завис, context.WithTimeout оборвёт ожидание и вернёт ошибку дедлайна. В примерах курса стоит context.Background() — контекст без дедлайна, нейтральный для скриптов; в реальном HTTP-сервисе вместо него передают контекст запроса, полученный от хендлера, а точечные таймауты навешивают там, где важна скорость ответа. Привычка одна: контекст — не формальность, а ручка, за которую операцию можно выключить. Мертвой и живой сети с точки зрения кода не отличить, и спасает только дисциплина: подробный разбор — в отдельном уроке про контекст и таймауты.
Проверка: Ping
Что вернёт rdb.Ping(ctx).Result() на живом сервере?
Проверка: момент соединения
Когда redis.NewClient открывает сетевое соединение?
Проверка: недоступный сервер
Сервер не запущен. Что вернёт Ping?
Что выведет фрагмент?
Прочитайте код и выберите, что он выведет:
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx := context.Background()
pong, err := rdb.Ping(ctx).Result()
fmt.Println(pong, err)Соберите конфигурацию клиента
Допишите конструктор, стандартный адрес, нулевую базу и команду проверки связи.
Соберите минимальный main()
Расставьте строки минимальной программы: контекст, клиент, закрытие, вывод Ping.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: установка и подключение
Скелет готов: сервер в Docker, клиент в go.mod, PONG в консоли. Дальше начинается содержательная часть — в уроке про типы данных Redis разберём пять структур и выбор под задачу; полный план из двадцати пяти уроков — на странице курса. Если чувствуете, что подводит база — go.mod, обработка ошибок, контекст — вернитесь к базовому курсу Go: всё дальнейшее опирается именно на него. Напомню про формат: весь код курса запускается только локально, песочница не поднимает ни Docker, ни TCP, поэтому домашнее задание выполняется на вашей машине. Если цепочка где-то порвалась — возвращайтесь к врезке про connection refused: это самая частая точка старта и на курсе, и в реальных проектах.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Установка: go mod init, затем go get github.com/redis/go-redis/v9 — путь с /v9 обязателен.
- Сервер для разработки поднимает Docker: docker run -p 6379:6379 -d redis:7.
- redis.NewClient(&redis.Options{Addr, Password, DB}) создаёт потокобезопасный клиент один раз на приложение.
- Клиент ленив: первое соединение откроется на первой команде; живость проверяет Ping → PONG.
- v9 требует context.Context в каждой команде — отличие от v8 и рычаг для таймаутов.
- connection refused — не запущен сервер или забыт проброс порта в Docker.
- Код курса запускается локальным Go: песочница сети не умеет.
Домашнее задание
Соберите скелет урока по памяти: docker run с пробросом порта, go mod init, go get, main.go с NewClient и Ping. Запустите локально и убедитесь, что в консоли PONG. Затем сломайте связку специально — остановите контейнер docker stop go-redis-course — и посмотрите на ошибку connection refused своими глазами: её надо узнавать в лицо.
Личные заметкиПусто
Вопросы и ответы по уроку
Нужен ли пароль для локального Redis в Docker?
Нет: официальный образ без флагов поднимается без авторизации, поэтому в Options достаточно пустой строки Password. Пароль задают флагом --requirepass и файлом конфигурации в продакшене — тогда клиент передаёт его в поле Password, а без него сервер отвечает ошибкой NOAUTH.
Что такое DB 0-15 и зачем нужны нумерованные базы?
Redis по умолчанию держит 16 логически изолированных пространств ключей; клиент выбирает их полем DB в Options. Для разделения окружений это плохая практика — берите разные инстансы или префиксы ключей; нумерованные базы остаются наследием и удобны в маленьких проектах.
Почему NewClient не падает, если Redis ещё не запущен?
Клиент ленивый: конструктор только сохраняет конфигурацию, а сеть трогает при первой команде. Поэтому несуществующий адрес не роняет программу при старте — упадёт первый вызов. Проверяйте связь Ping-ом и настраивайте DialTimeout, чтобы не ждать вечно.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
JWT-аутентификация в Go: golang-jwt и bcrypt
Разбираем JWT в Go: устройство токена, golang-jwt v5, подписи HMAC и RSA, refresh-токены, bcrypt, middleware авторизации и лучшие практики.
- Понимать анатомию JWT и почему payload не зашифрован
- Выпускать и проверять токены через golang-jwt v5
- Хешировать пароли bcrypt и сравнивать безопасно
- Писать middleware аутентификации для chi и Gin