侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

在go语言中使用singleflight包实现防缓存击穿功能示例

Go  /  管理员 发布于 1年前   281

在Go语言中,golang.org/x/sync/singleflight 包提供了一种机制,确保对于任何特定 key 的

并发请求在同一时刻只执行一次。这个机制有效地防止了缓存击穿问题。


本文将深入探讨 Go 语言中 singleflight 包的使用。

从缓存击穿问题的基础知识开始,进而详细介绍 singleflight 包的使用,

展示如何利用它来避免缓存击穿。


缓存击穿

缓存击穿 是指在高并发的情况下,某个热点的 key 突然过期,

导致大量的请求直接访问数据库,造成数据库的压力过大,甚至宕机的现象。


常见的解决方案:

设置热点数据永不过期:
对于一些确定的热点数据,可以将其设置为 永不过期,
这样就可以确保不会因为缓存失效而导致请求直接访问数据库。

设置互斥锁:
为了防止缓存失效时所有请求同时查询数据库,
可以采用锁机制确保仅有一个请求查询数据库并更新缓存,
而其他请求则在缓存更新后再进行访问。

提前更新:
后台监控缓存的使用情况,当缓存即将过期时,异步更新缓存,延长过期时间。



singleflight包

singleflight 包提供了一种“重复函数调用抑制机制”。

就是 singleflight 将多个请求合并成一个请求,多个请求共享同一个结果。


换句话说,当多个 goroutine 同时尝试调用同一个函数(基于某个给定的 key)时,

singleflight 会确保该函数只会被第一个到达的 goroutine 调用,

其他 goroutine 会等待这次调用的结果,然后共享这个结果,而不是同时发起多个调用。


组成部分

Group:
这是 singleflight 包的核心结构体。
它管理着所有的请求,确保同一时刻,对同一资源的请求只会被执行一次。
Group 对象不需要显式创建,直接声明后即可使用。

Do 方法:
Group 结构体提供了 Do 方法,这是实现合并请求的主要方法,
该方法接收两个参数:
一个是字符串 key(用于标识请求资源),另一个是函数 fn,用来执行实际的任务。
在调用 Do 方法时,如果已经有一个相同 key 的请求正在执行,
那么 Do 方法会等待这个请求完成并共享结果,否则执行 fn 函数,然后返回结果。
Do 方法有三个返回值,前两个返回值是 fn 函数的返回值,类型分别为 interface{} 和 error,
最后一个返回值是一个 bool 类型,表示 Do 方法的返回结果是否被多个调用共享。

DoChan:
该方法与 Do 方法类似,但它返回的是一个通道,通道在操作完成时接收到结果。
返回值是通道,意味着我们能以非阻塞的方式等待结果。

Forget:
该方法用于从 Group 中删除一个 key 以及相关的请求记录,
确保下次用同一 key 调用 Do 时,将立即执行新请求,而不是复用之前的结果。

Result:
这是 DoChan 方法返回结果时所使用的结构体类型,用于封装请求的结果。
这个结构体包含三个字段,具体如下:

Val(interface{} 类型):
请求返回的结果。

Err(error 类型):
请求过程中发生的错误信息。

Shared(bool 类型):
表示这个结果是否被当前请求以外的其他请求共享。



singleflight 安装

通过以下命令,在 go 应用中安装 singleflight 依赖:

go get golang.org/x/sync/singleflight

使用示例

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/singleflight/usage/main.go
package main
import (
    "errors"
    "fmt"
    "golang.org/x/sync/singleflight"
    "sync"
)
var errRedisKeyNotFound = errors.New("redis: key not found")
func fetchDataFromCache() (any, error) {
    fmt.Println("fetch data from cache")
    return nil, errRedisKeyNotFound
}
func fetchDataFromDataBase() (any, error) {
    fmt.Println("fetch data from database")
    return "zongscan.com", nil
}
func fetchData() (any, error) {
    cache, err := fetchDataFromCache()
    if err != nil && errors.Is(err, errRedisKeyNotFound) {
        fmt.Println(errRedisKeyNotFound.Error())
        return fetchDataFromDataBase()
    }
    return cache, err
}
func main() {
    var (
        sg singleflight.Group
        wg sync.WaitGroup
    )
    for range 5 {
        wg.Add(1)
        go func() {
            defer wg.Done()
            v, err, shared := sg.Do("key", fetchData)
            if err != nil {
                panic(err)
            }
            fmt.Printf("v: %v, shared: %v\n", v, shared)
        }()
    }
    wg.Wait()
}


这段代码模拟了一个典型的并发访问场景:

从缓存获取数据,若缓存未命中,则从数据库检索,在此过程中,singleflight 库起到了至关重要的作用。

它确保在多个并发请求尝试同时获取相同数据时,

实际的获取操作(不论是访问缓存还是查询数据库)只会执行一次。

这样不仅减轻了数据库的压力,还有效防止了高并发环境下可能发生的缓存击穿问题。


代码运行结果如下所示:

fetch data from cache
redis: key not found         
fetch data from database     
v: zongscan.com, shared: true
v: zongscan.com, shared: true
v: zongscan.com, shared: true
v: zongscan.com, shared: true
v: zongscan.com, shared: true

根据运行结果可知,当 5 个 goroutine 并发获取相同数据时,

数据获取操作实际上只由一个goroutine执行了一次。

此外,由于所有返回的 shared 值均为 true,这表明返回的结果被其他 4 个goroutine共享。



最佳实践


key 的设计

在生成 key 的时候,我们应该保证它的唯一性与一致性。

唯一性:
确保传递给 Do 方法的 key 具有唯一性,以便 Group 区分不同请求。
推荐使用结构化的命名方式来保证 key 的唯一性,例如,
可以遵循类似 {类型}):{标识} 的规范来构建 key。
以获取用户信息为例,相应的 key 可以是 user:1234,
其中 user 标识数据类型,而 1234 则是具体的用户标识。

一致性:
对于相同的请求,无论何时调用,生成的 key 应该保持一致,以便 Group 正确地合并相同的请求,
防止非预期的错误。


超时控制

在调用 Group.Do 方法时,第一个到达的 goroutine 可以成功执行 fn 函数,

而其他随后到达的 goroutine 将进入阻塞状态。

如果阻塞状态持续过长,可能需要采取降级策略以保证系统的响应性,

这时候,我们可以利用 Group.DoChan 方法和结合 select 语句实现超时控制。


以下是一个实现超时控制的简单示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/singleflight/timeout_control/main.go
package main
import (
    "fmt"
    "golang.org/x/sync/singleflight"
    "time"
)
func main() {
    var sg singleflight.Group
    doChan := sg.DoChan("key", func() (interface{}, error) {
        time.Sleep(4 * time.Second)
        return "程序员陈明勇", nil
    })
    select {
    case <-doChan:
        fmt.Println("done")
    case <-time.After(2 * time.Second):
        fmt.Println("timeout")
        // 采用其他降级策略
    }
}

总结

本文介绍了缓存击穿的含义及其常见的解决方案。

然后深入探讨了 singleflight 包,从基础概念、组成部分到具体的安装和使用示例。

接着通过模拟一个典型的并发访问场景来演示如何利用 singleflight 

来防止在高并发场景下可能发生的缓存击穿问题。

最后,探讨在实践中设计 key 和控制请求超时的最佳策略,以便更好地理解和应用 singleflight,

从而优化并发处理逻辑。


  • 上一条:
    在go语言种使用泛型实现一个有序map示例
    下一条:
    在go语言种实现微秒生成服务示例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客