diff --git a/README.md b/README.md index 4d32d04..7ee7d19 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ ## 分布式缓存 -#### 对于给定的 key,每次都选择同一个节点 +#### 一致性哈希 + +对于给定的 key,每次都选择同一个节点 节点固定时: 把 key 的每一个字符的 ASCII 码加起来,除以 10 取余数 -hash('Tom') % 10 \ No newline at end of file +hash('Tom') % 10 + +#### 防止缓存击穿 + +**缓存雪崩**:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。 + +**缓存击穿**:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。 + +**缓存穿透**:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。 \ No newline at end of file diff --git a/geecache.go b/geecache.go index 6982ff7..c11b134 100644 --- a/geecache.go +++ b/geecache.go @@ -2,6 +2,7 @@ package geecache import ( "fmt" + "geecache/singleflight" "log" "sync" ) @@ -29,6 +30,9 @@ type Group struct { getter Getter mainCache cache peers PeerPicker + // use singleflight.Group to make sure that + // each key is only fetched once + loader *singleflight.Group } var ( @@ -46,6 +50,7 @@ func NewGroup(name string, cacheBytes int64, getter Getter) *Group { name: name, getter: getter, mainCache: cache{cacheBytes: cacheBytes}, + loader: &singleflight.Group{}, } groups[name] = g // 其实只需要对 map 加锁 return g @@ -82,16 +87,24 @@ func (g *Group) Get(key string) (ByteView, error) { } func (g *Group) load(key string) (value ByteView, err error) { - if g.peers != nil { - if peer, ok := g.peers.PickPeer(key); ok { - if value, err = g.getFromPeer(peer, key); err == nil { - return value, nil + // 无论有多少调用,每个 key 只会获取一次(不论本地或远程) + viewi, err := g.loader.Do(key, func() (interface{}, error) { + if g.peers != nil { + if peer, ok := g.peers.PickPeer(key); ok { + if value, err = g.getFromPeer(peer, key); err == nil { + return value, nil + } + log.Println("[GeeCache] Failed to get from peer", err) } - log.Println("[GeeCache] Failed to get from peer", err) } - } - return g.getLocally(key) + return g.getLocally(key) + }) + + if err == nil { + return viewi.(ByteView), nil + } + return } // getFromPeer 使用实现了 PeerGetter 接口的 httpGetter 从访问远程节点,获取缓存值 diff --git a/singleflight/singleflight.go b/singleflight/singleflight.go new file mode 100644 index 0000000..175e4b1 --- /dev/null +++ b/singleflight/singleflight.go @@ -0,0 +1,41 @@ +package singleflight + +import "sync" + +// 正在进行中,或已经结束的请求 +type call struct { + wg sync.WaitGroup + val interface{} + err error +} + +// 管理不同 key 的请求(call) +type Group struct { + mu sync.Mutex // 保护 m 不被并发读写 + m map[string]*call +} + +func (g *Group) Do(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() + c.wg.Wait() // 如果请求正在进行中,则等待 + return c.val, c.err // 请求结束,返回结果 + } + c := new(call) + c.wg.Add(1) // 发起请求前加锁 + g.m[key] = c // 添加到 g.m,表明 key 已经有对应的请求在处理 + g.mu.Unlock() + + c.val, c.err = fn() // 调用 fn,发起请求 + c.wg.Done() // 请求结束 + + g.mu.Lock() + delete(g.m, key) // 更新 g.m + g.mu.Unlock() + + return c.val, c.err // 返回结果 +}