Go语言缓存穿透解题思路(singleflight)


本文摘自网络,作者,侵删。

缓存穿透:
  • 指用户请求的资源在缓存(redis等缓存中间件)中未命中,按照正常的逻辑,未命中的数据会去持久层(mysql/pg等)去查询并获取返回。
  • 但是一半持久层能承载的访问量非常有限(如果没有按照索引查找或需要很多字段时)。 当一个缓存未命中,用户体量足够大的情况下,一瞬间会有大量的请求直接打到持久层,导致超时甚至崩溃。
  • 这就需要一个可行的方案来处理类似的突发情况,当然,超时控制也是必要的。如果出现超时需要及时返回,否则超时也会造成整条业务线阻塞崩溃。
  • 超时需要考虑三大层级的超时,大家熟知的Nginx 超时(网关超时)/ 进程内超时 (goroutine超时控制)/ 服务间超时控制 (分布式架构下由于网络抖动等原因,需要做超时处理,不然请求堆积导致内存爆炸)
  • 这里就不讨论 这些超时控制以及如何实现,这里主要谈论 缓存击穿的方案(Go语言方案,其他语言思路也是相似的)
示例代码如下:
package main

import (
    "fmt"
    "sync"

    "golang.org/x/sync/singleflight"
)

var sg singleflight.Group
var wg sync.WaitGroup

func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go Req() // 模拟同时有10个请求该资源的请求未命中缓存
    }
    wg.Wait()
}

// Req 模拟新进入的请求,并且缓存未命中才会执行的操作
func Req() (interface{}, error) {
    defer wg.Done()
    val, err, _ := sg.Do("Once", Jobs)
    fmt.Println("得到返回值:", val)
    return val, err
}

// 实现符合Do()第二参数的函数签名,实际上应该是对持久层的访问,此处可添加超时控制
func Jobs() (res interface{}, err error) {
    fmt.Println("模拟一些操作") // 这次模拟可能会执行一次以上的Jobs(),因为第一个执行的单飞已经返回结果并释放了key,此时还有goroutine 未执行,一执行发现key还处于释放状态。
    // 实际场景中,当结果返回,就会填充缓存,所以基本不会存在新的请求重新打到持久层的情况,次数在可接受范围内。
    return "casso", nil
}

最后,如果硬件条件比较宽裕的情况下,还宽裕做得更为保险。

  • 在kafka集群中使用特定的topic 来专门接收缓存填充的消息,在起专门的消费kafka消息的服务,在该服务内进行缓存填充
  • 这样基本上不会对持久层造成什么压力,但是也要注意可能有多条重复的kafka消息,在填充缓存的服务中需要进行一定的判断,即缓存是否已存在
  • 还是有很多地方可以优化的,想提高可用性,那肯定是需要牺牲一定复杂度的
  • singleflight

本文来自:简书

感谢作者:Casso_W

查看原文:Go语言缓存穿透解题思路(singleflight)

相关阅读 >>

Golang的不足之处是什么?

Golang的memcache如何简单实现

test

pprof最全功能

手撸Golang 学etcd 手写raft协议之12 单元测试

聊聊nacos-coredns-plugin的udpserver

Go语言开发环境搭建

手撸Golang 仿spring ioc/aop 之6 扫码1

Golang和erlang区别

Golang语言可以做些什么

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...