全文转载自 golang防缓存击穿神器【singleflight】
缓存击穿
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。
首先我们使用代码来模拟以下缓存击穿。
var errorNotExist = errors.New("not exist")
var counter int64 = 0 // 计数器模拟缓存雪崩
func getData(key string) (string, error) {
data, err := getDataFromCache(key)
if err == errorNotExist {
data, err = getDataFromDB(key)
if err != nil {
fmt.Println(err)
return "", err
}
// TOOD: set cache
} else if err != nil {
return "", err
}
return data, nil
}
// 模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
atomic.AddInt64(&counter, 1)
if atomic.LoadInt64(&counter) <= 5 {
fmt.Println(fmt.Sprintf("get %s from cache", key))
return "data", nil
} else {
return "", errorNotExist
}
}
func getDataFromDB(key string) (string, error) {
fmt.Println(fmt.Sprintf("get %s from database", key))
return "data", nil
}
func main() {
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go func() {
defer wg.Done()
_, err := getData("key")
if err != nil {
fmt.Println(err)
return
}
//fmt.Println(data)
}()
}
wg.Wait()
}
输出,统计有5次是从cache中取出,剩下是从DB中访问。
image.png
使用 singleflight 改写代码:
import (
"errors"
"fmt"
"golang.org/x/sync/singleflight"
"sync"
"sync/atomic"
)
var errorNotExist = errors.New("not exist")
var counter int64 = 0 // 计数器模拟缓存雪崩
var gsf singleflight.Group
func getData(key string) (string, error) {
data, err := getDataFromCache(key)
if err == errorNotExist {
v, err, _ := gsf.Do(key, func() (interface{}, error) {
return getDataFromDB(key) // TODO set cache
})
if err != nil {
fmt.Println(err)
return "", err
}
data = v.(string)
} else if err != nil {
return "", err
}
return data, nil
}
// 模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
atomic.AddInt64(&counter, 1)
if atomic.LoadInt64(&counter) <= 5 {
fmt.Println(fmt.Sprintf("get %s from cache", key))
return "data", nil
} else {
return "", errorNotExist
}
}
func getDataFromDB(key string) (string, error) {
fmt.Println(fmt.Sprintf("get %s from database", key))
return "data", nil
}
func main() {
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go func() {
defer wg.Done()
_, err := getData("key")
if err != nil {
fmt.Println(err)
return
}
//fmt.Println(data)
}()
}
wg.Wait()
}
输出:
image.png
发现,从DB中获取的次数大大减少。
网友评论