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.rhkb.cn/news/348456.html

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

相关文章

Tabby:一款革新的Mac/Win现代化终端模拟器

在信息技术日新月异的今天&#xff0c;终端操作已成为众多开发者、系统管理员和技术爱好者的日常必备工具。然而&#xff0c;传统的终端模拟器往往功能单一、界面陈旧&#xff0c;无法满足用户对于高效、便捷操作体验的追求。Tabby应运而生&#xff0c;作为一款现代化、功能强大…

基于多传感器数据和周期性采样的滚动轴承故障诊断方法(Python)

代码较为简单&#xff0c;算法结构如下&#xff1a; from scipy.io import loadmat import numpy as np import os from sklearn import preprocessing # 0-1编码 import torch from torch.utils import data as da# 用训练集标准差标准化训练集以及测试集 def scalar_stand(d…

【Android面试八股文】1. 你说一说Handler机制吧 2. 你知道Handler的同步屏障吗? 3. Looper一直在循环,会造成阻塞吗?为什么?

文章目录 一. 你说一说Handler机制吧二、你知道Handler的同步屏障吗&#xff1f;2.1 Handler消息的分类2.2 什么是同步屏障2.3 为什么要设计同步屏障2.4 同步屏障的用法 三、Looper一直在循环&#xff0c;会造成阻塞吗&#xff1f;为什么&#xff1f;扩展阅读 一. 你说一说Hand…

现货黄金交易多少克一手?国内外情况大不同

如果大家想参与国际市场上的现货黄金交易&#xff0c;就应该从它交易细则的入手&#xff0c;先彻底认识这个品种&#xff0c;因为它是来自欧美市场的投资方式&#xff0c;所以无论是从合约的计的单位&#xff0c;计价的货币&#xff0c;交易的具体时间&#xff0c;以及买卖过程…

AI大模型在健康睡眠监测中的深度融合与实践案例

文章目录 1. 应用方案2. 技术实现2.1 数据采集与预处理2.2 构建与训练模型2.3 个性化建议生成 3. 优化策略4. 应用示例&#xff1a;多模态数据融合与实时监测4.1 数据采集4.2 实时监测与反馈 5. 深入分析模型选择和优化5.1 LSTM模型的优势和优化策略5.2 CNN模型的优势和优化策略…

【安装笔记-20240612-Linux-内网穿透服务之cpolar极点云】

安装笔记-系列文章目录 安装笔记-20240612-Linux-内网穿透服务之 cpolar 极点云 文章目录 安装笔记-系列文章目录安装笔记-20240612-Linux-内网穿透服务之 cpolar 极点云 前言一、软件介绍名称&#xff1a;cpolar极点云主页官方介绍 二、安装步骤测试版本&#xff1a;openwrt-…

0601 模拟集成电路中的直流偏置技术

模拟集成电路中的直流偏置技术 6.1.1 BJT 电流源6.1.2 FET电流源6.1.3电流源做作有源负载![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c5381fff64bd48a1b28ba9bee179b18f.png) 6.1.1 BJT 电流源 6.1.2 FET电流源 6.1.3电流源做作有源负载

tmega128单片机控制的智能小车设计

第1章 绪论1.1 选题背景和意义 自第一台工业机器人诞生以来,机器人的民展已经遍及机械、电子、冶金、交通、宇航、国防等领域。近年来机器人的智能水平不断提高,并且迅速地改变着人们的生活方式。人们在不断探讨、改造、认识自然的过程中,制造能替代人工作的机器一…

2.线性神经网络

目录 1.线性回归一个简化模型线性模型&#xff1a;可以看做是单层神经网络衡量预估质量训练数据参数学习显示解总结 2.基础优化方法小批量随机梯度下降总结 3.Softmax回归&#xff1a;其实是一个分类问题回归VS分类从回归到多类分类---均方损失Softmax和交叉熵损失 4.损失函数L…

阿里云香港服务器怎么样?

大家都知道阿里云是国内最受欢迎的云服务商&#xff0c;那么阿里云香港服务器究竟怎么样呢&#xff1f;和硅云的香港服务器用于做外贸网站等业务相比各有哪些优缺点呢&#xff1f; 阿里云和硅云在香港云服务领域有着广泛的应用和良好的口碑。然而&#xff0c;它们各自的特点和…

