---
title: "go-redis v9: установка и первое подключение"
description: "go-redis подключение к Redis из Go: go get, redis.NewClient с Options, Ping(ctx) и первые ошибки — connection refused, пароль и DB. Примеры кода v9."
course: "Redis в Go: кэш, сессии и очереди на go-redis"
url: "https://golangmaster.ru/courses/go-redis/lessons/02-ustanovka-i-podklyuchenie/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# go-redis v9: установка и первое подключение

*Курс «Redis в Go: кэш, сессии и очереди на go-redis», урок 2 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/go-redis/lessons/02-ustanovka-i-podklyuchenie/*

go-redis превращает подключение к Redis из Go в три шага: go get, redis.NewClient и один Ping для проверки связи. В этом уроке поднимем сервер в Docker, соберём рабочий main.go с полями Options и разберём первые ошибки — от connection refused до пустого ответа. Скелет, который здесь получится, будет повторяться во всех следующих уроках, поэтому соберём его аккуратно.

Точка старта: пустая папка, установленный 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 и клиент в модуле

```go
docker 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 целиком

```go
package 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.** Самая первая ошибка, которую видит каждый: 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-сервисе вместо него передают контекст запроса, полученный от хендлера, а точечные таймауты навешивают там, где важна скорость ответа. Привычка одна: контекст — не формальность, а ручка, за которую операцию можно выключить. Мертвой и живой сети с точки зрения кода не отличить, и спасает только дисциплина: подробный разбор — в отдельном уроке про [контекст и таймауты](/courses/go-redis/lessons/18-rabota-s-kontekstom/).

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

**Вопрос:** Что вернёт rdb.Ping(ctx).Result() на живом сервере?

- ✅ Строку PONG и nil-ошибку
- true и nil-ошибку
- Пустую строку и nil-ошибку
- Панику, если Redis в Docker

Ping возвращает *redis.StatusCmd; метод Result даёт строку PONG и nil. Формат ответа фиксирован протоколом — именно поэтому PONG используют как маркер здоровья в health-check.

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

**Вопрос:** Когда redis.NewClient открывает сетевое соединение?

- Сразу при вызове
- ✅ При первой команде
- При rdb.Close()
- Соединение держит redis-cli, клиент его не открывает

Клиент создаётся без обращения к сети; первое соединение откроется на первой команде. Поэтому несуществующий адрес в Options не роняет программу до первого вызова — а вот первая команда упадёт.

### Проверка: недоступный сервер

**Вопрос:** Сервер не запущен. Что вернёт Ping?

- redis.Nil
- ✅ Сетевую ошибку вида connection refused
- PONG с nil-ошибкой
- Программа зависнет навсегда

redis.Nil — сигнал «ключа нет», он относится к данным, а не к связи. Недоступный сервер даёт сетевую ошибку dial tcp ... connection refused; спасают DialTimeout в Options и контекст с таймаутом.

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

```go
rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})
ctx := context.Background()

pong, err := rdb.Ping(ctx).Result()
fmt.Println(pong, err)
```

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

- ✅ PONG <nil>
- <nil> redis: nil
- PONG
- true nil

Ping вернул строку PONG, ошибка равна nil — fmt печатает её как <nil>. Разделитель — пробел, как всегда у Println. Такой вывод — сигнал, что связь установлена и можно работать с данными.

### Соберите конфигурацию клиента

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

```go
rdb := redis.___(&redis.Options{
        Addr:     "___",
        Password: "",
        DB:       ___,
})

pong, err := rdb.___(ctx).Result()
```

Подсказка: Конструктор — NewClient; адрес по умолчанию localhost:6379; база нулевая; связь проверяет Ping.

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

### Соберите минимальный main()

Расставьте строки минимальной программы: контекст, клиент, закрытие, вывод Ping.

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

1. `func main() {`
2. `ctx := context.Background()`
3. `rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})`
4. `defer rdb.Close()`
5. `fmt.Println(rdb.Ping(ctx).Result())`
6. `}`

### Карточки: установка и подключение

- **go-redis v9** — Актуальная версия клиента: импорт github.com/redis/go-redis/v9, пакет называется redis, каждая команда принимает context.Context.
- **redis.NewClient** — Фабрика клиента: redis.NewClient(&redis.Options{...}). Клиент потокобезопасен, создаётся один раз на приложение.
- **redis.Options** — Поля Addr (адрес:порт), Password, DB (0-15), DialTimeout, PoolSize — вся конфигурация подключения в одной структуре.
- **Ping** — Проверка связи: rdb.Ping(ctx).Result() возвращает PONG и nil при живом сервере. Дешёвый health-check для /healthz.
- **Ленивое подключение** — NewClient не ходит в сеть; первое соединение открывается на первой команде. Пул клиент восстанавливает сам после перезапуска сервера.
- **connection refused** — Сетевая ошибка «порт никто не слушает»: Redis не запущен, контейнер без -p 6379:6379 или адрес в Addr неверный.

Скелет готов: сервер в Docker, клиент в go.mod, PONG в консоли. Дальше начинается содержательная часть — в уроке про [типы данных Redis](/courses/go-redis/lessons/03-tipy-dannyh-redis/) разберём пять структур и выбор под задачу; полный план из двадцати пяти уроков — на [странице курса](/courses/go-redis/). Если чувствуете, что подводит база — go.mod, обработка ошибок, контекст — вернитесь к [базовому курсу Go](/courses/go-basics/): всё дальнейшее опирается именно на него. Напомню про формат: весь код курса запускается только локально, песочница не поднимает ни Docker, ни TCP, поэтому домашнее задание выполняется на вашей машине. Если цепочка где-то порвалась — возвращайтесь к врезке про connection refused: это самая частая точка старта и на курсе, и в реальных проектах.

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

- Установка: 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, чтобы не ждать вечно.
