设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间23 现在时间23 缓存数据 荣耀也罢,屈辱也罢,都要以平和的心态去面对,少一些无奈与感慨,多一份从容和淡然。晚安!

荣耀也罢,屈辱也罢,都要以平和的心态去面对,少一些无奈与感慨,多一份从容和淡然。晚安!

查看: 2185|回复: 3

SingleFlight模式的Go并发编程学习

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:235
  • 打卡月天数:1
  • 打卡总奖励:3500
  • 最近打卡:2025-12-10 17:17:46
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
448
主题
427
精华
0
金钱
4856
积分
951
注册时间
2023-1-7
最后登录
2025-12-10

发表于 2023-10-16 19:36:00 | 显示全部楼层 |阅读模式
目录


  • 应用场景
  • 应用方式
  • 源码解析
  • 总结
  • 项目地址
最近接触到微服务框架
  1. go-zero
复制代码
,翻看了整个框架代码,发现结构清晰、代码简洁,所以决定阅读源码学习下,本次阅读的源码位于
  1. core/syncx/singleflight.go
复制代码

  1. go-zero
复制代码
  1. SingleFlight
复制代码
的作用是:将并发请求合并成一个请求,以减少对下层服务的压力。

应用场景


  • 查询缓存时,合并请求,提升服务性能。 假设有一个 IP 查询的服务,每次用户请求先在缓存中查询一个 IP 的归属地,如果缓存中有结果则直接返回,不存在则进行 IP 解析操作。
1.jpeg

如上图所示,n 个用户请求查询同一个 IP(8.8.8.8)就会对应 n 个 Redis 的查询,在高并发场景下,如果能将 n 个 Redis 查询合并成一个 Redis 查询,那么性能肯定会提升很多,而
  1. SingleFlight
复制代码
就是用来实现请求合并的,效果如下:
2.jpeg


  • 防止缓存击穿。
缓存击穿问题是指:在高并发的场景中,大量的请求同时查询一个 key ,如果这个 key 正好过期失效了,就会导致大量的请求都打到数据库,导致数据库的连接增多,负载上升。
3.jpeg

通过
  1. SingleFlight
复制代码
可以将对同一个Key的并发请求进行合并,只让其中一个请求到数据库进行查询,其他请求共享同一个结果,可以很大程度提升并发能力。

应用方式

直接上代码:
  1. func main() {
  2.   round := 10
  3.   var wg sync.WaitGroup
  4.   barrier := syncx.NewSingleFlight()
  5.   wg.Add(round)
  6.   for i := 0; i < round; i++ {
  7.     go func() {
  8.       defer wg.Done()
  9.       // 启用10个协程模拟获取缓存操作
  10.       val, err := barrier.Do("get_rand_int", func() (interface{}, error) {
  11.         time.Sleep(time.Second)
  12.         return rand.Int(), nil
  13.       })
  14.       if err != nil {
  15.         fmt.Println(err)
  16.       } else {
  17.         fmt.Println(val)
  18.       }
  19.     }()
  20.   }
  21.   wg.Wait()
  22. }
复制代码
以上代码,模拟 10 个协程请求 Redis 获取一个 key 的内容,代码很简单,就是执行
  1. Do()
复制代码
方法。其中,接收两个参数,第一个参数是获取资源的标识,可以是 redis 中缓存的 key,第二个参数就是一个匿名函数,封装好要做的业务逻辑。最终获得的结果如下:
  1. 5577006791947779410557700679194777941055770067919477794105577006791947779410557700679194777941055770067919477794105577006791947779410557700679194777941055770067919477794105577006791947779410
复制代码
从上看出,10个协程都获得了同一个结果,也就是只有一个协程真正执行了
  1. rand.Int()
复制代码
获取了随机数,其他的协程都共享了这个结果。

源码解析

先看代码结构:
  1. type (
  2.   // 定义接口,有2个方法 Do 和 DoEx,其实逻辑是一样的,DoEx 多了一个标识,主要看Do的逻辑就够了
  3.   SingleFlight interface {
  4.     Do(key string, fn func() (interface{}, error)) (interface{}, error)
  5.     DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
  6.   }
  7.   // 定义 call 的结构
  8.   call struct {
  9.     wg  sync.WaitGroup // 用于实现通过1个 call,其他 call 阻塞
  10.     val interface{}    // 表示 call 操作的返回结果
  11.     err error          // 表示 call 操作发生的错误
  12.   }
  13.   // 总控结构,实现 SingleFlight 接口
  14.   flightGroup struct {
  15.     calls map[string]*call // 不同的 call 对应不同的 key
  16.     lock  sync.Mutex       // 利用锁控制请求
  17.   }
  18. )
