Введение: Зачем нужно кэширование в 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 {