Go singlefight 源码详解|图解

写在前面

通俗的来说就是 singleflight 将相同的并发请求合并成一个请求,进而减少对下层服务的压力,通常用于解决缓存击穿的问题。

在这里插入图片描述

详解

基础结构

golang.org/x/sync/singleflight singleflight结构体:

type call struct {wg sync.WaitGroup// 这些字段在 WaitGroup 结束前写入一次// 只有在 WaitGroup 结束后才会被读取。val interface{}err error// 这些字段在 WaitGroup 结束前使用 singleflight 互斥锁进行读写// 在 WaitGroup 结束后读取但不写入。dups  intchans []chan<- Result
}

Group 代表分成多个工作组,形成一个命名空间,在这个命名空间中,各工作单元可以重复执行。

type Group struct {mu sync.Mutex       // 互斥锁m  map[string]*call // 懒加载
}

Result 保存 Do 方法的结果,以便在通道上传递。做异步处理。

type Result struct {Val    interface{}Err    errorShared bool
}

简单demo

func TestSingleFightExample(t *testing.T) {var group singleflight.Group// 模拟一个并发请求for i := 0; i < 5; i++ {go func(i int) {key := "example"tmp := i // 将tmp放进去val, err, _ := group.Do(key, func() (interface{}, error) {// 模拟一次耗时操作time.Sleep(time.Second)return fmt.Sprintf("result_%d", tmp), nil})if err != nil {fmt.Println("Error:", err)}fmt.Println("Value:", val)}(i)}// 等待所有请求完成time.Sleep(3 * time.Second)
}

结果:这是一个很随机的过程,0~4都有可能,主要看哪个协程最先进来。

Value: result_2
Value: result_2
Value: result_2
Value: result_2
Value: result_2

Do 执行函数:对同一个 key 多次调用的时候,在第一次调用没有执行完的时候, 只会执行一次 fn,其他的调用会阻塞住等待这次调用返回, shared 表示fn的结果是否被共享

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)

DoChan 和 Do 类似,只是 DoChan 返回一个 channel,也就是同步与异步的区别

func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result

Forget:用于通知 Group 删除某个 key 这样后面继续这个 key 的调用的时候就不会在阻塞等待了

func (g *Group) Forget(key string){g.mu.Lock()if c, ok := g.m[key]; ok {c.forgotten = true}delete(g.m, key)g.mu.Unlock()
}

singleflight的本质是对某次函数调用的复用,只执行1次,并将执行期间相同的函数返回相同的结果。由此产生一个问题,如果实际执行的函数出了问题,比如超时,则在此期间的所有调用都会超时,由此需要一些额外的方法来控制。

在一些对可用性要求极高的场景下,往往需要一定的请求饱和度来保证业务的最终成功率。一次请求还是多次请求,对于下游服务而言并没有太大区别,此时使用 singleflight 只是为了降低请求的数量级,那么使用 Forget() 提高下游请求的并发。

常见面试题

singleflight 是什么?什么时候用的?

缓存失效,合并请求的时候用的,这样我们就可以减少对DB的请求压力。
在这里插入图片描述

如果这个goruntine超时怎么办?

singleflight 内部使用 waitGroup 来让同一个 key 的除了第一个请求的后续所有请求都阻塞。直到第一个请求执行 func 返回后,其他请求才会返回。
这意味着,如果 func 执行需要很长时间,那么后面的所有请求都会被一直阻塞。
这时候我们可以使用 DoChan 结合ctx + select做超时控制

func TestSingleFightTimeout(t *testing.T) {ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)go doFly(ctx)time.Sleep(2 * time.Second)cancel() // 2秒后超时
}func doFly(ctx context.Context) {var g singleflight.Groupkey := "example"// 使用 DoChan 结合 select 做超时控制result := g.DoChan(key, func() (interface{}, error) {time.Sleep(5 * time.Second) // 模拟超时return "result", nil})select {case r := <-result:fmt.Println("r", r.Val)case <-ctx.Done():fmt.Println("done")return}
}

结果输出:

done

上述代码中,我们将主进程先sleep 2秒,然后再进行cancel,那么此时我们将会让DoChan这个方法 time.Sleep 5秒模拟超时。那么我们会发现函数过了2秒之后就会输出done

doChan方法具体是怎么实现的?

在DoChan方法中,有一个 go g.doCall(c, key, fn) 的操作,当一个 goroutine 来执行,并通过channel 来返回数据,这样外部可以自定义超时逻辑,防止因为 fn 的阻塞,导致大量请求都被阻塞。

