使用场景
在单体服务中的某一时间段的Cache Miss,造成大量的请求穿透Cache,打到DB上的场景。
使用单飞可以让一个请求去进行SELECT DB AND SET CACHE 操作,完成操作后让其它请求使用相同的结果进行返回
简单使用
安装库
go get golang.org/x/sync/singleflight
demo
package main
import (
"fmt"
"golang.org/x/sync/singleflight"
"sync"
"sync/atomic"
"time"
)
var count int64
func main() {
var group singleflight.Group
g := sync.WaitGroup{}
i := 0
for i < 1000 {
g.Add(1)
i++
go func() {
r, _, _ := group.Do("abc", func() (interface{}, error) {
time.Sleep(2 * time.Second)
return getResult(), nil
})
fmt.Println(r) //ok
g.Done()
}()
}
g.Wait()
fmt.Println(count) //1
}
func getResult() string {
atomic.AddInt64(&count, 1)
return "ok"
}
实现原理
这里仿照Do方法写的,少了一些error的处理
主要是用 sync.WaitGroup(add,wait,done)方法来实现
package main
import (
"sync"
)
type rui struct {
mu sync.Mutex
m map[string]*call
}
type ra struct {
wg sync.WaitGroup
val interface{}
err error
}
func (receiver *rui) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
receiver.mu.Lock()
if receiver.m == nil {
receiver.m = make(map[string]*call)
}
if c, ok := receiver.m[key]; ok {
receiver.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
receiver.m[key] = c
receiver.mu.Unlock()
c.val, c.err = fn()
defer c.wg.Done()
return c.val, c.err
}
执行下自己写的单飞
package main
import (
"fmt"
"sync"
"time"
)
func main() {
group := rui{}
g := sync.WaitGroup{}
i := 0
for i < 100 {
g.Add(1)
i++
go func() {
r, _ := group.Do("k", func() (interface{}, error) {
time.Sleep(2 * time.Second)
return "md", nil
})
fmt.Println(r)
g.Done()
}()
}
g.Wait()
}