美文网首页
go 使用 singleflight 库防止缓存击穿

go 使用 singleflight 库防止缓存击穿

作者: wayyyy | 来源:发表于2023-01-29 09:22 被阅读0次

全文转载自 golang防缓存击穿神器【singleflight】

缓存击穿

一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB ,造成瞬时DB请求量大、压力骤增。

首先我们使用代码来模拟以下缓存击穿。

var errorNotExist = errors.New("not exist")
var counter int64 = 0   // 计数器模拟缓存雪崩

func getData(key string) (string, error) {
    data, err := getDataFromCache(key)
    if err == errorNotExist {
        data, err = getDataFromDB(key)
        if err != nil {
            fmt.Println(err)
            return "", err
        }
        // TOOD: set cache
    } else if err != nil {
        return "", err
    }

    return data, nil
}

// 模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
    atomic.AddInt64(&counter, 1)
    if atomic.LoadInt64(&counter) <= 5 {
        fmt.Println(fmt.Sprintf("get %s from cache", key))
        return "data", nil
    } else {
        return "", errorNotExist
    }
}

func getDataFromDB(key string) (string, error) {
    fmt.Println(fmt.Sprintf("get %s from database", key))
    return "data", nil
}

func main() {
    var wg sync.WaitGroup
    wg.Add(20)
    for i := 0; i < 20; i++ {
        go func() {
            defer wg.Done()
            _, err := getData("key")
            if err != nil {
                fmt.Println(err)
                return
            }
            //fmt.Println(data)
        }()
    }
    wg.Wait()
}

输出,统计有5次是从cache中取出,剩下是从DB中访问。


image.png

使用 singleflight 改写代码:

import (
    "errors"
    "fmt"
    "golang.org/x/sync/singleflight"
    "sync"
    "sync/atomic"
)

var errorNotExist = errors.New("not exist")
var counter int64 = 0   // 计数器模拟缓存雪崩
var gsf singleflight.Group

func getData(key string) (string, error) {
    data, err := getDataFromCache(key)
    if err == errorNotExist {
        v, err, _ := gsf.Do(key, func() (interface{}, error) {
            return getDataFromDB(key)   // TODO set cache
        })
        if err != nil {
            fmt.Println(err)
            return "", err
        }
        data = v.(string)
    } else if err != nil {
        return "", err
    }
    return data, nil
}

// 模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
    atomic.AddInt64(&counter, 1)
    if atomic.LoadInt64(&counter) <= 5 {
        fmt.Println(fmt.Sprintf("get %s from cache", key))
        return "data", nil
    } else {
        return "", errorNotExist
    }
}

func getDataFromDB(key string) (string, error) {
    fmt.Println(fmt.Sprintf("get %s from database", key))
    return "data", nil
}

func main() {
    var wg sync.WaitGroup
    wg.Add(20)
    for i := 0; i < 20; i++ {
        go func() {
            defer wg.Done()
            _, err := getData("key")
            if err != nil {
                fmt.Println(err)
                return
            }
            //fmt.Println(data)
        }()
    }
    wg.Wait()
}

输出:


image.png

发现,从DB中获取的次数大大减少。

源码分析

相关文章

网友评论

      本文标题:go 使用 singleflight 库防止缓存击穿

      本文链接:https://www.haomeiwen.com/subject/qgayhdtx.html