复制代码
然后看最核心的
  1. Do方法
复制代码
做了什么事情:
  1. func (g *flightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
  2.   c, done := g.createCall(key)
  3.   if done {
  4.     return c.val, c.err
  5.   }
  6.   g.makeCall(c, key, fn)
  7.   return c.val, c.err
  8. }
复制代码
代码很简洁,利用
  1. g.createCall(key)
复制代码
对 key 发起 call 请求(其实就是做一件事情),如果此时已经有其他协程已经在发起 call 请求就阻塞住(done 为 true 的情况),等待拿到结果后直接返回。如果 done 是 false,说明当前协程是第一个发起 call 的协程,那么就执行
  1. g.makeCall(c, key, fn)
复制代码
真正地发起 call 请求(此后的其他协程就阻塞在了
  1. g.createCall(key)
复制代码
)。
4.jpeg

从上图可知,其实关键就两步:

  • 判断是第一个请求的协程(利用map)
  • 阻塞住其他所有协程(利用 sync.WaitGroup)
来看下
  1. g.createCall(key)
复制代码
如何实现的:
  1. func (g *flightGroup) createCall(key string) (c *call, done bool) {
  2.   g.lock.Lock()
  3.   if c, ok := g.calls[key]; ok {
  4.     g.lock.Unlock()
  5.     c.wg.Wait()
  6.     return c, true
  7.   }
  8.   c = new(call)
  9.   c.wg.Add(1)
  10.   g.calls[key] = c
  11.   g.lock.Unlock()
  12.   return c, false
  13. }
复制代码
先看第一步:判断是第一个请求的协程(利用map)
  1. g.lock.Lock()
  2. if c, ok := g.calls[key]; ok {
  3.   g.lock.Unlock()
  4.   c.wg.Wait()
  5.   return c, true
  6. }
复制代码
此处判断 map 中的 key 是否存在,如果已经存在,说明已经有其他协程在请求了,当前这个协程只需要等待,等待是利用了
  1. sync.WaitGroup
复制代码
  1. Wait()
复制代码
方法实现的,此处还是很巧妙的。要注意的是,map 在 Go 中是非并发安全的,所以需要加锁。
再看第二步:阻塞住其他所有协程(利用 sync.WaitGroup)
  1. c = new(call)
  2. c.wg.Add(1)
  3. g.calls[key] = c
复制代码
因为是第一个发起 call 的协程,所以需要 new 这个 call,然后将
  1. wg.Add(1)
复制代码
,这样就对应了上面的
  1. wg.Wait()
复制代码
,阻塞剩下的协程。随后将 new 的 call 放入 map 中,注意此时只是完成了初始化,并没有真正去执行call请求,真正的处理逻辑在
  1. g.makeCall(c, key, fn)
复制代码
中。
  1. func (g *flightGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
  2.   defer func() {
  3.     g.lock.Lock()
  4.     delete(g.calls, key)
  5.     g.lock.Unlock()
  6.     c.wg.Done()
  7.   }()
  8.   c.val, c.err = fn()
  9. }
复制代码
这个方法中做的事情很简单,就是执行了传递的匿名函数
  1. fn()
复制代码
(也就是真正call请求要做的事情)。最后处理收尾的事情(通过defer),也是分成两步:

  • 删除 map 中的 key,使得下次发起请求可以获取新的值。
  • 调用
    1. wg.Done()
    复制代码
    ,让之前阻塞的协程全部获得结果并返回。
至此,
  1. SingleFlight
复制代码
的核心代码就解析完毕了,虽然代码不长,但是这个思想还是很棒的,可以在实际工作中借鉴。

总结


  • map 非并发安全,记得加锁。
  • 巧用 sync.WaitGroup 去完成需要阻塞控制协程的应用场景。
  • 通过匿名函数 fn 去封装传递具体业务逻辑,在调用 fn 的上层函数中去完成统一的逻辑处理。

项目地址

https://github.com/zeromicro/go-zero
以上就是SingleFlight模式的Go并发编程学习的详细内容,更多关于SingleFlight模式Go并发编程的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
13
积分
6
注册时间
2023-10-30
最后登录
2023-10-30

发表于 2024-3-8 03:26:56 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
11
积分
2
注册时间
2023-5-2
最后登录
2023-5-2

发表于 2024-10-20 20:02:51 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-29
最后登录
2022-12-29

发表于 2025-12-11 20:57:05 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表