借助大语言模型快速学习金仓数据库 KES

基础概念 KES 人大金仓数据库管理系统 KingbaseES&#xff08;KES&#xff09; 是由 北京人大金仓信息技术股份有限公司 (以下简称“人大金仓”)自主研发的面向全行业、全客户关键应用的企业级大型通用数据库管理系统。产品融合了人大金仓在数据库领域几十年的产品研发和企业级…

SQL中distinct去重关键字的使用和count统计组合的使用

文章目录 SQL中distinct的使用1、distinct作用于单列2、distinct作用于多列3、 count()、distinct组合使用conut扩展知识 SQL中distinct的使用 1、distinct作用于单列 语法&#xff1a; select distinct 列名 from 表&#xff1b; distinct必须在列的前面&#xff0c;否则直…

Unity HoloLens2 MRTK 空间锚点 基础教程

Unity HoloLens2 MRTK 空间锚点 基础教程 Unity HoloLens2 空间锚点MRTK 空间锚点 准备Unity 工程创建设置切换 UWP 平台UWP 平台设置 下载并安装混合现实功能工具导入混合现实工具包和 OpenXR 包 Unity 编辑器 UWP 设置Unity 2019.4.40 设置Unity 2022.3.0 设置Unity 2022.3.0…

嵌入式仪器模块:音频综测仪和自动化测试软件

• 24 位分辨率 • 192 KHz 采样率 • 支持多种模拟/数字音频信号的输入/输出 应用场景 • 音频信号分析&#xff1a;幅值、频率、占空比、THD、THDN 等指标 • 模拟音频测试&#xff1a;耳机、麦克风、扬声器测试&#xff0c;串扰测试 • 数字音频测试&#xff1a;平板电…

天锐绿盾 | 无感知加密软件、透明加密系统、数据防泄漏软件

摘要&#xff1a;文件加密软件,包含禁止非授权的文件泄密和抄袭复制解决方案即使被复制泄密都是自动加密无法阅读,透明加密,反复制软件,内网监控,文件加密,网络安全方案,透明文件加密,加密文件,图纸加密,知识产权保护,加密数据; 通过绿盾信息安全管理软件&#xff0c;系统在不改…

项目五串行通信系统 任务5-3温度信息上传

任务描述&#xff1a;DS18B20测量温度&#xff0c;单片机采集温度数据转换显示代码&#xff0c;并通过串行口发送到上位机显示。 底层文件&#xff1a; /********************************************* ds18b20底层函数:能完成一次温度数据读取 ***************************…

docker 拉取不到镜像的问题:拉取超时

error pulling image configuration: download failed after attempts6: dial tcp 31.13.94.10:443: i/o timeout 首先设置国内的镜像源&#xff1a;复制下面直接执行 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF{"registry-mirrors"…

NSS题目练习9

[极客大挑战 2020]welcome 界面打开后一片空白&#xff0c;查看题目描述&#xff0c;翻译过来是 1.除了GET请求方法&#xff0c;还有一种常见的请求方法… 2.学习一些关于sha1和array的知识。 3.更仔细地检查phpinfo&#xff0c;你会发现标志在哪里。 补充&#xff1a; sh…

MicroPython 环境下使用 ESP32 连接百度 AI 大模型

前言 在物联网领域&#xff0c;ESP32 由于其丰富的功能和低功耗性能成为了一种流行的选择。结合 MicroPython&#xff0c;它为开发者提供了一个高效的开发环量&#xff0c;让 Python 程序员也能轻松介入到嵌入式系统和 IoT 应用的开发之中。本文将介绍如何利用这些技术&#x…

基于redis的分布式锁

一、redis分布式锁基本信息 1.详细讲解&#xff1a; Redis 分布式锁是一种用于控制分布式系统中多个进程对共享资源的并发访问的机制。通过 Redis 的原子操作和过期时间功能&#xff0c;可以实现一个简单而有效的分布式锁。接下来&#xff0c;我们将详细介绍其工作原理、基本…