推荐一个Go包:singleflight使用以及原理

缓解缓存击穿的大杀器之---singleflight深入浅出 singlefight直译“单飞”,那顾名思义就是有一堆鸟,但是咱只让一只鸟单飞。。。😄提供了重复函数调用抑制机制,使用它可以避免同时进行相同的函数调用。第一个调用未完成时后续的重复调用会等待,当第一个调用完成时则会与它们分享结果,这样以来虽然只执行了一次函数调用但是所有调用都拿到了最终的调用结果。 阅读详情

         

singleflight 包主要是用来做并发控制,整个包的核心代码不到100行,充分利用到了map和WaitGroup的特性。

常见的场景比如防止 缓存击穿 ,我们可以来模拟一下这种场景:

缓存击穿:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

package main

import (
	"errors"
	"log"
	"sync"

	"golang.org/x/sync/singleflight"
)

var errorNotExist = errors.New("not exist")
func main() {
	var wg sync.WaitGroup
	wg.Add(10)

	//模拟10个并发
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			data, err := getData("key")
			if err != nil {
				log.Print(err)
				return
			}
			log.Println(data)
		}()
	}
	wg.Wait()
}

//获取数据
func getData(key string) (string, error) {
	data, err := getDataFromCache(key)
	if err == errorNotExist {
		//模拟从db中获取数据
		data, err = getDataFromDB(key)
		if err != nil {
			log.Println(err)
			return "", err
		}

		//TOOD: set cache
	} else if err != nil {
		return "", err
	}
	return data, nil
}

//模拟从cache中获取值,cache中无该值
func getDataFromCache(key string) (string, error) {
	return "", errorNotExist
}

//模拟从数据库中获取值
func getDataFromDB(key string) (string, error) {
	log.Printf("get %s from database", key)
	return "data", nil
}

其中通过 getData(key)方法获取数据,逻辑是:

  1. 先尝试从cache中获取

  2. 如果cache中不存在就从db中获取

我们模拟了10个并发请求,来同时调用 getData 函数,执行结果如下:

2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data
2020/03/08 17:13:11 get key from database
2020/03/08 17:13:11 data

可以看得到10个请求都是走的db,因为cache中不存在该值,当我们利用上 singlefligth 包, getData 改动一下:

import "golang.org/x/sync/singleflight"
var g singleflight.Group
//获取数据
func getData(key string) (string, error) {
	data, err := getDataFromCache(key)
	if err == errorNotExist {
		//模拟从db中获取数据
		v, err, _ := g.Do(key, func() (interface{}, error) {
			return getDataFromDB(key)
			//set cache
		})
		if err != nil {
			log.Println(err)
			return "", err
		}

		//TOOD: set cache
		data = v.(string)
	} else if err != nil {
		return "", err
	}
	return data, nil
}

执行结果如下,可以看得到只有一个请求进入的db,其他的请求也正常返回了值,从而保护了后端DB。

2020/03/08 17:18:16 get key from database
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data
2020/03/08 17:18:16 data

原理解析

singleflight 在 golang.org/x/sync/singleflight 项目下,对外提供了以下几个方法

//Do方法,传入key,以及回调函数,如果key相同,fn方法只会执行一次,同步等待
//返回值v:表示fn执行结果
//返回值err:表示fn的返回的err
//第三个返回值shared:表示是否是真实fn返回的还是从保存的map[key]返回的,也就是共享的
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
//DoChan方法类似Do方法,只是返回的是一个chan
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
//暂时未用到:设计Forget 控制key关联的值是否失效,默认以上两个方法只要fn方法执行完成后,内部维护的fn的值也删除(即并发结束后就失效了)
func (g *Group) Forget(key string) {

代码也很简单,我们打开上看下,截取了部分:singleflight/singleflight.go

package singleflight // import "golang.org/x/sync/singleflight"

import "sync"

// call is an in-flight or completed singleflight.Do call
type call struct {
	wg sync.WaitGroup

	// These fields are written once before the WaitGroup is done
	// and are only read after the WaitGroup is done.
    //val和err用来记录fn发放执行的返回值
	val interface{}
	err error

	// forgotten indicates whether Forget was called with this call's key
	// while the call was still in flight.
    // 用来标识fn方法执行完成之后结果是否立马删除还是保留在singleflight中
	forgotten bool

	// These fields are read and written with the singleflight
	// mutex held before the WaitGroup is done, and are read but
	// not written after the WaitGroup is done.
    //dups 用来记录fn方法执行的次数
	dups  int
    //用来记录DoChan中调用次数以及需要返回的数据
	chans []chan<- Result
}

// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
	mu sync.Mutex       // protects m
	m  map[string]*call // lazily initialized
}


// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
    g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
    //check map是否已经存在值
	if c, ok := g.m[key]; ok {
		c.dups++
		g.mu.Unlock()
		c.wg.Wait()
		return c.val, c.err, true
	}
	c := new(call)
	c.wg.Add(1)
	g.m[key] = c
	g.mu.Unlock()

	g.doCall(c, key, fn)
	return c.val, c.err, c.dups > 0
}
//执行fn方法,并且wg.Done
// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
	c.val, c.err = fn()
	c.wg.Done()

	...
}

在Do方法中主要是通过waitgroup来控制的,主要流程如下:

  1. 在Group中设置了一个map,如果key不存在,则实例化call(用来保存值信息),并将key=>call的对应关系存入map中(通过mutex保证了并发安全)

  2. 如果已经在调用中则key已经存在map,则wg.Wait

  3. 在fn执行结束之后(在doCall方法中执行)执行wg.Done

  4. 卡在第2步的方法得到执行,返回结果

其他的DoChan方法也是类似的逻辑,只是返回的是一个chan。

文中用到的实例代码放在github上:https://github.com/go-demo/singleflight-demo

singleflight在缓存击穿中的应用(缓存穿透、缓存雪崩、缓存击穿的介绍,以及go版源码赏析) singleflight在缓存击穿中的应用(缓存穿透、缓存雪崩、缓存击穿的介绍,golang版源码赏析) 阅读详情

相关推荐

基于single flight来解决缓存击穿

基于single flight的方式解决缓存击穿

zhanghailang1的博客 1153

Go 并发控制:singleflight 详解

singleflight 是 Go 官方扩展库 x 中提供的扩展并发原语,能够将多个并发请求合并为一个,降低服务端压力。本文就来介绍下它的用法和实现原理。请求合并singleflight 主要用于抑制重复的并发调用,从而避免对同一资源进行重复操作,提升系统性能。比如,当我们有多个 goroutine 并发调用一个同一个函数时,singleflight 能够实现只让一个 goroutine 发起调用...

Go中国 1184

高性能 Go 缓存库 Ristretto:从算法原理到生产级架构实践

如果用一句话概括 Ristretto 的价值,那就是:它不是一个“更快的 map”,而是一个把命中率、吞吐、内存和工程可控性平衡得非常好的本地缓存系统。真正把它用好的关键,不在于会不会Set/Get正确估算MaxCost用真实对象大小实现Cost给缓存层补上 singleflight、空值缓存和 TTL jitter在架构上将其放进 L1/L2/L3 的完整链路中用指标和压测验证缓存是否真的在保护下游对于高并发 Go 服务,Ristretto 非常适合作为进程内热点缓存的基础设施层。

一起修行,不孤单。这里有编程语言、开发工具、学习方法论和踩坑笔记。用简单的话说复杂的事,陪你从小白到进阶。周更干货,欢迎一起成长! 472

singleflight 防缓存击穿 使用及原理

文章目录 以下文章来源于学点程序 ,作者silenceper singleflight 包主要是用来做并发控制,整个包的核心代码不到100行,充分利用到了map和WaitGroup的特性。 常见的场景比如防止 缓存击穿 ,我们可以来模拟一下这种场景: 缓存击穿:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到...

找不到我的路 5164

深入解析LCD DRM驱动框架中的KMS与GEM机制

