Go的三种扩展原语之 — SingleFlight

2023年 7月 26日 48.5k 0

概述

singleflight.Group 是 Go 语言扩展包的另一种同步原语,它能够再一个服务中抑制对下游的多次重复请求。一个比较常见的使用场景是,我们使用 Redis 对数据库中的数据进行缓存,发生缓存击穿时,大量请求会打到数据库上进而影响服务的尾延时。

singleflight.Group 能够有效地解决这个问题,它能够限制对同一个键值对的多次重复请求,减少对下游的瞬时流量。

在资源的获取非常昂贵时(例如访问缓存、数据库),就很适合使用 singleflight.Group 优化服务。它的使用方法如下:

type service struct {
    requestGroup singleflight.Group
}

func (s *service) handleRequest(ctx context.Context, request Request) (Response, error) {
    v, err, _ := requestGroup.Do(request.Hash(), func() (interface{}, error) {
        rows, err := // select * from tables
        if err != nil {
            return nil, err
        }
        return rows, nil
    })
    if err != nil {
        return nil, err
    }
    return Response{
        rows: rows,
    }, nil
}

因为请求的哈希在业务上一般表示相同的请求,所以上述代码使用它作为请求的键。当然,我们也可以选择其他的字段作为 singleflight.Group.Do 方法的第一个参数减少重复的请求。

结构体

singleflight.Group 结构体由一个互斥锁 sync.Mutex 和一个映射表组成,每一个 singleflight.call 结构体都保存了当前调用对应的信息:

type Group struct {
	mu sync.Mutex
	m  map[string]*call
}

type call struct {
	wg sync.WaitGroup

	val interface{}
	err error

	dups  int
	chans []chan 0
}

因为 val 和 err 两个字段都只会在 singleflight.Group.doCall 方法中赋值,所以当 singleflight.Group.doCall 和 sync.WaitGroup.Wait 返回时,函数调用的结果和错误都会返回给 singleflight.Group.Do 的调用方。

func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
	c.val, c.err = fn()
	c.wg.Done()

	g.mu.Lock()
	delete(g.m, key)
	for _, ch := range c.chans {
		ch  0}
	}
	g.mu.Unlock()
}
  • 行传入的函数 fn,该函数的返回值会赋值给 c.val 和 c.err
  • 调用 sync.WaitGroup.Done 方法通知所有等待结果的 Goroutine — 当前函数已经执行完成,可以从 call 结构体中取出返回值并返回了
  • 获取持有的互斥锁并通过管道将信息同步给使用 singleflight.Group.DoChan 方法的 Goroutine
func (g *Group) DoChan(key string, fn func() (interface{}, error))

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论