Singleflight
使用场景
我们一般都会在对外提供服务的时候加一层缓存,来减少数据库的压力,但是会在缓存服务出现抖动或者其他情况的时候会导致出现大量的 cache miss,给数据库带来较大的压力
graph LR
R1[request 1] --> golang --cache miss n--> redis
R2[request 2] --> golang --mysql get n--> mysql
Rn[request n] --> golang
Singleflight 就是将一组相同的请求合并成一个请求,对组内所有的请求返回相同的结果,这样的话实际去请求redis或者mysql就只有一个请求了
graph LR
R1[request 1] --> golang --cache miss 1--> redis
R2[request 2] --> golang --mysql get 1--> mysql
Rn[request n] --> golang
如何用
type Group
// Do 执行函数, 对同一个 key 多次调用的时候,在第一次调用没有执行完的时候
// 只会执行一次 fn 其他的调用会阻塞住等待这次调用返回
// v, err 是传入的 fn 的返回值
// shared 表示是否真正执行了 fn 返回的结果,还是返回的共享的结果
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)
// DoChan 和 Do 类似,只是 DoChan 返回一个 channel,也就是同步与异步的区别
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result
// Forget 用于通知 Group 删除某个 key 这样后面继续这个 key 的调用的时候就不会在阻塞等待了
func (g *Group) Forget(key string)
示例
package main
import (
"errors"
"log"
"sync"
"golang.org/x/sync/singleflight"
)
var errorNotExist = errors.New("not exist")
func main() {
var wg sync.WaitGroup
wg.Add(10)
//模拟10个并发
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
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 {
//模拟从db中获取数据
data, err = getDataFromDB(key)
if err != nil {
log.Println(err)
return "", err
}
//TOOD: set cache
} else if err != nil {
return "", err
}
return data, nil
}
//模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
return "", errorNotExist
}
//模拟从数据库中获取值
func getDataFromDB(key string) (string, error) {
log.Printf("get %s from database", key)
return "data", nil
}
加上singleflight,修改getData方法
//获取数据
func getData(sf *singleflight.Group,key string) (string, error) {
data, err := getDataFromCache(key)
if err == errorNotExist {
//模拟从db中获取数据
v, err, _ := sf.Do(key, func() (interface{}, error) {
return getDataFromDB(key)
//set cache
})
if err != nil {
log.Println(err)
return "", err
}
//TOOD: set cache
data = v.(string)
} else if err != nil {
return "", err
}
return data, nil
}
问题
-
一个阻塞,全部阻塞
context + DoChan + select//获取数据 func getData(ctx context.Context,sf *singleflight.Group,key string) (string, error) { data, err := getDataFromCache(key) if err == errorNotExist { //模拟从db中获取数据 resultChan := sf.DoChan(key, func() (interface{}, error) { return getDataFromDB(key) //set cache }) select { case v:= <- resultChan: //TOOD: set cache data = v.Val.(string) return data,nil case <-ctx.Done(): return "",ctx.Err() } } else if err != nil { return "", err } return data, nil } -
一个出错,全部出错
singleflight.Forget手动释放某个 key 下次调用就不会阻塞等待了