本文深入解析了Linux内核中DRM(Direct Rendering Manager)驱动框架的核心机制,重点剖析了KMS(内核模式设置)与GEM(图形执行管理器)的工作原理。KMS作为显示系统的总指挥,负责原子化地控制CRTC、Encoder、Plane等硬件资源,确保画面同步更新,避免撕裂;GEM则作为显存大管家,通过DUMB和PRIME机制高效管理图形缓冲区的分配、共享与同步。文章结合RK3568平台实战,阐述了如何利用这些机制优化显示性能,并提供了调试技巧与常见问题解决方案。

weixin_29188997的博客 387

Kaggle时间序列竞赛金牌方案解析与实战指南

本文深度解析了Kaggle时间序列竞赛的金牌方案,揭示了其成功不仅依赖先进模型,更在于系统工程思维。文章系统阐述了从数据理解、特征工程到模型集成与后处理的全流程,并重点剖析了分层建模、自定义损失函数、时序交叉验证等核心技巧,为参赛者提供了从理论到实战的完整指南。

weixin_29196315的博客 355

7days-golang项目全解析:7天用Go从零实现4大项目,一次看懂Web框架、分布式缓存、ORM与RPC

**7days-golang** 是一个帮助新手通过动手写代码来深入理解 Go 语言的开源仓库:每个项目只用 7 天时间,从第一行代码开始,从零实现 Web 框架、分布式缓存、ORM 框架与 RPC 框架四大经典基础设施。每天约 100 行代码,每天的功能都可以独立运行和测试,就像搭积木一样层层叠加,是学习 Go 并发编程与分布式系统设计的极佳入门路径 🧩。 ## 为什么"7天从零实现"值得学

gitblog_00654的博客 961

RTKLIB模糊度固定实战:三种模式对比与部分模糊度固定代码解析

本文深入解析RTKLIB中模糊度固定的三种核心模式(continuous、instantaneous、fix and hold),对比其性能与适用场景,并详细拆解了部分模糊度固定技术的源码实现。通过实战案例与代码级解析,为GNSS高精度定位开发者提供了在不同数据特性下提升固定成功率的优化思路与配置建议。

efc1234567的博客 473

OpenHaystack 完整部署指南:从克隆代码到第一个 Find My 蓝牙追踪器

OpenHaystack 可以把任何蓝牙低功耗(BLE)设备变成一枚由 Apple Find My 网络追踪的"自制 AirTag":把固件烧进 BBC micro:bit、ESP32 或树莓派,将它系在钥匙、背包或车上,途经的任何 iPhone 都会自动拾取它的广播并把位置报告上传到 Apple 服务器,你用一台 Mac 即可查看。本文覆盖从获取代码、安装应用,到烧录固件、在地图上看到第一个定位

gitblog_00685的博客 321

innovus中path group 的策略和应用(上)

在所有的后端工具里边,有三个重要的引擎:auto-place,CTS,auto-route三个。这里边的auto-place是决断了整个设计时序的基点。。当然,就算是到了CTS/route阶段后,也可以继续利用auto-place数据优化的功能,在数据库上做数据路径增量优化(optDesign -increment)。可以说,数据路径的优化手段,实际上是贯穿在整个后端流程上的。。看看这其中的奥妙和有趣的事情。ICer GO!

i_chip_backend的博客 4216

馈电网络设计:通过Smith圆图工具完成微带线到同轴探针的阻抗匹配

Smith圆图通过图形化阻抗变换路径,显著简化微带线到同轴探针的匹配设计。设计需结合理论计算(微带线阻抗公式、探针等效模型)、圆图操作(串联/并联元件移动规则)及仿真验证,最终实现宽带低反射匹配。

百态·数智思维 | 聚焦大数据、量化与未来AI 1106

go-micro etcd 注册中心性能优化实战:KeepAlive 长连接与缓存穿透防护

