缓存击穿:热key过期,同一时间大量请求进入到数据库
例子1:
package main import ( "fmt" "sync" "time" "golang.org/x/sync/singleflight" ) func main() { g := singleflight.Group{} wg := sync.WaitGroup{} for i := 0; i < 20; i++ { wg.Add(1) go func(j int) { defer wg.Done() // The return value shared indicates whether v was given to multiple callers, shared 表示返回 val 是否被多个调用者共享了https://segmentfault.com/q/1010000022916754?_ea=49700336 val, err, shared := g.Do("userId", queryMysql) if err != nil { fmt.Println(err) return } fmt.Printf("index: %d, val: %d, shared: %t\n", j, val, shared) }(i) } wg.Wait() } // 模拟数据库查询方法 func queryMysql() (interface{}, error) { time.Sleep(time.Millisecond * 500) fmt.Println("mysql query") return 1, nil }
例子2:
package main import ( "errors" "log" "sync" "time" "golang.org/x/sync/singleflight" ) var errorNotExist = errors.New("not exist") var g singleflight.Group func main() { var wg sync.WaitGroup cpu_amount := 8 wg.Add(cpu_amount) for i := 0; i < cpu_amount; i++ { go func() { defer wg.Done() // sleep的作用是确保8个协程同时启动,需要多核cpu time.Sleep(1 * time.Second) data, err := getData("key") if err != nil { log.Print(err) return } log.Println(data) }() } wg.Wait() } // 获取数据 func getData(key string) (string, error) { // 获取缓存 data, err := getDataFromCache(key) if err == errorNotExist { v, err, _ := g.Do(key, func() (interface{}, error) { // 从数据库获取数据,只有第一个请求会进入数据库,其他请求会复用返回值) return getDataFromDB(key) }) if err != nil { log.Println(err) return "", err } data = v.(string) } else if err != nil { return "", err } return data, nil } func getDataFromCache(key string) (string, error) { return "", errorNotExist } func getDataFromDB(key string) (string, error) { log.Printf("get %s from database", key) return "data", nil }
来自anson博客