返回

走进Golang singleflight 源码,一探实现机制

后端

了解 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 的实现非常简单,但它却是一个非常有效的缓存工具。它可以帮助我们解决缓存击穿问题,提高并发编程中的性能。

常见问题解答

  1. singleflight 是线程安全的 吗?
    是的,singleflight 是线程安全的。它使用 sync.Mutex 来保护共享数据结构。

  2. singleflight 可以在多个 goroutine 池中使用吗
    是的,singleflight 可以安全地在多个 goroutine 池中使用。

  3. singleflight 是否支持缓存失效
    singleflight 本身不支持缓存失效。但我们可以通过手动删除缓存项来实现缓存失效。

  4. singleflight 是否支持分布式缓存
    singleflight 本身不支持分布式缓存。但我们可以通过使用分布式缓存库来实现分布式缓存。

  5. singleflight 的开销大吗
    singleflight 的开销很小,可以忽略不计。