走进Golang singleflight 源码,一探实现机制
2024-02-19 12:14:04
了解 singleflight:一个解决缓存击穿问题的并发编程工具
简介
在高并发系统中,缓存击穿问题时有发生。当多个请求并发访问同一个缓存 key 时,如果缓存中不存在该 key 对应的值,就会导致所有请求同时去查询数据库,给数据库带来巨大压力。
singleflight 简介
singleflight 是一个 Go 语言编写的并发编程工具,专用于解决缓存击穿问题。它利用了一个简单的机制:当多个 goroutine 并发访问同一个缓存 key 时,只有一个 goroutine 会实际执行查询操作,其他 goroutine 则等待结果。
singleflight 源码解析
singleflight 的源码非常简洁,核心代码只有几百行。让我们深入了解一下它的实现机制。
类型定义
type Call struct {
wg sync.WaitGroup
val interface{}
err error
// 用于避免重复调用 Do()
done bool
}
Call 结构体用于存储每个缓存 key 对应的 goroutine 调用信息。wg 字段是一个 sync.WaitGroup,用于等待 goroutine 调用完成。val 和 err 字段分别存储 goroutine 调用返回的值和错误。done 字段用于标记 goroutine 调用是否已完成。
Do() 函数
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
if g.m == nil {
g.m = make(map[string]*Call)
}
c, ok := g.m[key]
if !ok {
c = new(Call)
c.wg.Add(1)
g.m[key] = c
go func() {
c.val, c.err = fn()
c.wg.Done()
}()
} else {
c.wg.Add(1)
}
c.wg.Wait()
return c.val, c.err
}
Do() 函数是 singleflight 的核心函数,用于执行缓存查询。它首先检查缓存中是否存在 key 对应的 Call 结构体,如果没有则创建一个新的 Call 结构体,并启动一个 goroutine 来执行查询操作。如果有,则将当前 goroutine 加入到 Call 结构体的 wg 字段中,等待查询操作完成。
当查询操作完成时,wg 字段会减少 1,并唤醒所有等待的 goroutine。这些 goroutine 会从 Call 结构体中获取查询结果,并返回给调用者。
总结
singleflight 的实现非常简单,但它却是一个非常有效的缓存工具。它可以帮助我们解决缓存击穿问题,提高并发编程中的性能。
常见问题解答
-
singleflight 是线程安全的 吗?
是的,singleflight 是线程安全的。它使用 sync.Mutex 来保护共享数据结构。 -
singleflight 可以在多个 goroutine 池中使用吗 ?
是的,singleflight 可以安全地在多个 goroutine 池中使用。 -
singleflight 是否支持缓存失效 ?
singleflight 本身不支持缓存失效。但我们可以通过手动删除缓存项来实现缓存失效。 -
singleflight 是否支持分布式缓存 ?
singleflight 本身不支持分布式缓存。但我们可以通过使用分布式缓存库来实现分布式缓存。 -
singleflight 的开销大吗 ?
singleflight 的开销很小,可以忽略不计。