Open kevinyan815 opened 3 years ago
SingleFlight是Go语言sync扩展库提供的另一种并发原语,那么SingleFlight是用于解决什么问题的呢?官方文档里的解释是:
SingleFlight
sync
Package singleflight provides a duplicate function call suppression mechanism. 翻译过来就是:singleflight包提供了一种抑制重复函数调用的机制。
Package singleflight provides a duplicate function call suppression mechanism.
翻译过来就是:singleflight包提供了一种抑制重复函数调用的机制。
具体到Go程序运行的层面来说,SingleFlight的作用是在处理多个goroutine同时调用同一个函数的时候,只让一个goroutine去实际调用这个函数,等到这个goroutine返回结果的时候,再把结果返回给其他几个同时调用了相同函数的goroutine,这样可以减少并发调用的数量。在实际应用中也是,它能够在一个服务中减少对下游的并发重复请求。还有一个比较常见的使用场景是用来防止缓存击穿。
Go
goroutine
Go扩展库里用singleflight.Group结构体类型提供了SingleFlight并发原语的功能。
singleflight.Group
singleflight.Group类型提供了三个方法:
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result func (g *Group) Forget(key string)
fn
使用缓存时,一个常见的用法是查询一个数据先去查询缓存,如果没有就去数据库里查到数据并缓存到Redis里。缓存击穿问题是指,高并发的系统中,大量的请求同时查询一个缓存Key 时,如果这个 Key 正好过期失效,就会导致大量的请求都打到数据库上,这就是缓存击穿。用 SingleFlight 来解决缓存击穿问题再合适不过,这个时候只要这些对同一个 Key 的并发请求的其中一个到数据库中查询就可以了,这些并发的请求可以共享同一个结果。用 SingleFlight能够限制对同一个缓存 Key 的多次重复请求,减少对下游的瞬时流量。 下面是一个模拟用SingleFlight并发原语合并查询Redis缓存的程序,你可以自己动手测试一下,开10个goroutine去查询一个固定的Key,观察一下返回结果就会发现最终只执行了一次Redis查询。
Redis
// 模拟一个Redis客户端 type client struct { // ... 其他的配置省略 requestGroup singleflight.Group } // 普通查询 func (c *client) Get(key string) (interface{}, error) { fmt.Println("Querying Database") time.Sleep(time.Second) v := "Content of key" + key return v, nil } // SingleFlight查询 func (c *client) SingleFlightGet(key string) (interface{}, error) { v, err, _ := c.requestGroup.Do(key, func() (interface{}, error) { return c.Get(key) }) if err != nil { return nil, err } return v, err }
完整可运行的示例代码,访问:https://github.com/kevinyan815/gocookbook/tree/master/codes/singleflight 。
SingleFlight
是Go语言sync
扩展库提供的另一种并发原语,那么SingleFlight
是用于解决什么问题的呢?官方文档里的解释是:具体到
Go
程序运行的层面来说,SingleFlight
的作用是在处理多个goroutine
同时调用同一个函数的时候,只让一个goroutine
去实际调用这个函数,等到这个goroutine
返回结果的时候,再把结果返回给其他几个同时调用了相同函数的goroutine
,这样可以减少并发调用的数量。在实际应用中也是,它能够在一个服务中减少对下游的并发重复请求。还有一个比较常见的使用场景是用来防止缓存击穿。Go
扩展库里用singleflight.Group
结构体类型提供了SingleFlight
并发原语的功能。singleflight.Group
类型提供了三个方法:fn
函数。同一个 key,在同一时间只有第一次调用Do方法时才会去执行fn
函数,其他并发的请求会等待调用的执行结果。fn
函数执行完,产生了结果以后,就能从这个 chan 中接收这个结果。fn
函数,而不是等待前一个未完成的fn
函数的结果。使用缓存时,一个常见的用法是查询一个数据先去查询缓存,如果没有就去数据库里查到数据并缓存到
Redis
里。缓存击穿问题是指,高并发的系统中,大量的请求同时查询一个缓存Key 时,如果这个 Key 正好过期失效,就会导致大量的请求都打到数据库上,这就是缓存击穿。用SingleFlight
来解决缓存击穿问题再合适不过,这个时候只要这些对同一个 Key 的并发请求的其中一个到数据库中查询就可以了,这些并发的请求可以共享同一个结果。用SingleFlight
能够限制对同一个缓存 Key 的多次重复请求,减少对下游的瞬时流量。 下面是一个模拟用SingleFlight
并发原语合并查询Redis
缓存的程序,你可以自己动手测试一下,开10个goroutine
去查询一个固定的Key,观察一下返回结果就会发现最终只执行了一次Redis
查询。完整可运行的示例代码,访问:https://github.com/kevinyan815/gocookbook/tree/master/codes/singleflight 。