singleflight

60 阅读2分钟

pkg.go.dev/golang.org/…

cs.opensource.google/go/x/sync/+…

zhuanlan.zhihu.com/p/392975826

singleflightd的设计思路就是将一组相同的请求合并成一个请求,使用map存储,只会有一个请求到达mysql,使用sync.waitgroup包进行同步,对所有的请求返回相同的结果。

type Group struct {
 mu sync.Mutex       // 互斥锁,保证并发安全
 m  map[string]*call // 存储相同的请求,key是相同的请求,value保存调用信息。
}
type call struct {
 wg sync.WaitGroup
 // 存储返回值,在wg done之前只会写入一次
 val interface{}
  // 存储返回的错误信息
 err error

 // 标识别是否调用了Forgot方法
 forgotten bool

 // 统计相同请求的次数,在wg done之前写入
 dups  int
  // 使用DoChan方法使用,用channel进行通知
 chans []chan<- Result
}
// Dochan方法时使用
type Result struct {
 Val    interface{} // 存储返回值
 Err    error // 存储返回的错误信息
 Shared bool // 标示结果是否是共享结果
}
// 入参:key:标识相同请求,fn:要执行的函数
// 返回值:v: 返回结果 err: 执行的函数错误信息 shard: 是否是共享结果
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
 // 代码块加锁
 g.mu.Lock()
 // map进行懒加载
 if g.m == nil {
   // map初始化
  g.m = make(map[string]*call)
 }
 // 判断是否有相同请求
 if c, ok := g.m[key]; ok {
   // 相同请求次数+1
  c.dups++
  // 解锁就好了,只需要等待执行结果了,不会有写入操作了
  g.mu.Unlock()
  // 已有请求在执行,只需要等待就好了
  c.wg.Wait()
  // 区分panic错误和runtime错误
  if e, ok := c.err.(*panicError); ok {
   panic(e)
  } else if c.err == errGoexit {
   runtime.Goexit()
  }
  return c.val, c.err, true
 }
 // 之前没有这个请求,则需要new一个指针类型
 c := new(call)
 // sync.waitgroup的用法,只有一个请求运行,其他请求等待,所以只需要add(1)
 c.wg.Add(1)
 // m赋值
 g.m[key] = c
 // 没有写入操作了,解锁即可
 g.mu.Unlock()
 // 唯一的请求该去执行函数了
 g.doCall(c, key, fn)
 return c.val, c.err, c.dups > 0
}