本文主要说明如何用 Go 实现简单的本地缓存系统 SDK。
1. 目标特性
-
✅ 通用缓存接口(Set/Get/Delete)
-
✅ 支持 TTL(过期自动清除)
-
✅ 并发安全(支持高并发读写)
-
✅ 易于集成到任意平台(封装为模块)
2. 简易版实现
localcache/
├── cache.go # 外部可用接口
└── entry.go # 缓存条目定义
✅ 1. 缓存条目定义 entry.go
package localcache
import "time"
type entry struct {
value interface{}
expireAt time.Time
hasExpired bool
}
func (e *entry) isExpired() bool {
if !e.hasExpired {
return false
}
return time.Now().After(e.expireAt)
}
✅ 2. 缓存实现 cache.go
package localcache
import (
"sync"
"time"
)
type Cache struct {
items map[string]*entry
mu sync.RWMutex
ttl time.Duration
quit chan struct{}
}
func NewCache(defaultTTL time.Duration) *Cache {
c := &Cache{
items: make(map[string]*entry),
ttl: defaultTTL,
quit: make(chan struct{}),
}
go c.cleanupLoop()
return c
}
func (c *Cache) Set(key string, value interface{}, ttl ...time.Duration) {
exp := c.ttl
if len(ttl) > 0 {
exp = ttl[0]
}
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = &entry{
value: value,
expireAt: time.Now().Add(exp),
hasExpired: exp > 0,
}
}
func (c *Cache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ok := c.items[key]
if !ok || item.isExpired() {
return nil, false
}
return item.value, true
}
func (c *Cache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
func (c *Cache) cleanupLoop() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.cleanExpired()
case <-c.quit:
return
}
}
}
func (c *Cache) cleanExpired() {
c.mu.Lock()
defer c.mu.Unlock()
now := time.Now()
for k, v := range c.items {
if v.hasExpired && now.After(v.expireAt) {
delete(c.items, k)
}
}
}
func (c *Cache) Close() {
close(c.quit)
}
✅ 3. 如何使用
cache := localcache.NewCache(10 * time.Minute)
defer cache.Close()
cache.Set("username", "huweihuang")
value, found := cache.Get("username")
if found {
fmt.Println("Got:", value)
}
3. 增强版实现
基于最大容量的缓存,并使用 LRU(Least Recently Used,最近最少使用)策略进行淘汰。
我们在前面提供的 Cache
实现基础上,引入 双向链表 + map 来实现高效的 O(1) 时间复杂度的 LRU 缓存淘汰机制。
✅ 增强目标
-
✅ 支持最大容量限制
maxEntries
-
✅ 最近使用的缓存项保留,最久未使用的优先被淘汰
-
✅ 并发安全、支持 TTL 清理
-
✅
Get
/Set
都更新访问时间
✅ 实现结构
LRUCache
├── map[string]*list.Element // 快速查找
├── list.List // 保存访问顺序,头是最近使用,尾是最久未使用
└── entry { key, value, expireAt }
✅ LRU 缓存结构代码
package lru
import (
"container/list"
"sync"
"time"
)
type entry struct {
key string
value interface{}
expireAt time.Time
ttlSet bool
}
type LRUCache struct {
capacity int
mu sync.Mutex
ll *list.List
cache map[string]*list.Element
ttl time.Duration
}
func NewLRUCache(capacity int, defaultTTL time.Duration) *LRUCache {
return &LRUCache{
capacity: capacity,
ll: list.New(),
cache: make(map[string]*list.Element),
ttl: defaultTTL,
}
}
✅ 核心操作逻辑
Set
func (c *LRUCache) Set(key string, value interface{}, ttl ...time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
if ele, ok := c.cache[key]; ok {
// update existing
en := ele.Value.(*entry)
en.value = value
en.expireAt = c.getExpireAt(ttl)
en.ttlSet = true
c.ll.MoveToFront(ele)
return
}
// new entry
en := &entry{
key: key,
value: value,
expireAt: c.getExpireAt(ttl),
ttlSet: true,
}
ele := c.ll.PushFront(en)
c.cache[key] = ele
if c.ll.Len() > c.capacity {
c.removeOldest()
}
}
func (c *LRUCache) getExpireAt(ttl []time.Duration) time.Time {
d := c.ttl
if len(ttl) > 0 {
d = ttl[0]
}
if d == 0 {
return time.Time{}
}
return time.Now().Add(d)
}
Get
func (c *LRUCache) Get(key string) (interface{}, bool) {
c.mu.Lock()
defer c.mu.Unlock()
ele, ok := c.cache[key]
if !ok {
return nil, false
}
en := ele.Value.(*entry)
if en.ttlSet && !en.expireAt.IsZero() && time.Now().After(en.expireAt) {
c.removeElement(ele)
return nil, false
}
c.ll.MoveToFront(ele)
return en.value, true
}
removeOldest
& removeElement
func (c *LRUCache) removeOldest() {
ele := c.ll.Back()
if ele != nil {
c.removeElement(ele)
}
}
func (c *LRUCache) removeElement(e *list.Element) {
en := e.Value.(*entry)
delete(c.cache, en.key)
c.ll.Remove(e)
}
✅ 使用方式示例
cache := lru.NewLRUCache(100, 5*time.Minute)
cache.Set("a", "1")
cache.Set("b", "2")
val, ok := cache.Get("a") // 会变为“最近使用”
4. 原理
要理解这个 Go 实现的 本地 LRU 缓存系统 的原理,我们可以从两部分来讲:基本思想 和 核心数据结构设计。
🔧 4.1. 基本原理(逻辑流程)
📌 缓存的本质:
-
把热点数据保存在内存中,提高读取速度。
-
每个缓存项可能有「生命周期」(TTL)。
-
当缓存空间满了,需要淘汰旧的数据。
📌 LRU(Least Recently Used)策略:
-
含义:优先淘汰最久没有被访问过的缓存项。
-
适用场景:访问数据具有局部性原则(近期使用的数据可能会再次被访问)。
📌 怎么实现 LRU + TTL:
功能 | 实现方法 |
---|---|
O(1) 访问 | 用 map[string]*list.Element 快速查找 key |
O(1) 更新访问顺序 | 用 container/list 双向链表(LRU 队列) |
O(1) 淘汰最旧项 | 从链表尾部直接删除 |
TTL 过期 | 每个 entry 存 expireAt 时间戳,访问时检查 |
🧠 4.2. 核心数据结构设计
1. Map + 双向链表
LRU 队列(最近访问的放头部):
[Most Recently Used] ---> [Middle] ---> [Least Recently Used]
↑ ↑
list.Front() list.Back()
type LRUCache struct {
ll *list.List // 双向链表:记录访问顺序
cache map[string]*list.Element // 快速定位 key -> 链表节点
}
2. 每个缓存 entry 包含信息
type entry struct {
key string
value interface{}
expireAt time.Time // TTL 过期时间
ttlSet bool // 是否设置了 TTL
}
⚙️ 4.3. 工作流程举例
1. 添加元素 Set("a", 1)
:
-
如果
a
已存在:更新值,移动到链表头部。 -
如果不存在:
-
创建新 entry,放到链表头部。
-
如果超过最大容量:删除链表尾部元素。
-
2. 访问元素 Get("a")
:
-
找到
a
对应的链表节点。 -
如果 TTL 未过期,移动它到链表头部(表示最近使用)。
3. 淘汰策略:
- 添加新元素时,如果超过容量,就执行:
removeElement(list.Back()) // 淘汰最久未用的缓存项
🛡️ 4.4. 并发安全
-
所有操作都使用
sync.Mutex
加锁。 -
确保在多 goroutine 下不会出现竞争状态。
✅ 总结一句话:
这个缓存系统本质上是一个「哈希表 + 双向链表」的组合,用于快速定位 + 快速维护访问顺序,并结合 TTL 保证缓存不过期且高效清理。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.