go-micro 的 etcd 注册中心在开启服务端认证、服务规模增长到数千 Pod 时,会遭遇认证请求 QPS 打满集群和缓存穿透引发的级联故障。本文基于 [registry/etcd/PERFORMANCE.md](https://link.gitcode.com/i/d4001bf247f898b3b0211f127f0751ed) 这一性能改进文档,结合 [registry/etcd/et

gitblog_00972的博客 933

NS-3 和Aqua-sim-ng 学习笔记(一)

因为要搞水下网络,OMNET++不太合适,就转Aqua-sim。这是基于NS-2上的模型,NS-2需要额外学一门tcl脚本语言。 正好查到它的一个升级版Aqua-sim-NG,在github上,https://github.com/rmartin5/aqua-sim-ng是基于NS-3的,只需要基本的C++,不用学tcl语言。美滋滋。 装好ubuntu之后,安装NS-3和Aqu...

diboshang8820的博客 2191

groupcache 分布式缓存实战指南:缓存填充、去重与一致性哈希原理全解析

groupcache 是一个用 Go 实现的分布式缓存与缓存填充(cache-filling)库,目标是让开发者用一台服务器、一套进程内代码就搭建起一个可横向扩展的分布式缓存集群,从而在许多场景下替代一组独立的 memcached 节点。本文以仓库根目录的 [README.md](https://link.gitcode.com/i/26a2f9178a739c259452a6846b10ef16

gitblog_00876的博客 395

工业级4~20mA转0~3V信号调理电路原理图设计,已量产(电流传感器)

在工业现场,4~20mA 电流信号是模拟量传输的 “老熟人”—— 抗干扰强、能跑长距离,但嵌入式 MCU 的 ADC 一般只认电压信号。最近做项目时,我设计了一款能覆盖 0~25mA(含超量程冗余)、线性输出 0~3V 的调理电路,经过现场调试挺稳定,分享下细节。

不脱发的程序猿 525

基于STM32L4XX、HAL库的CH182H1以太网收发器(PHY)驱动程序设计

define PHY_REG_PHYSCR 0x12 // PHY特殊控制寄存器。#define PHY_REG_PHYSTS 0x10 // PHY状态寄存器。#define PHY_REG_PHYIDR 0x11 // PHY标识寄存器。#define PHY_REG_ID1 0x02 // PHY标识符1。#define PHY_REG_ID2 0x03 // PHY标识符2。

zhusl6688的博客 403

opencodex OAuth 刷新单飞(singleflight)与 Kiro CLI SQLite 令牌重载机制详解

本篇技术指南围绕 opencodex 在 Kiro(CodeWhisperer)网关对齐工程中的 **Phase 90(P0-3)OAuth 刷新加固**展开:通过按 provider 的刷新单飞(singleflight)消除并发临近过期请求导致的重复刷新与凭证竞写,并在刷新前后对本地 Kiro CLI SQLite 令牌缓存做重载以优先采用更新的外部会话。读完本文,你将掌握 opencodex

gitblog_00549的博客 1113

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

7天用Go从零实现:Gee、GeeCache、GeeORM、GeeRPC 四大框架源码全览

本篇技术指南以 [README.md](https://link.gitcode.com/i/fa90d448bf3fa5612ee2e6f988f9104e) 为主线,系统梳理 `7days-golang` 仓库中"7天用 Go 从零实现"系列的四大开源项目:Web 框架 Gee、分布式缓存 GeeCache、ORM 框架 GeeORM、RPC 框架 GeeRPC,并延伸介绍 WebAssemb

gitblog_01015的博客 376

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

go-zero高级并发工具集:executors上下文隔离与collection进程内缓存实战指南

在 Go 微服务开发中,**go-zero** 框架内置了两个常被低估的并发利器:**executors 批量任务执行器**和 **collection.Cache 进程内缓存**。它们分别解决"高并发下任务缓冲与执行隔离"和"热点数据缓存与击穿防护"两大痛点,让新手也能快速写出高性能、稳定的服务端代码。 ## 为什么需要并发工具集 🚀 高并发场景下,一个请求往往要扇出多个依赖调用。如果没有

gitblog_00777的博客 1016

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

JX3Toy 剑网3 Lua 技能宏配置指南:从克隆到首次运行三步完成

JX3Toy 是一款剑网3 Lua 技能宏工具,按门派和职业拆好现成脚本,覆盖副本输出、日常任务等场景,适合不想从零写代码的 PVE 玩家,配置进游戏后明显减少重复点击。 ## 项目定位与技术原理 JX3Toy 把各门派的技能循环写成可直接运行的脚本文件,按职业分文件夹存放,七秀、天策、少林各占一个目录。它的核心收益是把"什么时候放什么技能"这类重复判断交给脚本完成,玩家只需负责走位和应对突发

gitblog_01043的博客 428

【Java】缓存击穿解决方案

参考go语言中的SingleFlight来解决缓存击穿,并进行优化

Zhangsama1的博客 9252

SiYuan v2.10.12 版本解析:集市包搜索的落地实现与周边改进

本文基于 SiYuan v2.10.12 官方变更记录([v2.10.12_zh_CN.md](https://link.gitcode.com/i/31a67d6972209f0d24ebccb1f17a6c76)),围绕本版本的核心特性——集市(Bazaar)支持搜索——展开,结合当前仓库中 `kernel/bazaar`、`kernel/model/bazaar.go`、`kernel/ap

gitblog_00167的博客 575

7days-golang ORM框架GeeORM完全指南:如何模仿gorm/xorm在7天实现一个ORM框架

**GeeORM** 是 7days-golang 开源项目中的一个 Go 语言 ORM 框架教学项目:它在 7 天内从零模仿 gorm 和 xorm,用约 1000 行代码亲手实现一个可用的 ORM 框架,涵盖对象表结构映射、记录增删查改、链式操作、钩子 Hooks、事务 Transaction 和数据库迁移 Migrate 等核心特性。本指南带你快速看懂这个 ORM 框架的完整设计思路与 7

gitblog_00834的博客 523

AI 如何自动分类截图?Shots Studio 智能归入集合的完整原理指南

Shots Studio 是一款 AI 驱动的 Android 截图管理工具,它能用 AI 分析截图内容,并把截图自动归类到对应的集合(Collection)中,让杂乱的截图相册变成有序档案。本文带你完整看懂它的自动分类功能是如何运作的:从 AI 如何"读懂"截图,到两条自动归类路径的触发时机,再到正确的开启方式与实用建议。 [![Shots Studio 项目页面:AI 驱动的截图管理工具](

gitblog_00570的博客 1010

ArtCraft:62 个模型的 AI 创作工作台,从文生图到 3D 布景一站配齐

姿势、机位、光线都想攥在自己手里,可光往 API 里丢提示词永远差口气。ArtCraft 是一个 Rust + Tauri 桌面应用,内置 62 个覆盖图像、视频、音频、3D 的生成模型,更像一间能挪道具、搭布景的虚拟工作室:先把场景搭好,再去出图。 它解决的正是生成式工作流里最烦的事——重画。构图不满意,不用重新掷骰子:把静图拆成图层,把角色摆成想要的姿势,把道具在三维空间里挪到正确深度,然后

gitblog_00247的博客 205

WeChatMsg 实战指南:3 步跑通微信聊天记录导出,顺带生成年度聊天报告

WeChatMsg 是一款免费的微信聊天记录导出工具:它读取电脑版微信的本地数据,把对话导出成 HTML、Word 或 CSV 文档永久留存,还能基于整年数据生成年度聊天报告。首次跑通约 2 条命令、3 个步骤,10 分钟内拿到第一批记录。 ## WeChatMsg 适合谁:3 类场景与 1 个用不上的情况 这节能帮你在 1 分钟内判断自己值不值得上手,避免白花时间。 - 想给重要对话留底:

gitblog_01044的博客 615
上一篇: 分析kubernetes中的事件机制
下一篇: 记一次问题排查:为什么在POD无法通过Service访问自己?
学点程序
博客等级 码龄12年 64粉丝 68原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值