2024-04-20
Go & 后端
00

目录

1 Day6 防止缓存击穿

主要内容:

  1. Day6 防止缓存击穿

1 Day6 防止缓存击穿

还是拿昨天的图说明。

aa203d2d34545afb6afb03262027c2a.jpg

在这套流程里,如果一次性对同一个接口请求多次,那么这些请求也会一直到缓存里甚至是数据库里,有可能引发缓存击穿,毕竟这套流程里并没有对请求和调用进行限制。

对此,可以在GeeCache里通过实现一个singleflight的包来进行并发保护。

go
// src/geecache/singleflight/singleflight.go package singleflight import "sync" // call 含义为正在进行中 或者是已经结束的请求 type call struct { wg sync.WaitGroup // 避免重入 val interface{} err error } // Group 对call进行管理 type Group struct { mu sync.Mutex // 互斥锁 这个锁是保护下面的m的 m map[string]*call } // DoFunc 要保护的方法调用入口 针对同样的key 传入的fn只会被调用一次 直到第一次fn的调用完成 func (g *Group) DoFunc (key string, fn func() (interface{}, error)) (interface{}, error) { g.mu.Lock() // 加锁 if g.m == nil { // 如果没初始化的话先初始化 g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { // 已经存在之前的请求 则等待之前的请求完成 直接返回之前的请求结果 g.mu.Unlock() // 读m完成 解锁 c.wg.Wait() // 等待信号量归零 return c.val, c.err } // 如果不存在相同的请求 则新增请求 c := new(call) c.wg.Add(1) // 开始调用 信号量+1 g.m[key] = c // 写入m g.mu.Unlock() // 读写m完成 解锁 c.val, c.err = fn() // 实际调用 c.wg.Done() // 调用已完成 信号量-1 g.mu.Lock() // 加锁 delete(g.m, key) // 请求结束 删除 g.mu.Unlock() // 写入m完成 解锁 return c.val, c.err }

之后再融合进Group中:

go
// src/geecache/geecache.go // 仅展示修改的三个部分 // Group 缓存对外交互的核心数据结构 type Group struct { name string // 该缓存的标识 getter Getter // 缓存未能命中时的回调函数 类型是Getter接口 mainCache cache // 缓存主体 是具有并发保护的LRU缓存 peers PeerPicker // 这是实现了PeerPicker的HTTPPool // attention 为什么要将远程节点集成进HTTPPool 而不是节点本身? 是否可以优化? loader *singleflight.Group // 非本地缓存的并发请求管理 } // NewGroup 构造函数 func NewGroup(name string, cacheBytes int64, getter Getter) *Group { if getter == nil { panic("nil getter") } mu.Lock() defer mu.Unlock() group := &Group{ name: name, getter: getter, mainCache: cache{cacheBytes: cacheBytes}, loader: &singleflight.Group{}, } groups[name] = group return group } // load 缓存未命中时 从别的地方加载缓存 func (g *Group) load(key string) (value ByteView, err error) { viewi, err := g.loader.DoFunc(key, func() (interface{}, error) { if g.peers != nil { if peer, ok := g.peers.PickPeer(key); ok { // 先从存储着远程节点信息的HTTPPool中选出具体的远程节点 if value, err := g.getFromPeer(peer, key); err != nil { // 再根据这个具体的远程节点开始请求 return value, nil } log.Println("[GeeCache] Failed to get from peer", err) } } return g.getFromLocal(key) }) if err == nil { return viewi.(ByteView), err } return }

本文作者:御坂19327号

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!