Введение в go-redis: мощный Redis клиент для Go
Redis — это высокопроизводительное хранилище данных типа "ключ-значение", работающее в оперативной памяти. Оно используется для кэширования, управления сессиями, очередей сообщений, рейтингов и многого другого. Для взаимодействия с Redis из Go существует несколько библиотек, и go-redis (пакет github.com/redis/go-redis/v9) является самой популярной, активно поддерживаемой и функциональной.
Эта библиотека предоставляет чистый, идиоматичный Go API для работы со всеми структурами данных Redis (строки, списки, множества, хеши, сортированные множества, HyperLogLog, потоки, геопространственные данные) и поддерживает такие продвинутые функции, как кластеризация, Sentinel, Pub/Sub, конвейеризация (pipelining) и Lua-скрипты. go-redis полностью поддерживает Redis 7 и 8.
В этом руководстве мы подробно разберем установку, основные методы, типичные ошибки и напишем реальный практический пример.
Установка go-redis
Установка производится стандартной командой go get:
go get github.com/redis/go-redis/v9
После выполнения этой команды в вашем go.mod появится зависимость. Убедитесь, что у вас установлен Go версии 1.18 или выше.
Также вам потребуется работающий сервер Redis (локально или удаленно). Для тестов можно запустить Redis в Docker:
docker run --name my-redis -p 6379:6379 -d redis
Быстрый старт: подключаемся и делаем первый запрос
Создайте файл main.go и напишите простейшую программу, которая подключается к Redis, записывает и читает значение.
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
// Создаем контекст (рекомендуется использовать context.Background())
ctx := context.Background()
// Создаем клиента
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // адрес Redis сервера
Password: "", // пароль (если есть)
DB: 0, // номер базы данных (по умолчанию 0)
})
// Проверяем соединение с помощью Ping
pong, err := rdb.Ping(ctx).Result()
if err != nil {
panic(fmt.Sprintf("Не удалось подключиться к Redis: %v", err))
}
fmt.Println("Подключено:", pong)
// Устанавливаем ключ "name" со значением "Alice"
err = rdb.Set(ctx, "name", "Alice", 0).Err()
if err != nil {
panic(err)
}
// Читаем значение по ключу "name"
val, err := rdb.Get(ctx, "name").Result()
if err != nil {
panic(err)
}
fmt.Println("name:", val)
// Закрываем соединение при завершении программы
defer rdb.Close()
}
Запустите программу:
go run main.go
Вы должны увидеть:
Подключено: PONG
name: Alice
Основные методы go-redis (8+ штук)
Все методы go-redis возвращают объект *Cmd (или его специализацию), который имеет метод .Result() для получения результата и ошибки, .Val() для получения только значения и .Err() для получения только ошибки. Это позволяет гибко обрабатывать результаты.
1. Set — установка значения ключа
Что делает: Устанавливает значение для указанного ключа. Можно задать время жизни (TTL) или 0 для бессрочного хранения.
Параметры:
ctx context.Context— контекст для управления временем выполненияkey string— имя ключаvalue interface{}— значение (будет сериализовано)expiration time.Duration— время жизни (0 — без TTL)
Пример:
err := rdb.Set(ctx, "user:100", "John Doe", 10*time.Minute).Err()
if err != nil {
log.Fatal(err)
}
2. Get — получение значения ключа
Что делает: Возвращает строковое значение по ключу. Если ключ не существует, возвращает ошибку redis.Nil.
Параметры:
ctx context.Contextkey string
Пример:
val, err := rdb.Get(ctx, "user:100").Result()
if err == redis.Nil {
fmt.Println("Ключ не найден")
} else if err != nil {
log.Fatal(err)
} else {
fmt.Println("Значение:", val)
}
3. Del — удаление одного или нескольких ключей
Что делает: Удаляет указанные ключи. Возвращает количество удаленных ключей.
Параметры:
ctx context.Contextkeys ...string— один или несколько ключей
Пример:
deleted, err := rdb.Del(ctx, "user:100", "temp_key").Result()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Удалено ключей: %d\n", deleted)
4. Exists — проверка существования ключей
Что делает: Проверяет, существуют ли указанные ключи. Возвращает количество существующих ключей.
Параметры:
ctx context.Contextkeys ...string
Пример:
exists, err := rdb.Exists(ctx, "user:100", "nonexistent").Result()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Существует ключей: %d\n", exists)
5. Expire — установка времени жизни ключа
Что делает: Устанавливает время жизни (TTL) для существующего ключа. Возвращает true, если ключ существует и TTL установлен.
Параметры:
ctx context.Contextkey stringexpiration time.Duration
Пример:
ok, err := rdb.Expire(ctx, "session:abc", 30*time.Minute).Result()
if err != nil {
log.Fatal(err)
}
if ok {
fmt.Println("Время жизни установлено")
}
6. Incr / IncrBy — атомарное увеличение числа
Что делает: Атомарно увеличивает значение ключа на 1 (Incr) или на заданное число (IncrBy). Если ключ не существует, он создается со значением 0 перед увеличением.
Параметры (IncrBy):
ctx context.Contextkey stringvalue int64— число, на которое увеличить
Пример:
newVal, err := rdb.Incr(ctx, "counter").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Новое значение счетчика:", newVal)
// Увеличить на 10
newVal2, err := rdb.IncrBy(ctx, "counter", 10).Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("После IncrBy 10:", newVal2)
7. HSet / HGet / HGetAll — работа с хешами
Что делает: Хеши в Redis — это структура данных для хранения пар "поле-значение". HSet устанавливает одно или несколько полей, HGet получает одно поле, HGetAll получает все поля.
Параметры (HSet):
ctx context.Contextkey stringvalues ...interface{}— чередующиеся поле, значение
Пример:
// Устанавливаем поля хеша "user:1"
err := rdb.HSet(ctx, "user:1", "name", "Bob", "age", 30, "city", "NYC").Err()
if err != nil {
log.Fatal(err)
}
// Получаем одно поле
name, err := rdb.HGet(ctx, "user:1", "name").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Имя:", name)
// Получаем все поля
userData, err := rdb.HGetAll(ctx, "user:1").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Все данные пользователя:", userData)
8. LPush / RPush / LPop / RPop — работа со списками
Что делает: Списки Redis — это упорядоченные коллекции строк. LPush добавляет элемент в начало, RPush — в конец, LPop удаляет и возвращает первый элемент, RPop — последний.
Пример:
// Добавляем элементы в список "tasks"
rdb.RPush(ctx, "tasks", "task1", "task2", "task3")
rdb.LPush(ctx, "tasks", "urgent_task")
// Извлекаем первый элемент
first, err := rdb.LPop(ctx, "tasks").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("Первый элемент:", first)
// Получаем длину списка
len, _ := rdb.LLen(ctx, "tasks").Result()
fmt.Println("Длина списка:", len)
9. SAdd / SMembers / SIsMember — работа с множествами
Что делает: Множества Redis — это неупорядоченные коллекции уникальных строк. SAdd добавляет элементы, SMembers возвращает все элементы, SIsMember проверяет наличие элемента.
Пример:
// Добавляем теги
rdb.SAdd(ctx, "tags:article:1", "go", "redis", "programming")
// Проверяем наличие тега
exists, _ := rdb.SIsMember(ctx, "tags:article:1", "go").Result()
fmt.Println("Тег 'go' существует:", exists)
// Получаем все теги
tags, _ := rdb.SMembers(ctx, "tags:article:1").Result()
fmt.Println("Все теги:", tags)
10. ZAdd / ZRangeByScore — работа с сортированными множествами
Что делает: Сортированные множества похожи на обычные, но каждый элемент имеет "вес" (score), по которому происходит сортировка. ZAdd добавляет элементы с весом, ZRangeByScore возвращает элементы в заданном диапазоне весов.
Пример:
// Добавляем игроков с их рейтингом
rdb.ZAdd(ctx, "leaderboard",
redis.Z{Score: 100, Member: "Alice"},
redis.Z{Score: 200, Member: "Bob"},
redis.Z{Score: 150, Member: "Charlie"},
)
// Получаем игроков с рейтингом от 100 до 200
players, _ := rdb.ZRangeByScore(ctx, "leaderboard", &redis.ZRangeBy{
Min: "100",
Max: "200",
}).Result()
fmt.Println("Игроки в диапазоне:", players)
Реальный практический пример: кэширование результатов API
Рассмотрим типичный сценарий: у нас есть функция, которая получает данные из внешнего API (например, погоду). Чтобы не делать запросы слишком часто, мы кэшируем результат в Redis на 5 минут.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
// WeatherData структура для хранения данных о погоде
type WeatherData struct {
Temperature float64 `json:"temperature"`
Humidity int `json:"humidity"`
City string `json:"city"`
}
// getWeatherFromAPI имитирует запрос к внешнему