这个库的主要应用场景是缓存系统。例如,当有大量用户同时请求同一资源时,如果没有Singleflight,服务器可能需要执行大量的数据库查询或其他高负荷操作。但有了Singleflight,服务器只需执行一次查询或操作,然后将结果返回给所有请求。这大大减轻了服务器的负载。
var g singleflight.Group
func someExpensiveOperation() (interface{}, error) {
time.Sleep(1 * time.Second)
return "foo", nil
}
func main() {
for i := 0; i < 5; i++ {
go func() {
v, err, _ := g.Do("some_key", someExpensiveOperation)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("got: %v\n", v)
}()
}
time.Sleep(2 * time.Second)
}
在这个例子中,someExpensiveOperation
代表一项消耗很大的操作,比如一个复杂的数据库查询或者一个网络请求。当这个函数被并发调用多次时,singleflight
会保证这个函数只执行一次,然后各个并发的调用者将获得相同的返回结果。
注意"some_key"这个参数,singleflight
使用这个 key 去区分不同的请求,只有在相同的 key 的访问中,才会合并请求。
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
}
以上伪代码中,Group是Singleflight的主要结构,它有一个锁和一个map,用于存储所有的请求(call)。锁保证了对同一资源的访问在同一时间只能由一个进程进行。
Do方法是实现请求合并的核心,它首先会检查当前的请求(key表示的请求)是否正在被处理,如果是则等待处理结束,然后返回结果,如果不是则执行请求(fn),并将结果存储在call结构中,供之后的请求使用。
END