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

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

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

Начать курс

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

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

1. Встроенное кэширование: sync.Map и map с мьютексом

Go предлагает базовые примитивы для создания кэша прямо в памяти приложения. Самый простой способ — использовать обычный map с мьютексом для потокобезопасности.

Пример: простой потокобезопасный кэш

package main

import (
    "fmt"
    "sync"
    "time"
)

type Cache struct {
    mu    sync.RWMutex
    items map[string]interface{}
}

func NewCache() *Cache {
    return &Cache{
        items: make(map[string]interface{}),
    }
}

func (c *Cache) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = value
}

func (c *Cache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    val, ok := c.items[key]
    return val, ok
}

func main() {
    cache := NewCache()
    cache.Set("user:1", "Alice")
    val, _ := cache.Get("user:1")
    fmt.Println(val) // Alice
}

Для более сложных сценариев Go предоставляет sync.Map — оптимизированную конкурентную карту, которая лучше подходит для случаев, когда ключи пишутся один раз, а читаются много раз.

Когда использовать встроенное кэширование?

  • Микро-кэширование (на время одного запроса или сессии).
  • Синглтон-приложения без распределённой архитектуры.
  • Простота — не нужно поднимать внешние сервисы.

Минусы: отсутствие автоматической инвалидации, ограниченный размер (зависит от памяти приложения), нет персистентности.

2. BigCache и FreeCache: легковесные in-memory решения

Библиотеки bigcache и freecache решают проблему ограниченной памяти и GC (сборки мусора). Они используют предварительно выделенные буферы и минимизируют нагрузку на сборщик мусора Go.

Пример с BigCache

package main

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

func main() {
    cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
    cache.Set("mykey", []byte("myvalue"))
    val, _ := cache.Get("mykey")
    fmt.Println(string(val)) // myvalue
}

Пример с FreeCache

package main

import (
    "fmt"
    "github.com/coocood/freecache"
)

func main() {
    cache := freecache.NewCache(100 * 1024) // 100 KB
    cache.Set([]byte("key"), []byte("value"), 60) // TTL 60 сек
    val, _ := cache.Get([]byte("key"))
    fmt.Println(string(val)) // value
}

Обе библиотеки поддерживают TTL (время жизни) и автоматическое удаление старых записей. BigCache лучше для больших объёмов данных (сотни гигабайт), FreeCache — для сценариев с частыми операциями записи/чтения.

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

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

Подключение Redis в Go

package main

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

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

    err := rdb.Set(ctx, "key", "value", 0).Err()
    if err != nil {
        panic(err)
    }

    val, err := rdb.Get(ctx, "key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("key:", val)
}

Продвинутые возможности Redis

  • Списки и множества — для очередей и тегов.
  • Pub/Sub — для уведомлений об инвалидации кэша.
  • TTL и LRU-эвикция — автоматическое удаление устаревших данных.
  • Кластеризация — для горизонтального масштабирования.

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

4. 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("my value"),
    })

    item, _ := mc.Get("foo")
    fmt.Println(string(item.Value)) // my value
}

Когда выбирать Memcached?

  • Высокая скорость чтения/записи простых строк.
  • Не нужна персистентность и сложные структуры.
  • Масштабирование через консистентное хеширование.

Memcached отлично подходит для кэширования результатов SQL-запросов, HTML-фрагментов или сессий.

Сравнительная таблица

Критери

Блоги

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