背景
高并发的场景下,经常会出现并发重复请求资源的情况。
比如说,缓存失效时,我们去请求db获取最新的数据,如果这个key是一个热key,那么在缓存失效的瞬间,可能会有大量的并发请求访问到db,导致db访问量陡增,甚至是打崩db,这种场景也就是我们常说的缓存击穿。
针对同一个key的并发请求,这些请求和响应实际上都是一样的。所以我们可以把这种并发请求优化为:只进行一次实际请求去访问资源,然后得到实际响应,所有的并发请求共享这个实际响应的结果
针对分布式场景,我们可以使用分布式锁来实现
针对单机场景,我们可以使用singleflight来实现
singleflight
singleflight是golang内置的一个包,这个包提供了对重复函数调用的抑制功能,也就是保证并发请求只会有一个实际请求去访问资源,所有并发请求共享实际响应。
使用
singleflight在golang sdk源码中的路径为:src/internal/singleflight
但是internal是golang sdk内部的包,所以我们不能直接去使用
使用步骤:
引入go mod
go get golang.org/x/sync
使用singleflight包
singleflight包主要提供了三个方法
// 方法作用:保证并发请求只会执行一次函数,并共享实际响应
// 请求参数
// key:请求的唯一标识,相同的key会被视为并发请求
// fn:实际需要执行的函数
// 响应参数
// v:实际执行函数的返回值
// err:实际执行函数的错误
// shared:返回值v是否被共享,若存在并发请求,则为true;若不存在并发请求则为false
func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool)
// 方法作用:和Do类似,不过方法返回的是chan
func (g *Group) DoChan(key string, fn func() (any, error)) (= 0 {
stack = stack[line+1:]
}
return &panicError{value: v, stack: stack}
}
// call is an in-flight or completed singleflight.Do call
type call struct {
// 保证相同key,只会进行一次实际请求
// 相同key的并发请求会共享返回
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
// 实际执行函数的返回值和错误
val interface{}
err error
// forgotten indicates whether Forget was called with this call's key
// while the call was still in flight.
// 是否已删除当前并发请求的key
forgotten bool
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
// 并发请求的次数
dups int
chans []chan 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group) DoChan(key string, fn func() (interface{}, error))