singleflight在并发场景下保护下游业务

singleflight 使用场景

针对同一业务的同一批请求(需自定义缓存的 key),只放一个请求去执行,其他等待结果(和普通缓存还不一样), 可以在不使用缓存的情况下,保护下游业务;

这是一条测试修改

例如 1:在有缓存的数据读取场景中,缓存过期失效时且大并发场景中,瞬间会有大量请求压到数据库,当设置上缓存后才会恢复.但如果去数据库当中查询数据\内存中计算组装\设置缓存等操作耗时稍长,同样会存在很大的风险,瞬间的巨量数据库访问,可能会使数据库异常。

例如 1:同上,在无缓存的场景中, 如果一个业务完成处理需要 1s, 100 并发情况下, 这 1s 内都会被到服务器执行,会给服务器造成巨大的压力, 用 singleflight 只会有一个请求被真正处理, 其它的会等 1s(第一个请求处理完成),直接取第一个请求的处理结果 .
golang singleflight 用武之地,杨锡坤 2017-09-17如果每个请求都落到下游服务,通常会导致下游服务瞬时负载升高。如果使用缓存,如何判断当前接口请求的内容需要缓存下来?缓存的过期、更新问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 实现原理
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// sample
func TestDoDupSuppress(t *testing.T) {
var g singleflight.Group
var calls int32
fn := func() (interface{}, error) {
fmt.Printf("inprocess %d\n", calls)
atomic.AddInt32(&calls, 1)
// 模拟耗时
time.Sleep(time.Second * 1)
// 回写返回结果
return "ok", nil
}

const n = 30
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)

go func(j int) { // n个协程同时调用了g.Do,fn中的逻辑只会被一个协程执行
fmt.Printf("before request %d\n", j)
v, err := g.Do("key", fn)

fmt.Printf("after request %d, %#v\n", j, v)
if err != nil {
fmt.Printf("Do error: %v\n", err)
}

wg.Done()
}(i)
}
wg.Wait()
fmt.Printf("done calls= %d\n", calls)

}