Skip to content

Commit

Permalink
✨ feat: Day6 防止缓存击穿
Browse files Browse the repository at this point in the history
  • Loading branch information
p3ddd committed Apr 6, 2022
1 parent e420e43 commit 151c2ec
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 9 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
## 分布式缓存

#### 对于给定的 key,每次都选择同一个节点
#### 一致性哈希

对于给定的 key,每次都选择同一个节点

节点固定时:

把 key 的每一个字符的 ASCII 码加起来,除以 10 取余数

hash('Tom') % 10
hash('Tom') % 10

#### 防止缓存击穿

**缓存雪崩**:缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。

**缓存击穿**:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。

**缓存穿透**:查询一个不存在的数据,因为不存在则不会写到缓存中,所以每次都会去请求 DB,如果瞬间流量过大,穿透到 DB,导致宕机。
27 changes: 20 additions & 7 deletions geecache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package geecache

import (
"fmt"
"geecache/singleflight"
"log"
"sync"
)
Expand Down Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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 从访问远程节点,获取缓存值
Expand Down
41 changes: 41 additions & 0 deletions singleflight/singleflight.go
Original file line number Diff line number Diff line change
@@ -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 // 返回结果
}

0 comments on commit 151c2ec

Please sign in to comment.