news 2026/5/8 22:05:33

Cache缓存项目学习3

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cache缓存项目学习3
Group设计
type Group struct { name string getter Getter mainCache *Cache peers PeerPicker loader *singleflight.Group expiration time.Duration // 缓存过期时间,0表示永不过期 closed int32 // 原子变量,标记组是否已关闭 stats groupStats // 统计信息 }

Getter

作为当缓存中拿不到数据后与数据库的连接接口。

type Getter interface { Get(ctx context.Context, key string) ([]byte, error) }
Cache

是对存储的封装,通过store接口提供可插拔底层缓存设计。支持LRU2、LRU等缓存接口。

Options提供缓存配置。其余是辅助记录信息。

PeerPicker

提供peer选择器接口。

type ClientPicker struct { selfAddr string svcName string mu sync.RWMutex consHash *consistenthash.Map clients map[string]*Client etcdCli *clientv3.Client ctx context.Context cancel context.CancelFunc }

通过一致性哈希来选择节点。从clients中获取grpc客户端并与之通信。

Loader

负责单飞机制,一批请求只允许放行一个,其他请求共享结果。

// Do 针对相同的key,保证多次调用Do(),都只会调用一次fn func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { // Check if there is already an ongoing call for this key if existing, ok := g.m.Load(key); ok { c := existing.(*call) c.wg.Wait() // Wait for the existing request to finish return c.val, c.err // Return the result from the ongoing call } // If no ongoing request, create a new one c := &call{} c.wg.Add(1) g.m.Store(key, c) // Store the call in the map // Execute the function and set the result c.val, c.err = fn() c.wg.Done() // Mark the request as done // After the request is done, clean up the map g.m.Delete(key) return c.val, c.err }

各模块详细介绍

Getter
type Getter interface { Get(ctx context.Context, key string) ([]byte, error) } // GetterFunc 函数类型实现 Getter 接口 type GetterFunc func(ctx context.Context, key string) ([]byte, error) // Get 实现 Getter 接口 func (f GetterFunc) Get(ctx context.Context, key string) ([]byte, error) { return f(ctx, key) }

Getter使用策略模式,定义了使用接口,并使用GetterFunc类型自动实现了Get接口,让传参既可以是简单方法也可以是结构体。

Getter是作为数据库接口,当加载数据时发现缓存没有数据时对远程数据库进行数据加载。

func (g *Group) loadData(ctx context.Context, key string) (value ByteView, err error) { // 尝试从远程节点获取 ........ // 从数据源加载 bytes, err := g.getter.Get(ctx, key) if err != nil { return ByteView{}, fmt.Errorf("failed to get data: %w", err) } return ByteView{b: cloneBytes(bytes)}, nil }
Cache

Cache是对本地缓存存储实例的封装。

type Cache struct { mu sync.RWMutex store store.Store // 底层存储实现 opts CacheOptions // 缓存配置选项 hits int64 // 缓存命中次数 misses int64 // 缓存未命中次数 initialized int32 // 原子变量,标记缓存是否已初始化 closed int32 // 原子变量,标记缓存是否已关闭 }

Store接口是底层缓存实例的封装,opts是与store相关的配置信息。

store
//根据缓存类型和具体配置进行实例化 c.store = store.NewStore(c.opts.CacheType, storeOpts) func NewStore(cacheType CacheType, opts Options) Store { switch cacheType { case LRU2: return newLRU2Cache(opts) case LRU: return newLRUCache(opts) default: return newLRUCache(opts) } } //store 接口声明 type Store interface { Get(key string) (Value, bool) Set(key string, value Value) error SetWithExpiration(key string, value Value, expiration time.Duration) error Delete(key string) bool Clear() Len() int Close() }
store接口实例
type lruCache struct { mu sync.RWMutex list *list.List // 双向链表,用于维护 LRU 顺序 items map[string]*list.Element // 键到链表节点的映射 expires map[string]time.Time // 过期时间映射 maxBytes int64 // 最大允许字节数 usedBytes int64 // 当前使用的字节数 onEvicted func(key string, value Value) cleanupInterval time.Duration cleanupTicker *time.Ticker closeCh chan struct{} // 用于优雅关闭清理协程 }

cleanupInterval:清理时间间隔,超过则启动清理

cleanupTicker:根据cleanupInterval生成的定时器,定时执行清理任务

func newLRUCache{ c.cleanupTicker = time.NewTicker(c.cleanupInterval) go c.cleanupLoop() } func (c *lruCache) cleanupLoop() { for { select { case <-c.cleanupTicker.C: c.mu.Lock() c.evict() //清理 c.mu.Unlock() case <-c.closeCh: return } } }

list:维护元素顺序,便于淘汰时进行针对性删除

items:映射键与元素

expires:映射键与过期时间

func (c *lruCache) Get(key string) (Value, bool) { c.mu.RLock() elem, ok := c.items[key] if !ok { c.mu.RUnlock() return nil, false } // 检查是否过期 if expTime, hasExp := c.expires[key]; hasExp && time.Now().After(expTime) { c.mu.RUnlock() // 异步删除过期项,避免在读锁内操作 go c.Delete(key) return nil, false } // 获取值并释放读锁 entry := elem.Value.(*lruEntry) value := entry.value c.mu.RUnlock() // 更新 LRU 位置需要写锁 c.mu.Lock() // 再次检查元素是否仍然存在(可能在获取写锁期间被其他协程删除) if _, ok := c.items[key]; ok { c.list.MoveToBack(elem) } c.mu.Unlock() return value, true } func (c *lruCache) SetWithExpiration(key string, value Value, expiration time.Duration) error { if value == nil { c.Delete(key) return nil } c.mu.Lock() defer c.mu.Unlock() // 计算过期时间 var expTime time.Time if expiration > 0 { expTime = time.Now().Add(expiration) c.expires[key] = expTime } else { delete(c.expires, key) } // 如果键已存在,更新值 if elem, ok := c.items[key]; ok { oldEntry := elem.Value.(*lruEntry) c.usedBytes += int64(value.Len() - oldEntry.value.Len()) oldEntry.value = value c.list.MoveToBack(elem) return nil } // 添加新项 entry := &lruEntry{key: key, value: value} elem := c.list.PushBack(entry) c.items[key] = elem c.usedBytes += int64(len(key) + value.Len()) // 检查是否需要淘汰旧项 c.evict() return nil }
PeerPicker
// PeerPicker 定义了peer选择器的接口 type PeerPicker interface { PickPeer(key string) (peer Peer, ok bool, self bool) Close() error } type ClientPicker struct { selfAddr string svcName string mu sync.RWMutex consHash *consistenthash.Map clients map[string]*Client etcdCli *clientv3.Client ctx context.Context cancel context.CancelFunc }

PeerPicker核心通过consHash一致性哈希来解决问题。

一致性Hash并不知道Key在哪里,也不存储具体的K-V数据,只负责计算。

对于输入的Key,它输出应该去哪个节点寻找。纯本地计算,没有网络通信。

具体一致性哈希算法详解,见下一节。

流程:

loader

单飞设计,防止击穿。

的实现使用 sync.Map ,对相同 Key 的并发请求只执行一次加载,有效避免缓存雪崩。

// 代表正在进行或已结束的请求 type call struct { wg sync.WaitGroup val interface{} err error } // Group manages all kinds of calls type Group struct { m sync.Map // 使用sync.Map来优化并发性能 } // Do 针对相同的key,保证多次调用Do(),都只会调用一次fn func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { // Check if there is already an ongoing call for this key if existing, ok := g.m.Load(key); ok { c := existing.(*call) c.wg.Wait() // Wait for the existing request to finish return c.val, c.err // Return the result from the ongoing call } // If no ongoing request, create a new one c := &call{} c.wg.Add(1) g.m.Store(key, c) // Store the call in the map // Execute the function and set the result c.val, c.err = fn() c.wg.Done() // Mark the request as done // After the request is done, clean up the map g.m.Delete(key) return c.val, c.err }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 21:59:32

基于AI与RPA的智能求职自动化系统设计与实现

1. 项目概述&#xff1a;当求职自动化遇上AI与RPA最近在技术社区里&#xff0c;看到不少朋友在讨论一个叫auto_job__find__chatgpt__rpa的项目。光看这个标题&#xff0c;就让我这个在招聘和自动化领域摸爬滚打了十来年的老鸟眼前一亮。这名字拆开来看&#xff0c;auto_job_fin…

作者头像 李华
网站建设 2026/5/8 21:52:34

好用的WMS解决方案哪家好

在当今竞争激烈的商业环境中&#xff0c;仓库管理的效率和准确性对于企业的成功至关重要。WMS&#xff08;仓库管理系统&#xff09;作为一种关键的工具&#xff0c;能够帮助企业实现精细化管理仓库业务&#xff0c;提升运营效率。那么&#xff0c;好用的WMS解决方案哪家好呢&a…

作者头像 李华
网站建设 2026/5/8 21:47:42

开源项目蓝图:从TypeScript到Vite的工程化实践与自动化流程

1. 项目概述&#xff1a;从蓝图到现实&#xff0c;一个开源项目的诞生逻辑在开源世界里&#xff0c;每天都有成千上万的新项目诞生&#xff0c;但真正能沉淀下来、形成社区、产生价值的&#xff0c;往往是那些从一开始就拥有清晰“蓝图”的项目。今天要聊的&#xff0c;不是一个…

作者头像 李华
网站建设 2026/5/8 21:46:33

ZynqMP SD卡启动全记录:从Vivado配置到Linux命令行(基于黑金AXU2CGB板)

ZynqMP SD卡启动实战指南&#xff1a;黑金AXU2CGB开发板全流程解析 当一块崭新的ZynqMP开发板摆在面前&#xff0c;如何快速搭建完整的启动环境往往是开发者面临的第一个挑战。不同于传统嵌入式系统&#xff0c;ZynqMP的异构架构和多重启动阶段让许多初次接触的工程师感到困惑。…

作者头像 李华
网站建设 2026/5/8 21:28:29

技术创业者如何用Bootstrapping模式实现零成本启动与快速验证

1. 从“灵光一现”到“现实骨感”&#xff1a;一个博士生创业者的第一课几年前&#xff0c;我还是个埋头在实验室里捣鼓能量收集技术的博士生&#xff0c;满脑子都是微瓦级的功率优化和晦涩的论文。有一天&#xff0c;盯着桌上那台崭新的iPad&#xff0c;一个念头突然蹦出来&am…

作者头像 李华