Кэширование в Go: Redis, Memcached и встроенные решения (продвинутый уровень)

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

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

Начать курс

Введение: Зачем нужно кэширование в Go на продвинутом уровне?

В высоконагруженных системах на Go (Golang) скорость доступа к данным часто становится узким местом. База данных не всегда способна обработать тысячи запросов в секунду без существенных задержек. Кэширование — это ключевой инструмент для снижения нагрузки на БД и ускорения ответа API.

На начальном уровне разработчики часто используют простые map с мьютексами. Однако в продакшене требуются более продвинутые решения: распределённые кэши (Redis, Memcached) и высокопроизводительные in-memory библиотеки (BigCache, FreeCache). В этой статье мы разберём их особенности, сравним производительность и покажем примеры кода на Go для каждого варианта.

1. Встроенные решения: sync.Map и библиотеки BigCache/FreeCache

1.1. sync.Map — когда не нужна внешняя зависимость

Пакет sync.Map — это потокобезопасная map, оптимизированная для сценариев, где ключи записываются один раз, а читаются много раз. Он не требует блокировок на чтение, что даёт прирост производительности.

Пример использования:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var cache sync.Map

    // Запись
    cache.Store("user:1", "Alice")

    // Чтение
    if value, ok := cache.Load("user:1"); ok {
        fmt.Println("Найдено:", value)
    }

    // Удаление
    cache.Delete("user:1")
}

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

1.2. BigCache — быстрый in-memory кэш с TTL

BigCache от Allegro — это библиотека, которая хранит данные в байтовых массивах, избегая накладных расходов GC. Она поддерживает TTL и автоматическую эвакуацию старых записей.

Пример:

import (
    "github.com/allegro/bigcache/v3"
    "time"
)

func main() {
    cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))

    // Запись
    cache.Set("mykey", []byte("myvalue"))

    // Чтение
    entry, _ := cache.Get("mykey")
    println(string(entry))
}

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

1.3. FreeCache — альтернатива с фиксированным размером

FreeCache — ещё одна in-memory библиотека с настраиваемым размером кэша. Она использует сегментирование для уменьшения блокировок.

import "github.com/coocood/freecache"

func main() {
    // Размер кэша — 100 МБ
    cache := freecache.NewCache(100 * 1024 * 1024)

    cache.Set([]byte("key"), []byte("value"), 60) // TTL 60 сек
    got, _ := cache.Get([]byte("key"))
    println(string(got))
}

2. Redis — распределённое кэширование с продвинутыми структурами

Redis — это самый популярный внешний кэш для Go-приложений. Он поддерживает не только строки, но и списки, хэши, множества, а также TTL, Pub/Sub и транзакции.

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

Для работы с Redis в Go используется драйвер go-redis/redis (или redis/v8).

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

var ctx = context.Background()

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })

    // Установка с TTL 10 секунд
    err := rdb.Set(ctx, "key", "value", 10*time.Second).Err()
    if err != nil {
        panic(err)
    }

    val, err := rdb.Get(ctx, "key").Result()
    if err != redis.Nil {
        println(val)
    }
}

2.2. Продвинутые паттерны: кэширование с обновлением (Cache-Aside)

В продвинутых сценариях используется паттерн Cache-Aside: при промахе кэша данные загружаются из БД и сохраняются в Redis.

func GetUser(userID string) (*User, error) {
    // Попытка получить из кэша
    val, err := rdb.Get(ctx, "user:"+userID).Bytes()
    if err == nil {
        var user User
        json.Unmarshal(val, &user)
        return &user, nil
    }

    // Промах — загружаем из БД
    user, err := db.GetUser(userID)
    if err != nil {
        return nil, err
    }

    // Сохраняем в кэш на 5 минут
    data, _ := json.Marshal(user)
    rdb.Set(ctx, "user:"+userID, data, 5*time.Minute)

    return user, nil
}

2.3. Redis Cluster и Pipeline для высокой нагрузки

Для масштабирования используйте Redis Cluster. А для массовых операций — Pipeline, который уменьшает количество сетевых вызовов:

pipe := rdb.Pipeline()
pipe.Set(ctx, "key1", "val1", 0)
pipe.Set(ctx, "key2", "val2", 0)
pipe.Exec(ctx)

3. Memcached — лёгкий и быстрый, но с ограничениями

Memcached — это распределённый кэш, проще Redis. Он не поддерживает сложные структуры данных и постоянство, но даёт высокую скорость и низкое потребление памяти.

3.1. Подключение через go-memcached

Используем библиотеку bradfitz/gomemcache.

import "github.com/bradfitz/gomemcache/memcache"

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

    // Запись с TTL (секунды)
    err := mc.Set(&memcache.Item{
        Key:        "mykey",
        Value:      []byte("myvalue"),
        Expiration: 60,
    })

    // Чтение
    item, err := mc.Get("mykey")
    if err == nil {                

Блоги

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