防止缓存击穿之进程内共享调用

2020-09-19 22:21:54 +08:00
 kevinwan

go-zero 微服务框架中提供了许多开箱即用的工具,好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错,实现代码风格的统一方便他人阅读等等。

本文主要讲述进程内共享调用神器 SharedCalls

使用场景

并发场景下,可能会有多个线程(协程)同时请求同一份资源,如果每个请求都要走一遍资源的请求过程,除了比较低效之外,还会对资源服务造成并发的压力。举一个具体例子,比如缓存失效,多个请求同时到达某服务请求某资源,该资源在缓存中已经失效,此时这些请求会继续访问 DB 做查询,会引起数据库压力瞬间增大。而使用 SharedCalls 可以使得同时多个请求只需要发起一次拿结果的调用,其他请求"坐享其成",这种设计有效减少了资源服务的并发压力,可以有效防止缓存击穿。

高并发场景下,当某个热点 key 缓存失效后,多个请求会同时从数据库加载该资源,并保存到缓存,如果不做防范,可能会导致数据库被直接打死。针对这种场景,go-zero 框架中已经提供了实现,具体可参看 sqlcmongoc 等实现代码。

为了简化演示代码,我们通过多个线程同时去获取一个 id 来模拟缓存的场景。如下:

func main() {
  const round = 5
  var wg sync.WaitGroup
  barrier := syncx.NewSharedCalls()

  wg.Add(round)
  for i := 0; i < round; i++ {
    // 多个线程同时执行
    go func() {
      defer wg.Done()
      // 可以看到,多个线程在同一个 key 上去请求资源,获取资源的实际函数只会被调用一次
      val, err := barrier.Do("once", func() (interface{}, error) {
        // sleep 1 秒,为了让多个线程同时取 once 这个 key 上的数据
        time.Sleep(time.Second)
        // 生成了一个随机的 id
        return stringx.RandId(), nil
      })
      if err != nil {
        fmt.Println(err)
      } else {
        fmt.Println(val)
      }
    }()
  }

  wg.Wait()
}

运行,打印结果为:

837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db

可以看出,只要是同一个 key 上的同时发起的请求,都会共享同一个结果,对获取 DB 数据进缓存等场景特别有用,可以有效防止缓存击穿。

关键源码分析

总结

本文主要介绍了 go-zero 框架中的 SharedCalls 工具,对其应用场景和关键代码做了简单的梳理,希望本篇文章能给大家带来一些收获。

项目地址

https://github.com/tal-tech/go-zero

微信交流群

1879 次点击
所在节点    Go 编程语言
1 条回复
woniuge
2020-09-21 00:31:17 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/708644

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX