Ответ
Инвалидация кеша — это процесс удаления или пометки данных в кеше как неактуальных. Это одна из самых сложных проблем в кешировании. В Go-приложении можно реализовать несколько основных стратегий.
Рассмотрим на примере простого конкурентно-безопасного кеша:
import (
"sync"
"time"
)
type CacheItem struct {
Value interface{}
ExpiresAt time.Time
}
type Cache struct {
mu sync.RWMutex // RWMutex для оптимизации чтений
items map[string]CacheItem
}
func NewCache() *Cache {
return &Cache{
items: make(map[string]CacheItem),
}
}
1. Инвалидация по времени жизни (Time-To-Live, TTL)
Самая распространенная стратегия. Каждой записи в кеше присваивается срок жизни, по истечении которого она считается недействительной. Проверка происходит в момент запроса данных.
// Set добавляет элемент с TTL
func (c *Cache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = CacheItem{
Value: value,
ExpiresAt: time.Now().Add(ttl),
}
}
// Get извлекает элемент, проверяя его TTL
func (c *Cache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key]
if !found {
return nil, false
}
// Проверка TTL. Если срок истек, элемент считается отсутствующим.
if time.Now().After(item.ExpiresAt) {
// В реальном приложении здесь можно запустить горутину для удаления
// или иметь фоновый процесс очистки.
return nil, false
}
return item.Value, true
}
2. Явная (ручная) инвалидация
Данные удаляются из кеша по прямому вызову, например, после обновления этих данных в базе данных.
// Delete удаляет элемент из кеша по ключу
func (c *Cache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
// Пример использования:
// db.UpdateUser(user)
// userCache.Delete(user.ID)
3. Политики вытеснения (Eviction Policies)
Когда кеш достигает максимального размера, старые данные вытесняются, чтобы освободить место для новых. Распространенные политики:
- LRU (Least Recently Used): Вытесняется элемент, который дольше всех не использовался.
- LFU (Least Frequently Used): Вытесняется элемент, который использовался реже всего.
- FIFO (First-In, First-Out): Вытесняется самый старый элемент.
Реализация таких политик сложна, поэтому для production-систем настоятельно рекомендуется использовать готовые библиотеки, которые предоставляют эти и другие возможности "из коробки":
groupcache: Распределенный кеш от Google.ristretto: Высокопроизводительный, конкурентный кеш с LFU-политикой.go-cache: Простой кеш в памяти с TTL, похожий на наш пример, но с фоновой очисткой.
Ответ 18+ 🔞
Да ты посмотри, какая хуйня творится с этим кешированием! Инвалидация кеша — это, блядь, процесс удаления или пометки данных в кеше как неактуальных. Одна из самых ебученных проблем, на которую можно налететь, когда начинаешь с кешем работать. В Go-приложении можно, конечно, нагородить несколько стратегий, но готовься, что мозги набекрень вывернет.
Вот, смотри, пример простого конкурентно-безопасного кеша, чтоб хотя бы не грохнулось всё в один момент:
import (
"sync"
"time"
)
type CacheItem struct {
Value interface{}
ExpiresAt time.Time
}
type Cache struct {
mu sync.RWMutex // RWMutex для оптимизации чтений
items map[string]CacheItem
}
func NewCache() *Cache {
return &Cache{
items: make(map[string]CacheItem),
}
}
1. Инвалидация по времени жизни (Time-To-Live, TTL)
Ну, самая, блядь, распространённая. Каждой записи в кеше присваивается срок жизни, как молоку в холодильнике. Кончился срок — нахуй, в мусорку. Проверка происходит, когда кто-то пришёл за данными.
// Set добавляет элемент с TTL
func (c *Cache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = CacheItem{
Value: value,
ExpiresAt: time.Now().Add(ttl),
}
}
// Get извлекает элемент, проверяя его TTL
func (c *Cache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, found := c.items[key]
if !found {
return nil, false
}
// Проверка TTL. Если срок истек, элемент считается отсутствующим.
if time.Now().After(item.ExpiresAt) {
// В реальном приложении здесь можно запустить горутину для удаления
// или иметь фоновый процесс очистки.
return nil, false
}
return item.Value, true
}
2. Явная (ручная) инвалидация
Тут всё просто, как три копейки. Данные удаляются из кеша по прямому вызову, например, после того как ты эти же данные в базе обновил. Логично же, ёпта? В кеше теперь лежит хуйня старая.
// Delete удаляет элемент из кеша по ключу
func (c *Cache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
// Пример использования:
// db.UpdateUser(user)
// userCache.Delete(user.ID)
3. Политики вытеснения (Eviction Policies)
А вот это уже, блядь, ёперный театр! Когда кеш достигает максимального размера, старые данные надо выкидывать, чтобы место для новых было. Как в коммуналке, когда новый жилец приезжает. Основные политики:
- LRU (Least Recently Used): Вытесняется тот, кого дольше всех не трогали. Забытый, блядь, сирота.
- LFU (Least Frequently Used): Вытесняется тот, кого реже всего спрашивали. Непопулярный, сука.
- FIFO (First-In, First-Out): Вытесняется тот, кто первым зашёл. Старейшина, нахуй.
Реализовать эту хуйню с нуля — это ж мозги вытекут, честно. Поэтому для продакшена бери готовые библиотеки, они уже всё за тебя придумали, эти мудя:
groupcache: Распределённый кеш от гугла, серьёзная штука.ristretto: Высокопроизводительный, конкурентный кеш с LFU-политикой. Быстрый, как удар током.go-cache: Простой кеш в памяти с TTL, похожий на наш пример, но там уже фоновый уборщик мусора бегает.