索引
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day1 - 實(shí)現(xiàn)LRU算法
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day2 - 單機(jī)并發(fā)緩存
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day3 - HTTP 服務(wù)端
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day4 - 一致性哈希(hash)
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day5 - 分布式節(jié)點(diǎn)
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day6 - 防止緩存擊穿
【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day7 - 使用 Protobuf 通信
收獲
- 了解緩存穿透,緩存雪崩, 緩存擊穿
- 知道怎么防止緩存擊穿
- 了解匿名函數(shù)的使用
- 加深了鎖的使用
- 了解空接口interface{}和模板的區(qū)別和作用
介紹緩存穿透,緩存雪崩, 緩存擊穿
概念可以看我這篇博客緩存穿透,緩存雪崩,緩存擊穿概念及解決方法
簡(jiǎn)單說(shuō)一下緩存擊穿,就是在緩存中找不到數(shù)據(jù),導(dǎo)致直接訪問(wèn)數(shù)據(jù)庫(kù),降低效率
為什么我們要防止緩存擊穿
當(dāng)前我們所做的項(xiàng)目對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)沒(méi)有做任何限制的,所以當(dāng)客戶端發(fā)起大量請(qǐng)求,很容易導(dǎo)致緩存擊穿和穿透,所以我們需要減少重復(fù)請(qǐng)求
引入singleflight
singleflight是一種用于減少重復(fù)請(qǐng)求的技術(shù),它可以避免在高并發(fā)場(chǎng)景下出現(xiàn)重復(fù)的請(qǐng)求。
singleflight的實(shí)現(xiàn)方式是在請(qǐng)求前先檢查是否已經(jīng)有相同的請(qǐng)求正在處理,如果有,則等待該請(qǐng)求的處理結(jié)果并直接返回,避免重復(fù)發(fā)起請(qǐng)求。
實(shí)現(xiàn)singleflight數(shù)據(jù)結(jié)構(gòu)
package singleflight
import "sync"
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
type Group struct {
mu sync.Mutex // protects m
m map[string]*call
}
- call:表示正在進(jìn)行中,或已經(jīng)完成的函數(shù)調(diào)用,其中wg字段是
sync.WaitGroup
鎖避免沖突,val字段是一個(gè)空接口,err字段表示函數(shù)調(diào)用是否發(fā)生了錯(cuò)誤。 - Group:表示一組正在進(jìn)行中的函數(shù)調(diào)用,其中mu字段是一個(gè)互斥鎖,用于保護(hù)m字段的讀寫,m字段是一個(gè)map,用于存儲(chǔ)正在進(jìn)行中的函數(shù)調(diào)用
這里我對(duì)sync.WaitGroup
和interface{}
空接口解釋一下:
sync.WaitGroup
用于等待一組goroutine執(zhí)行完成。主要用于在主goroutine等待一組子goroutine執(zhí)行完畢后再繼續(xù)執(zhí)行的場(chǎng)景。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-418845.html
interface{} 空接口
我們知道接口時(shí)一組方法的集合,接口類型定義了一組方法,但沒(méi)有實(shí)現(xiàn)這些方法??战涌诶锩鏇](méi)有方法,意味著它可以表示任意類型的值,可以方便地實(shí)現(xiàn)通用性較強(qiáng)的函數(shù)或數(shù)據(jù)結(jié)構(gòu)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-418845.html
空接口和模板的區(qū)別
- 空接口的類型判斷需要通過(guò)斷言,而模板的數(shù)據(jù)類型是利用編譯器進(jìn)行類型推導(dǎo)來(lái)獲得的
- 模板在使用時(shí)需要在編譯階段進(jìn)行類型檢查,因此可以保證類型安全性
實(shí)現(xiàn)singleflight的Do方法
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.mu.Unlock()
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
-
Do()
: 這個(gè)函數(shù)傳入key和函數(shù)fn,只要key相同,那么fn函數(shù)指揮運(yùn)行一次
- 首先mutex鎖,防止g.m并發(fā)讀寫沖突
- 檢查group的m成員變量是否存在,不存在則初始化
- 檢查key的請(qǐng)求是否正在執(zhí)行,如果正在執(zhí)行,則等待結(jié)果并解鎖,等待結(jié)果后返回結(jié)果
- 否則如果沒(méi)有在執(zhí)行,新建一個(gè)call類型表示正在執(zhí)行
- 發(fā)起請(qǐng)求前加鎖
- 將這個(gè)call加入到group的m(map)中,表示正在執(zhí)行
- 調(diào)用fn
- c.wg.Done() 表示請(qǐng)求結(jié)束
- group的m(map)中從刪除call
把singleflight加入主進(jìn)程group.go
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
loader *singleflight.Group
}
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
g := &Group{
loader: &singleflight.Group{},
}
return g
}
func (g *Group) load(key string) (value ByteView, err error) {
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)
}
}
return g.getLocally(key)
})
if err == nil {
return viewi.(ByteView), nil
}
return
}
- 添加
Group
結(jié)構(gòu)體loader
,更新NewGroup
- 修改
load
函數(shù),使用匿名函數(shù)保證對(duì)于同一個(gè)key只會(huì)執(zhí)行一次
測(cè)試
$ ./run.sh
2020/02/16 22:36:00 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 22:36:00 [Server http://localhost:8001] GET /_geecache/scores/Tom
2020/02/16 22:36:00 [SlowDB] search key Tom
630630630
更新后的group.go
// 負(fù)責(zé)與外部交互,控制緩存存儲(chǔ)和獲取的主流程
package geecache
import (
"fmt"
"geecache/singleflight"
"log"
"sync"
)
type Group struct {
name string
getter Getter
mainCache cache
peers PeerPicker
loader *singleflight.Group
}
type Getter interface {
Get(key string) ([]byte, error)
}
type GetterFunc func(key string) ([]byte, error)
func (f GetterFunc) Get(key string) ([]byte, error) {
return f(key)
}
var (
mu sync.RWMutex
groups = make(map[string]*Group)
)
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
if getter == nil {
panic("nil Getter")
}
mu.Lock()
defer mu.Unlock()
g := &Group{
name: name,
getter: getter,
mainCache: cache{cacheBytes: cacheBytes},
loader: &singleflight.Group{},
}
groups[name] = g
return g
}
func GetGroup(name string) *Group {
mu.RLock()
g := groups[name]
mu.RUnlock()
return g
}
func (g *Group) Get(key string) (ByteView, error) {
if key == "" {
return ByteView{}, fmt.Errorf("key is required")
}
if v, ok := g.mainCache.get(key); ok {
log.Println("[GeeCache] hit")
return v, nil
}
return g.load(key)
}
func (g *Group) getLocally(key string) (ByteView, error) {
bytes, err := g.getter.Get(key)
if err != nil {
return ByteView{}, err
}
value := ByteView{b: cloneBytes(bytes)}
g.populateCache(key, value)
return value, nil
}
func (g *Group) populateCache(key string, value ByteView) {
g.mainCache.add(key, value)
}
func (g *Group) RegisterPeers(peers PeerPicker) {
if g.peers != nil {
panic("RegisterPeerPicker called more than once")
}
g.peers = peers
}
func (g *Group) load(key string) (value ByteView, err error) {
// each key is only fetched once (either locally or remotely)
// regardless of the number of concurrent callers.
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)
}
}
return g.getLocally(key)
})
if err == nil {
return viewi.(ByteView), nil
}
return
}
func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
bytes, err := peer.Get(g.name, key)
if err != nil {
return ByteView{}, err
}
return ByteView{b: bytes}, nil
}
到了這里,關(guān)于【go項(xiàng)目-geecache】動(dòng)手寫分布式緩存 - day6 - 防止緩存擊穿的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!