主要内容:
还是拿昨天的图说明。
在这套流程里,如果一次性对同一个接口请求多次,那么这些请求也会一直到缓存里甚至是数据库里,有可能引发缓存击穿,毕竟这套流程里并没有对请求和调用进行限制。
对此,可以在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 许可协议。转载请注明出处!