Кэширование в Go: Redis, Memcached и встроенные решения

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Кэширование в Go: Redis, Memcached и встроенные решения

Кэширование — один из ключевых инструментов для повышения производительности веб-приложений и микросервисов. В Go (Golang) существует несколько подходов: от простых встроенных структур данных до мощных распределённых систем вроде Redis и Memcached. В этой статье мы разберём основные варианты кэширования, их преимущества, недостатки и приведём практические примеры кода.

Зачем нужно кэширование в Go?

Go славится своей скоростью и эффективностью, но даже в нём часто встречаются узкие места: медленные запросы к базе данных, внешние API или тяжёлые вычисления. Кэширование позволяет:

  • Снизить нагрузку на базу данных и внешние сервисы.
  • Уменьшить время ответа API (latency).
  • Сэкономить ресурсы сервера (CPU, память).
  • Повысить отказоустойчивость при временных сбоях.

Выбор конкретного решения зависит от архитектуры приложения: монолит или микросервисы, требования к согласованности данных, объём кэша и бюджет.

Встроенное кэширование в Go

Для простых сценариев (например, кэширование данных в рамках одного процесса) подходят встроенные средства Go. Они не требуют установки внешних сервисов и идеальны для небольших приложений.

1. sync.Map

sync.Map — потокобезопасная map из стандартной библиотеки Go. Подходит для кэширования, которое редко обновляется и часто читается. Пример:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var cache sync.Map

    // Сохраняем данные
    cache.Store("user_1", "Alice")
    cache.Store("user_2", "Bob")

    // Читаем данные
    if val, ok := cache.Load("user_1"); ok {
        fmt.Println("Found:", val)
    }

    // Удаляем
    cache.Delete("user_2")
}

Плюсы: простота, нет внешних зависимостей, потокобезопасность. Минусы: нет TTL (времени жизни), нет автоматической очистки, ограничен одним процессом.

2. Пакет go-cache

Популярная библиотека github.com/patrickmn/go-cache добавляет поддержку TTL и автоматическую очистку. Пример:

package main

import (
    "fmt"
    "time"
    "github.com/patrickmn/go-cache"
)

func main() {
    // Создаём кэш с временем жизни 5 минут и очисткой каждые 10 минут
    c := cache.New(5*time.Minute, 10*time.Minute)

    // Сохраняем с TTL
    c.Set("session_token", "abc123", cache.DefaultExpiration)

    // Получаем
    token, found := c.Get("session_token")
    if found {
        fmt.Println("Token:", token)
    }

    // Удаляем
    c.Delete("session_token")
}

Плюсы: TTL, простота интеграции, автоматическая очистка. Минусы: данные хранятся в памяти одного процесса, не подходит для распределённых систем.

Redis: распределённое кэширование для Go

Redis — самое популярное решение для кэширования в Go. Он работает как отдельный сервер, поддерживает сложные структуры данных (строки, хеши, списки, множества) и имеет отличную поддержку в Go через драйвер go-redis.

Подключение и базовые операции

package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()

    // Подключение к Redis
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // без пароля
        DB:       0,
    })

    // Сохраняем строку с TTL (1 час)
    err := rdb.Set(ctx, "key", "value", 3600*time.Second).Err()
    if err != nil {
        panic(err)
    }

    // Читаем
    val, err := rdb.Get(ctx, "key").Result()
    if err != nil {
        fmt.Println("Key not found")
    } else {
        fmt.Println("Value:", val)
    }

    // Удаляем
    rdb.Del(ctx, "key")
}

Кэширование сериализованных структур

Для кэширования сложных объектов используйте сериализацию в JSON:

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func cacheUser(rdb *redis.Client, ctx context.Context, user User) error {
    data, _ := json.Marshal(user)
    return rdb.Set(ctx, fmt.Sprintf("user:%d", user.ID), data, 10*time.Minute).Err()
}

func getUser(rdb *redis.Client, ctx context.Context, id int) (*User, error) {
    data, err := rdb.Get(ctx, fmt.Sprintf("user:%d", id)).Bytes()
    if err != nil {
        return nil, err
    }
    var user User
    json.Unmarshal(data, &user)
    return &user, nil
}

Плюсы Redis: распределённость, поддержка TTL, сложные структуры данных, Pub/Sub, персистентность. Минусы: дополнительный сервер, потребление памяти, сетевая задержка.

Memcached: лёгкое и быстрое кэширование

Memcached — ещё один популярный инструмент, но он проще Redis: только строки, нет персистентности, меньше функций. Однако он быстрее на простых операциях и потребляет меньше памяти.

Пример с драйвером bradfitz/gomemcache

package main

import (
    "fmt"
    "github.com/bradfitz/gomemcache/memcache"
)

func main() {
    mc := memcache.New("localhost:11211")

    // Сохраняем
    mc.Set(&memcache.Item{Key: "foo", Value: []byte("bar"), Expiration: 3600})

    // Читаем
    item, err := mc.Get("foo")
    if err != nil {
        fmt.Println("Not found")
    } else {
        fmt.Println("Value:", string(item.Value))
    }

    // Удаляем
    mc.Delete("foo")
}

Плюсы Memcached: высокая скорость, минимальное потребление памяти, простота. Минусы: только строки, нет TTL по умолчанию (задаётся в секундах), нет персистентности, данные могут быть потеряны при перезапуске.

Сравнение решений для Go

Блоги

Рекомендаций по книгам