Кэширование в 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-фрагментов или сессий.