func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {ch := make(chan Result, 1)g.mu.Lock()if g.m == nil {g.m = make(map[string]*call)}if c, ok := g.m[key]; ok { // 如果没有这个keyc.dups++c.chans = append(c.chans, ch)g.mu.Unlock()return ch}c := &call{chans: []chan<- Result{ch}} // 构造异步返回结构体,可以接参数进行超时c.wg.Add(1)g.m[key] = cg.mu.Unlock()go g.doCall(c, key, fn) // 异步执行return ch
}

如果请求失败了怎么办?

如果第一个请求失败了,那么后续所有等待的请求都会返回同一个 error。但实际上可以根据下游能支撑的 rps 定时 forget 这个 key,让更多的请求能有机会走到后续逻辑。

go func() {time.Sleep(100 * time.Millisecond)g.Forget(key)}()

比如1秒内有100个请求过来,正常是第一个请求能执行queryDB,后续99个都会阻塞。增加这个 Forget 之后,每 100ms 就能有一个请求执行 queryDB,相当于是多了几次尝试的机会,相对的也给DB造成了更大的压力,需要根据具体场景进去取舍。 因为有可能前几次是因为DB的抖动导致的查询失败,重试之后就能实现了。

参考链接

[1] https://pkg.go.dev/golang.org/x/sync/singleflight
[2] https://www.lixueduan.com/posts/go/singleflight
[3] https://juejin.cn/post/7093859835694809125

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/26000.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高效能光伏监控解决方案 —— ARMxy工业计算机深度解析

在浩瀚的戈壁滩上&#xff0c;一座现代化的光伏电站正沐浴在烈日之下&#xff0c;将无尽的阳光转化为清洁的电能。这背后&#xff0c;离不开一项关键技术的支撑——ARMxy工业计算机&#xff0c;它如同一位智慧的指挥官&#xff0c;精确掌控着这座绿色能源基地的心跳。 面对广袤…

王学岗鸿蒙开发(北向)——————(十三)音乐播放器

AudioRenderer适合录音 AVPlayer:简单的本地单曲播放 MP3文件放置的地方 import media from ohos.multimedia.media import common from ohos.app.ability.common; Entry Component struct Index {//第1步&#xff1a;avPlayer:media.AVPlayer nullasync onPageShow(){//第…

kafka集成spark

1.新建Scala项目 具体教程可见在idea中创建Scala项目教程-CSDN博客 1.1右键项目名-添加框架支持-勾选scala 1.2main目录下新建scala目录-右键Scala目录-将目录标记为-勾选源代码根目录 1.3创建包com.ljr.spark 1.4引入依赖&#xff08;pox.xml) <dependencies><…

[FreeRTOS 基础知识] 保存现场与恢复现场

文章目录 什么是现场&#xff1f;保存现场的数据存放在哪里&#xff1f;保护现场的场景 什么是现场&#xff1f; 在[FreeRTOS 基础知识] 栈 与 汇编语言文章中解析了fun_c汇编函数&#xff0c;假设在执行fun_c函数的过程中产生高优先级的中断。如下图所示。 此时刚从RAM的SP栈…

秋招突击——6/10——复习{(树形DP)树的最长路径、}——新作{电话号码的字母组合}

文章目录 引言复习树形DP——树的最长路径思路分析参考思路求图的最长的直径的通用方法证明 树形DP分析方法问题 参考代码使用一维数组模拟邻接表存储树形结构或者稀疏图 新作电话号码的组合思路分析参考实现 总结 引言 中间面试了两天&#xff0c;去上海呆了一天&#xff0c;…

Linux 安装ab测试工具

yum -y install httpd-tools ab -help #10个并发连接&#xff0c;100个请求 ab -n 200 -c 100 http://www.baidu.com/

基于51单片机的车辆动态称重系统设计

一 动态称重 所谓动态称重是指通过分析和测量车胎运动中的力,来计算该运动车辆的总重量、轴重、轮重和部分重量数据的过程。动态称重系统按经过车辆行驶的速度划分,可分为低速动态称重系统与高速动态称重系统。因为我国高速公路的限速最高是120,所以高速动态称重系统在理论…

【Rd-03E】使用CH340给Rd03_E雷达模块烧录固件

Rd03_E 指导手册 安信可新品雷达模组Rd-03搭配STM32制作简易人体感应雷达灯教程 http://t.csdnimg.cn/mqhkE 测距指导手册网址&#xff1a; https://docs.ai-thinker.com/_media/rd-03e%E7%B2%BE%E5%87%86%E6%B5%8B%E8%B7%9D%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C%E4%B8%AD%…

万能表单与AI的完美融合,打造个性化AI小程序

在人工智能技术日益成熟的今天&#xff0c;如何将AI智能与用户界面无缝结合&#xff0c;已成为软件开发领域的新挑战。MyCms 以其创新的“万能表单结合AI”功能&#xff0c;为开发者提供了一个全新的解决方案&#xff0c;让个性化AI小程序的开发变得前所未有的简单和高效。 一、…

【解读】小提琴图

ref&#xff1a;解读文献中的箱线图&#xff08;Box-plot&#xff09;和小提琴图&#xff08;Violin-plot)&#xff09;_小提琴图和箱线图的区别-CSDN博客小提琴图展示了每个变量的数据分布情况&#xff0c;通过图中的“小提琴”形状可以看出数据的密度和分布情况。 在图中&…

2024-6-10-Model-Agnostic Meta-Learning (MAML)

摘自&#xff1a;Meta-Transfer Learning for Zero-Shot Super-Resolution 近年来&#xff0c;提出了各种元学习算法。它们可以分为三类&#xff1a; 基于度量的方法&#xff1a;这些方法通过学习度量空间&#xff0c;使得在少量样本内进行高效的学习。例如[35, 38, 39]。基于…

ElasticSearch聚合方式

聚合方式 ES支持灵活的聚合方式,它不仅支持聚合和查询相结合,而且还可以使聚合的过滤条件不影响搜索条件,并且还支持在聚合后的结果中进行过滤筛选。 1.1 直接聚合 直接聚合指的是聚合时的DSL中国没有query子句,是直接对索引内的所有文档进行聚合。 比如下面的DSL: 1.2 先…

入门级的卷积神经网络训练识别手写数字-小白轻松上手-含数据集+pyqt界面

代码下载地址&#xff1a; https://download.csdn.net/download/qq_34904125/89374845 本代码是基于python pytorch环境安装的。 下载本代码后&#xff0c;有个requirement.txt文本&#xff0c;里面介绍了如何安装环境&#xff0c;环境需要自行配置。 或可直接参考下面博文…

苹果WWDC大会AI亮点:大揭晓

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Java网络通信实现

UDP UDPServer import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket;public class UDPServer {public static void main(String[] args) throws IOException {System.out.println("UdpServer启动");// 创建upd套接字Data…

两台电脑通过网线直连共享数据(超详细)- 我的实践记录

原文链接 按照原文的操作&#xff0c;成功通过直连网线连接了两台windows电脑并共享传输数据。 ping不通可能是防火墙没关闭导致的&#xff0c;但是完全关闭防火墙又不安全。 那么有没有不关闭防火墙&#xff0c;能够上网&#xff0c;又能直连另一台电脑呢&#xff1f; 我们…

拓扑排序-java

主要通过宽度优先搜索&#xff08;BFS&#xff09;来实现有向无环图的拓扑序列&#xff0c;邻接表存储图。数组模拟单链表、队列&#xff0c;实现BFS基本操作。 文章目录 前言 一、有向图的拓扑序列 二、算法思路 1.拓扑序列 2.算法思路 三、使用步骤 1.代码如下&#xff08;示…

QT 使用资源文件的注意点

不要存放没有使用的资源文件 即使在代码中没有使用到的资源文件&#xff0c;也会编译到执行文件或者DLL里面去这样会增大它的体积。如下 在代码没有使用这个资源文件(10.4M的2k图片)&#xff0c;但是编译出来的程序有 12M左右的大小 1 假设我们有一个比较复杂的项目&#…

ReentrantLock底层原理

ReentrantLock public ReentrantLock() {sync new NonfairSync(); }public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync(); }ReentrantLock 的默认实现是非公平锁&#xff0c;实际上 ReentrantLock 中的方法&#xff0c;几乎都让 sync 实现…

springboot高校运动会信息管理系统设计与实现-计算机毕业设计源码92968

摘 要 本论文介绍了一个高校运动会信息管理系统的设计和实现过程。首先是高校运动会的需求分析和可行性分析&#xff0c;通过比较运动会的各个工作流程&#xff0c;确定了系统的数据流程和数据库结构&#xff0c;然后介绍了高校运动会信息管理系统开发所使用的软件开发工具&…