Go中singleflight文件提供了可重复的函数调用抑制机制。通过给每次函数调用分配一个key,相同key的函数并发调用时,只会被执行一次,返回相同的结果。其本质是对函数调用的结果进行复用。一般用于缓存击穿,去除重复请求
// 普通调用方法
func callFunc(i int) (int,error) {
time.Sleep(500 * time.Millisecond)
return i, nil
}
// 使用singleflight
// 1. 定义全局变量
var sf singleflight.Group
func callFuncBySF(key string, i int) (int, error) {
// 2. 调用sf.Do方法
value, err, shared := sf.Do(key, func() (interface{}, error) {
return callFunc(i)
})
res, _ := value.(int)
return res, err
}
// singleflight的本质是对某次函数调用的复用,只执行1次,并将执行期间相同的函数返回相同的结果。由此产生一个问题,如果实际执行的函数出了问题,比如超时,则在此期间的所有调用都会超时。由此需要一些额外的方法来控制
// 使用DoChan进行超时控制 超时控制:解决一个阻塞,全部阻塞
func CtrTimeout(ctx context.Context, req interface{}){
ch := g.DoChan(key, func() (interface{}, error) {
return call(ctx, req)
})
select {
case <-time.After(500 * time.Millisecond):
return
case <-ctx.Done()
return
case ret := <-ch:
go handle(ret)
}
}
// 频率控制:解决一个出错,全部出错
// 在一些对可用性要求极高的场景下,往往需要一定的请求饱和度来保证业务的最终成功率。
// 一次请求还是多次请求,对于下游服务而言并没有太大区别,此时使用 singleflight 只是为了降低请求的数量级,那么使用 Forget() 提高下游请求的并发。
// 另外启用协程定时删除key,提高请求下游次数,提高成功率
func CtrRate(ctx context.Context, req interface{}){
res, _, shared := g.Do(key, func() (interface{}, error) {
// 另外其一个goroutine,等待一段时间后,删除key
// 删除key后的调用,会重新执行Do
go func() {
time.Sleep(10 * time.Millisecond)
g.Forget(key)
}()
return call(ctx, req)
})
handle(res)
}