缓存击穿是指在缓存失效的时刻,大量的请求过来,回被同时打到下游,给下游造成瞬时压力,解决的办法就是只允许少量的请求打到下游,然后回填到缓存,其他请求还是从缓存拿数据。
解决办法一,使用分布式锁,确保只有一个请求获取到这个锁,其他请求去轮训缓存。特点是,为了保护下游,伤害了自己和redis,因为大量的请求停留在这里造成内存和CPU升高,但是大家都知道,接口服务器的性能不是问题,所以这个方案可行。
此处主要介绍方案二,那就是单飞模式 SingleFlight
,在 golang 中有对应的实现 golang/groupcache ,它跟方案一的区别是讲分布式的锁改成了内存锁,没有了轮训redis这个 *** 作;其次就是打到下游的请求要多一点,但是不构成压力。
go get -u github.com/golang/groupcache
先来模拟缓存击穿的情况
package main
import (
"fmt"
"sync"
"time"
"github.com/golang/groupcache/singleflight"
)
var cache sync.Map
var wg sync.WaitGroup
var cachekey = "key"
var cacheval = "test"
var g singleflight.Group
func main() {
for i := 0; i < 20; i++ {
go action()
}
wg.Wait()
}
func action() {
wg.Add(1)
c, ok := cache.Load(cachekey)
if !ok {
SetCache(cachekey, cacheval)
c = cacheval
}
fmt.Println(c)
wg.Done()
}
func SetCache(key string, val string) bool {
defer fmt.Println("SetCache...")
time.Sleep(10 * time.Millisecond)
cache.Store(key, val)
return true
}
go run main.go
从打印信息可以看出SetCache
被调用了多次。
下面使用单飞模式
func action2() {
wg.Add(1)
c, ok := cache.Load(cachekey)
if !ok {
fn := func() (interface{}, error) {
SetCache(cachekey, cacheval)
return cacheval, nil
}
cc, _ := g.Do(cachekey, fn)
c = cc.(string)
}
fmt.Println(c)
wg.Done()
}
从打印信息可以看出SetCache
被调用了一次。
源码部分
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import "sync"
// call is an in-flight or completed Do call
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
// Group represents a class of work and forms a namespace in which
// units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
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
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)