go singleflight库对外提供的方法:
- //Do方法,传入key,以及回调函数,如果key相同,fn方法只会执行一次,同步等待
- //返回值v:表示fn执行结果
- //返回值err:表示fn的返回的err
- //第三个返回值shared:表示是否是真实fn返回的还是从保存的map[key]返回的,也就是共享的
- func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
- //DoChan方法类似Do方法,只是返回的是一个chan
- func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
- //暂时未用到:设计Forget 控制key关联的值是否失效,默认以上两个方法只要fn方法执行完成后,内部维护的fn的值也删除(即并发结束后就失效了)
- func (g *Group) Forget(key string) {
背景:singleflight方法可以很好的解决缓存击穿的问题,可以避免大量的流量都会打到DB上进而引起DB压力。但是依然有可能阻塞大量请求导致系统等待获取缓存数据的goroutine激增,因此需要灵活使用 DoCall() 方法和 Forget() 方法。
效果:使用singleflight能有效解决这个问题,限制对同一个键值对的多次重复请求,减少对下游如DB的瞬时流量。
这个库的主要作用就是: 将一组相同的请求合并成一个请求,实际上最终只会有 第一个 请求去访问DB,在其获取到结果后,然后对剩余其他请求返回相同的结果。
原理:请求1、2、3同时请求相同的key,singleflight机制只会让请求1访问DB,请求1返回的value不仅返回给客户端1,也作为请求2、请求3的结果返回给客户端。这里的多请求理解为多个goroutine并发执行。底层实际上是通过 go map(区分是否是相同key的请求) + lock(线程安全) + waitgroup(阻塞其他请求) 将请求1从DB获取的结果直接通过本地内存去返回给其他相同的请求,其中lock是为了并发安全
总结:golang/sync/singleflight.Group.Do和golang/sync/singleflight.Group.DoChan分别提供了同步和异步的调用方式,这让我们使用起来也更加灵活。当我们需要减少对下游的相同请求时,可以使用 golang/sync/singleflight.Group 来增加吞吐量和服务质量,不过在使用的过程中我们也需要注意以下的几个问题:
golang/sync/singleflight.Group.Do和golang/sync/singleflight.Group.DoChan一个用于同步阻塞调用传入的函数,一个用于异步调用传入的参数并通过 Channel 接收函数的返回值;golang/sync/singleflight.Group.Forget可以通知golang/sync/singleflight.Group在持有的映射表中删除某个键,接下来对该键的调用就不会等待前面的函数返回了;- 一旦调用的函数返回了错误,所有在等待的 Goroutine 也都会接收到同样的错误;
参考:
continue ~