我们在开发时,有时会碰到一个接口的访问量突然上升,导致服务响应延迟或者宕机的情况。这时,除了利用缓存之外,也可以用到singlefilght
来解决,下面是一个简单的示例
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
"golang.org/x/sync/singleflight"
)
func main() {
g := singleflight.Group{}
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
val, err, shared := g.Do("a", a)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("index: %d, val: %d, shared: %v\n", j, val, shared)
}(i)
}
wg.Wait()
}
var (
count = int64(0)
)
// 模拟接口方法
func a() (interface{}, error) {
time.Sleep(time.Millisecond * 500)
return atomic.AddInt64(&count, 1), nil
}
// 部分输出,shared表示是否共享了其他请求的返回结果
index: 2, val: 1, shared: false
index: 71, val: 1, shared: true
index: 69, val: 1, shared: true
index: 73, val: 1, shared: true
index: 8, val: 1, shared: true
index: 24, val: 1, shared: true
val
这里绝大部分都为1
是因为程序运行时间太快了,可以试着把time.Sleep
时间缩短一点看看效果
singleflight
核心代码非常简单
// 成员非常少,就两个
type Group struct {
mu sync.Mutex
m map[string]*call
}
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
// 判断key是否存在,存在则表示有其他请求先一步进来,
// 直接等待其他请求返回就行
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err, true
}
// 不存在就创建一个新的call对象,然后去执行
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
doCall
方法很简单,这里就不展开了,除了Do
方法之外,还有一个异步的DoChan
方法,原理一模一样。
我们一般可以在一些类似于幂等的接口上用singleflight
它源码非常短,有需要的可以去展开阅读,代码思路非常清楚
有疑问加站长微信联系(非本文作者)