gocookbook icon indicating copy to clipboard operation
gocookbook copied to clipboard

Go并发编程--SingleFlight

Open kevinyan815 opened this issue 4 years ago • 0 comments

SingleFlight是Go语言sync扩展库提供的另一种并发原语,那么SingleFlight是用于解决什么问题的呢?官方文档里的解释是:

Package singleflight provides a duplicate function call suppression mechanism.

翻译过来就是:singleflight包提供了一种抑制重复函数调用的机制。

具体到Go程序运行的层面来说,SingleFlight的作用是在处理多个goroutine同时调用同一个函数的时候,只让一个goroutine去实际调用这个函数,等到这个goroutine返回结果的时候,再把结果返回给其他几个同时调用了相同函数的goroutine,这样可以减少并发调用的数量。在实际应用中也是,它能够在一个服务中减少对下游的并发重复请求。还有一个比较常见的使用场景是用来防止缓存击穿。

Go扩展库里用singleflight.Group结构体类型提供了SingleFlight并发原语的功能。

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)
  • Do方法,接受一个字符串Key和一个待调用的函数,会返回调用函数的结果和错误。使用Do方法的时候,它会根据提供的Key判断是否去真正调用fn函数。同一个 key,在同一时间只有第一次调用Do方法时才会去执行fn函数,其他并发的请求会等待调用的执行结果。
  • DoChan方法:类似Do方法,只不过是一个异步调用。它会返回一个通道,等fn函数执行完,产生了结果以后,就能从这个 chan 中接收这个结果。
  • Forget方法:在SingleFlight中删除一个Key。这样一来,之后这个Key的Do方法调用会执行fn函数,而不是等待前一个未完成的fn 函数的结果。

使用缓存时,一个常见的用法是查询一个数据先去查询缓存,如果没有就去数据库里查到数据并缓存到Redis里。缓存击穿问题是指,高并发的系统中,大量的请求同时查询一个缓存Key 时,如果这个 Key 正好过期失效,就会导致大量的请求都打到数据库上,这就是缓存击穿。用 SingleFlight 来解决缓存击穿问题再合适不过,这个时候只要这些对同一个 Key 的并发请求的其中一个到数据库中查询就可以了,这些并发的请求可以共享同一个结果。用 SingleFlight能够限制对同一个缓存 Key 的多次重复请求,减少对下游的瞬时流量。 下面是一个模拟用SingleFlight并发原语合并查询Redis缓存的程序,你可以自己动手测试一下,开10个goroutine去查询一个固定的Key,观察一下返回结果就会发现最终只执行了一次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 。

kevinyan815 avatar Dec 12 '20 05:12 kevinyan815