Каждая запись хранит собственный срок, а таймер удаляет только то поколение, для которого создан. Get дополнительно проверяет TTL независимо от задержки таймера.
Кеш в памяти с индивидуальным временем жизни для записей
Каждая запись хранит собственный срок, а таймер удаляет только то поколение, для которого создан. Get дополнительно проверяет TTL независимо от задержки таймера.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Реализовать кеш, который хранится в оперативной памяти и обладает следующими свойствами:
- каждый элемент представляет собой пару k → v;
- у каждого v может быть собственный параметр TTL (time‑to‑live);
- после истечения указанного TTL значение должно автоматически удаляться из кеша без внешних запросов.
Ответ
Каждая запись хранит собственный срок, а таймер удаляет только то поколение, для которого создан. Get дополнительно проверяет TTL независимо от задержки таймера.
Минимальный потокобезопасный кеш строк:
import (
"sync"
"time"
)
type ttlEntry struct {
value string
expires time.Time
}
type TTLCache struct {
mu sync.Mutex
items map[string]*ttlEntry
}
func (c *TTLCache) Set(key, value string, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
if c.items == nil { c.items = make(map[string]*ttlEntry) }
if ttl <= 0 { delete(c.items, key); return }
entry := &ttlEntry{value, time.Now().Add(ttl)}
c.items[key] = entry
time.AfterFunc(ttl, func() {
c.mu.Lock()
defer c.mu.Unlock()
if c.items[key] == entry { delete(c.items, key) }
})
}
func (c *TTLCache) Get(key string) (string, bool) {
c.mu.Lock()
defer c.mu.Unlock()
entry, ok := c.items[key]
if !ok { return "", false }
if !time.Now().Before(entry.expires) {
delete(c.items, key)
return "", false
}
return entry.value, true
}
Нулевое значение кеша работоспособно. Старый callback сравнивает указатель и не удаляет обновлённую запись. При частых обновлениях остаются старые таймеры до их срабатывания: для большого кеша нужны управление таймерами или общая очередь сроков и лимит памяти.