【Golang开源项目】Golang高性能内存缓存库BigCache设计与分析

news2024/11/18 13:49:04

项目地址

BigCache 是一个快速,支持并发访问,自淘汰的内存型缓存,可以在存储大量元素时依然保持高性能。BigCache将元素保存在堆上却避免了GC的开销。

背景介绍

BigCache的作者在项目里遇到了如下的需求:

  • 支持http协议
  • 支持 10 k 10k 10kRPS ,其中读写各占一半
  • cache缓存至少 10 10 10分钟
  • 平均 r t = 5 m s , p 99 < = 10 m s , p 999 < = 400 m s rt=5ms,p99<=10ms,p999<=400ms rt=5ms,p99<=10ms,p999<=400ms
    开发的缓存库需要保证:
  • 即使有百万的缓存对象速度也要很快
  • 支持高并发访问
  • 支持过期自动删除

简单入门

func Test_BigCache(t *testing.T) {
	cache, _ := bigcache.New(context.Background(), bigcache.DefaultConfig(10*time.Minute)) //定义cache
	cache.Set("my-unique-key", []byte("value")) //设置k,v键值对
	entry, _ := cache.Get("my-unique-key") //获取k,v键值对
	t.Log(string(entry))
}

配置文件

config字段说明

字段名类型含义
Shardsint缓存分片数,值必须是 2 的幂
LifeWindowtime.Duration条目可以被逐出的时间,近似可以理解为缓存时间
CleanWindowtime.Duration删除过期条目(清理)之间的间隔。如果设置为 <= 0,则不执行任何操作。设置为 < 1 秒会适得其反,因为 bigcache 的分辨率为 1 秒。
MaxEntriesInWindowint生命周期窗口中的最大条目数。仅用于计算缓存分片的初始大小。如果设置了适当的值,则不会发生额外的内存分配。
MaxEntrySizeint条目的最大大小(以字节为单位)。仅用于计算缓存分片的初始大小。
StatsEnabledboolStatsEnabled如果为true,则计算请求缓存资源的次数。
Verbosebool是否以详细模式打印有关新内存分配的信息
HasherHasher哈希程序用于在字符串键和无符号 64 位整数之间进行映射,默认情况下使用 fnv64 哈希
HardMaxCacheSizeint是BytesQueue 大小的限制 MB。它可以防止应用程序占用计算机上的所有可用内存,从而防止运行 OOM Killer。
OnRemovefunc(key string, entry []byte)OnRemove 是当最旧的条目由于过期时间或没有为新条目留出空间或调用 delete 而被删除时触发的回调。如果指定了 OnRemoveWithMetadata,则忽略。
OnRemoveWithMetadatafunc(key string, entry []byte, keyMetadata Metadata)OnRemoveWithMetadata 是当最旧的条目由于过期时间或没有为新条目留出空间或调用 delete 而被删除时触发的回调,携带有关该特定条目的详细信息的结构。
OnRemoveWithReasonfunc(key string, entry []byte, reason RemoveReason)OnRemoveWithReason 是当最旧的条目由于过期时间或没有为新条目留出空间或调用了 delete 而被删除时触发的回调,将传递一个表示原因的常量。如果指定了 OnRemove,则忽略。
onRemoveFilterint和OnRemoveWithReason一起使用,阻止 bigcache 解包它们,从而节省 CPU
LoggerLogger日志记录接口

说明:

  • LifeWindow 是一个时间。在此之后,条目可以称为死条目,但不能删除。
  • CleanWindow 是一个时间。在此之后,将删除所有无效条目,但不会删除仍具有生命的条目。
  • HardMaxCacheSize 默认值为 0,表示大小不受限制。当限制高于 0 并达到时,新条目将覆盖最旧的条目。由于 Shards 的额外内存,最大内存消耗将大于 HardMaxCacheSize。每个分片都会消耗额外的内存来映射键和统计信息 (map[uint64]uint32),此映射的大小等于缓存中的条目数 ~ 2×(64+32)×n 位 + 开销或映射本身。
  • OnRemove,OnRemoveWithMetadata ,OnRemoveWithReason 这三个跟删除有关的属性默认值为 nil,表示没有回调,并且会阻止解开最早的条目。

配置代码文件


// Config for BigCache
type Config struct {
	// Number of cache shards, value must be a power of two
	Shards int
	// Time after which entry can be evicted
	LifeWindow time.Duration
	// Interval between removing expired entries (clean up).
	// If set to <= 0 then no action is performed. Setting to < 1 second is counterproductive — bigcache has a one second resolution.
	CleanWindow time.Duration
	// Max number of entries in life window. Used only to calculate initial size for cache shards.
	// When proper value is set then additional memory allocation does not occur.
	MaxEntriesInWindow int
	// Max size of entry in bytes. Used only to calculate initial size for cache shards.
	MaxEntrySize int
	// StatsEnabled if true calculate the number of times a cached resource was requested.
	StatsEnabled bool
	// Verbose mode prints information about new memory allocation
	Verbose bool
	// Hasher used to map between string keys and unsigned 64bit integers, by default fnv64 hashing is used.
	Hasher Hasher
	// HardMaxCacheSize is a limit for BytesQueue size in MB.
	// It can protect application from consuming all available memory on machine, therefore from running OOM Killer.
	// Default value is 0 which means unlimited size. When the limit is higher than 0 and reached then
	// the oldest entries are overridden for the new ones. The max memory consumption will be bigger than
	// HardMaxCacheSize due to Shards' s additional memory. Every Shard consumes additional memory for map of keys
	// and statistics (map[uint64]uint32) the size of this map is equal to number of entries in
	// cache ~ 2×(64+32)×n bits + overhead or map itself.
	HardMaxCacheSize int
	// OnRemove is a callback fired when the oldest entry is removed because of its expiration time or no space left
	// for the new entry, or because delete was called.
	// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
	// ignored if OnRemoveWithMetadata is specified.
	OnRemove func(key string, entry []byte)
	// OnRemoveWithMetadata is a callback fired when the oldest entry is removed because of its expiration time or no space left
	// for the new entry, or because delete was called. A structure representing details about that specific entry.
	// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
	OnRemoveWithMetadata func(key string, entry []byte, keyMetadata Metadata)
	// OnRemoveWithReason is a callback fired when the oldest entry is removed because of its expiration time or no space left
	// for the new entry, or because delete was called. A constant representing the reason will be passed through.
	// Default value is nil which means no callback and it prevents from unwrapping the oldest entry.
	// Ignored if OnRemove is specified.
	OnRemoveWithReason func(key string, entry []byte, reason RemoveReason)

	onRemoveFilter int

	// Logger is a logging interface and used in combination with `Verbose`
	// Defaults to `DefaultLogger()`
	Logger Logger
}

默认配置

DefaultConfig 使用默认值初始化配置。当可以提前预测 BigCache 的负载时,最好使用自定义配置

字段名含义
Shards1024缓存分片数是1024
LifeWindoweviction自定义过期时间
CleanWindow1 * time.Second每隔1秒就清理失效数据
MaxEntriesInWindow1000 * 10 * 60生命周期窗口中的最大条目数为6e5
MaxEntrySize500条目的最大大小为500字节
StatsEnabledfalse不计算请求缓存资源的次数
Verbosetrue以详细模式打印有关新内存分配的信息
Hasherfnv64哈希程序,fnv64 哈希
HardMaxCacheSize0BytesQueue 大小无限制
LoggerDefaultLogger日志记录接口

优点:支持自定义过期时间,清理失效数据的间隔为最小间隔、效率高
缺点:BytesQueue 大小无限制,容易造成内存占用过高
默认配置代码:

func DefaultConfig(eviction time.Duration) Config {
	return Config{
		Shards:             1024,
		LifeWindow:         eviction,
		CleanWindow:        1 * time.Second,
		MaxEntriesInWindow: 1000 * 10 * 60,
		MaxEntrySize:       500,
		StatsEnabled:       false,
		Verbose:            true,
		Hasher:             newDefaultHasher(),
		HardMaxCacheSize:   0,
		Logger:             DefaultLogger(),
	}
}

数据结构

前提说明:BigCache 是快速、并发、逐出缓存,旨在保留大量条目而不影响性能。它将条目保留在堆上,但省略了它们的 GC。为了实现这一点,操作发生在字节数组上,因此在大多数用例中,都需要在缓存前面进行条目**(反序列化)**。

BigCache数据结构

字段名类型含义
shards[]*cacheShard缓存分片数据
lifeWindowuint64缓存时间,对应配置里的LifeWindow
clockclock时间计算函数
hashHasher哈希函数
configConfig配置文件
shardMaskuint64值为(config.Shards-1),寻找分片位置时使用的参数,可以理解为对config.Shards取余后的最大值
closechan struct{}关闭通道
type BigCache struct {
	shards     []*cacheShard
	lifeWindow uint64
	clock      clock
	hash       Hasher
	config     Config
	shardMask  uint64
	close      chan struct{}
}

cacheShard数据结构

字段名类型含义
hashmapmap[uint64]uint32索引列表,key为存储的key,value为该key在entries里的位置
entriesqueue.BytesQueue实际数据存储的地方
locksync.RWMutex互斥锁,用于并发读写
entryBuffer[]byte入口缓冲区
onRemoveonRemoveCallback删除回调函数
isVerbosebool是否详细模式打印有关新内存分配的信息
statsEnabledbool是否计算请求缓存资源的次数
loggerLogger日志记录函数
clockclock时间计算函数
lifeWindowuint64缓存时间,对应配置里的LifeWindow
hashmapStatsmap[uint64]uint32存储缓存请求次数
statsStats存储缓存统计信息
cleanEnabledbool是否可清理,由config.CleanWindow决定
type cacheShard struct {
	hashmap     map[uint64]uint32
	entries     queue.BytesQueue
	lock        sync.RWMutex
	entryBuffer []byte
	onRemove    onRemoveCallback

	isVerbose    bool
	statsEnabled bool
	logger       Logger
	clock        clock
	lifeWindow   uint64

	hashmapStats map[uint64]uint32
	stats        Stats
	cleanEnabled bool
}

BytesQueue数据结构

BytesQueue 是一种基于 bytes 数组的 fifo 非线程安全队列类型。对于每个推送操作,都会返回条目的索引。它可用于稍后读取条目。

字段名类型含义
fullbool队列是否已满
array[]byte实际数据存储的地方
capacityint容量
maxCapacityint最大容量
headint队首位置
tailint下次可以插入的元素位置
countint当前存在的元素数量
rightMarginint右边界
headerBuffer[]byte插入时的临时缓冲区
verbosebool是否详细模式打印有关新内存分配的信息
type BytesQueue struct {
	full         bool
	array        []byte
	capacity     int
	maxCapacity  int
	head         int
	tail         int
	count        int
	rightMargin  int
	headerBuffer []byte
	verbose      bool
}

优秀设计

处理并发访问

设计点1:将数据打散后存储

通用解法: 缓存支持并发访问是很基本的要求,比较常见的解决访问是对缓存整体加读写锁,在同一时间只允许一个协程修改缓存内容。这样的缺点是锁可能会阻塞后续的操作,而且高频的加锁、解锁操作会导致缓存性能降低。

设计点: B i g C a c h e BigCache BigCache使用一个 s h a r d shard shard数组来存储数据,将数据打散到不同的 s h a r d shard shard里,每个 s h a r d shard shard里都有一个小的 l o c k lock lock,从而减小了锁的粒度,提高访问性能。

设计点2:打散数据过程中借助位运算加快计算速度

接下来看一下将某个数据放到缓存的过程的源代码:

// Set saves entry under the key
func (c *BigCache) Set(key string, entry []byte) error {
	hashedKey := c.hash.Sum64(key)
	shard := c.getShard(hashedKey)
	return shard.set(key, hashedKey, entry)
}
func (c *BigCache) getShard(hashedKey uint64) (shard *cacheShard) {
	return c.shards[hashedKey&c.shardMask]
}

可以得到 s e t set set的过程如下:

  • 进行 h a s h hash hash操作,将 s t r i n g string string类型 k e y key key哈希为一个 u i n t 64 uint64 uint64类型的 h a s h e d K e y hashedKey hashedKey
  • 根据 h a s h e d K e y hashedKey hashedKey s h a r d i n g sharding sharding,最后落到的 s h a r d shard shard的下标为 h a s h e d K e y % n hashedKey\%n hashedKey%n,其中 n n n是分片数量。理想情况下,每次请求会均匀地落在各自的分片上,单个 s h a r d shard shard的压力就会很小。
  • 调用对应 s h a r d shard shard的set方法来设置缓存

设计点:
n n n 2 2 2的幂次方的时候,对于任意的 x x x,下面的公式都成立的。
x   m o d   N = ( x & ( N − 1 ) ) x\ mod\ N = (x \& (N − 1)) x mod N=(x&(N1))
所以可以借助位运算快速计算余数,因此倒推回去 缓存分片数必须要设置为 2 2 2的幂次方

设计点3 避免栈上的内存分配

默认的哈希算法为 f n v 64 fnv64 fnv64算法,该算法采用位运算的方式在栈上运算,避免了在堆上分配内存

package bigcache

// newDefaultHasher returns a new 64-bit FNV-1a Hasher which makes no memory allocations.
// Its Sum64 method will lay the value out in big-endian byte order.
// See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
func newDefaultHasher() Hasher {
	return fnv64a{}
}

type fnv64a struct{}

const (
	// offset64 FNVa offset basis. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash
	offset64 = 14695981039346656037
	// prime64 FNVa prime value. See https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function#FNV-1a_hash
	prime64 = 1099511628211
)

// Sum64 gets the string and returns its uint64 hash value.
func (f fnv64a) Sum64(key string) uint64 {
	var hash uint64 = offset64
	for i := 0; i < len(key); i++ {
		hash ^= uint64(key[i])
		hash *= prime64
	}

	return hash
}

减少GC开销

设计点1:利用go1.5+特性,减少GC扫描

g o l a n g golang golang里实现缓存最简单的方式是 m a p map map来存储元素,比如 m a p [ s t r i n g ] I t e m map[string]Item map[string]Item
使用 m a p map map的缺点为垃圾回收器 G C GC GC会在标记阶段访问 m a p map map里的每一个元素,当 m a p map map里存储了大量数据的时候会降低程序性能。

B i g C a c h e BigCache BigCache使用了 g o 1.5 go1.5 go1.5版本以后的特性:如果使用的map的key和value中都不包含指针,那么GC会忽略这个map
具体而言, B i g C a c h e BigCache BigCache使用 m a p [ u i n t 64 ] u i n t 32 map[uint64]uint32 map[uint64]uint32
来存储数据,不包含指针, G C GC GC就会自动忽略这个 m a p map map

m a p map map k e y key key存储的是缓存的 k e y key key经过 h a s h hash hash函数后得到的值
m a p map map v a l u e value value存储的是序列化后的数据在全局 [ ] b y t e []byte []byte中的下标。
因为 B i g C a c h e BigCache BigCache是将存入缓存的 v a l u e value value序列化为 b y t e byte byte数组,然后将该数组追加到全局的 b y t e byte byte数组里(说明:结合前面的打散思想可以得知一个 s h a r d shard shard对应一个全局的 b y t e byte byte数组
这样做的缺点是删除元素的开销会很大,因此 B i g C a c h e BigCache BigCache里也没有提供删除指定 k e y key key的接口,删除元素靠的是全局的过期时间或是缓存的容量上限,是先进先出的队列类型的过期。

性能测试

项目开发者给出了项目和主流缓存方案的 B e n c h m a r k s Benchmarks Benchmarks结果和 G C GC GC测试结果
测试文件链接

在这里插入图片描述
在这里插入图片描述

参考
妙到颠毫: bigcache优化技巧
[译] Go开源项目BigCache如何加速并发访问以及避免高额的GC开销

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

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

相关文章

【RT-DETR改进涨点】为什么YOLO版本的RT-DETR训练模型不收敛的问题

前言 大家好&#xff0c;我是Snu77&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 其中提到的多个版本ResNet18、ResNet34、ResNet50、ResNet101为本人根据RT-…

Linux|centos7操作系统|VMware虚拟机安装水星免驱USB网卡8188gu记录

引言&#xff1a; 最近对于嵌入式系统比较感兴趣&#xff0c;因此&#xff0c;计划先使用VMware workstation虚拟机试一试Linux系统下的网卡驱动安装 这不试不知道&#xff0c;一试吓一跳&#xff0c;发现Linux下的驱动安装还是比较麻烦的&#xff0c;下面将就本次的Linux系统…

详解SpringCloud微服务技术栈:强推!源码跟踪分析Ribbon负载均衡原理、Eureka服务部署

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;详解SpringCloud微服务技术栈&#xff1a;认识微服务、服务拆分与远程调用 &#x1f4da;订阅专栏&#xff1a;微服务技术全家桶…

【LangChain学习之旅】—(6) 提示工程(下):用思维链和思维树提升模型思考质量

【LangChain学习之旅】—&#xff08;6&#xff09; 提示工程&#xff08;下&#xff09;&#xff1a;用思维链和思维树提升模型思考质量 什么是 Chain of ThoughtFew-Shot CoTZero-Shot CoTChain of Thought 实战CoT 的模板设计程序的完整框架Tree of Thought总结 Reference&a…

UE5 伤害数字跳出

学习视频 大体思路&#xff1a; 1.创建一个控件蓝图。 播放动画&#xff0c;K透明度&#xff0c;文本位置。 2.创建一个控件组件。 类默认值中设置。 3.在位置处创建组件实例 自定义事件略。

NVIDIA Isaac Sim 入门教程(一)

系列文章目录 前言 一、 NVIDIA Omniverse™ Isaac Sim 是什么&#xff1f; NVIDIA Omniverse™ Isaac Sim 是一款适用于 NVIDIA Omniverse™ 平台的机器人仿真工具包。Isaac Sim 具有构建虚拟机器人世界和实验的基本功能。它为研究人员和从业人员提供了创建稳健、物理精确的仿…

无法解析服务器的名称或地址/Wsl/0x80072eff/win10 WSL2问题解决Wsl 0x800701bc/Wsl:0x80041002

无法解析服务器的名称或地址 和 Wsl/0x80072eff 1.连VPN&#xff0c;推荐的VPN如下。(如一直显示无法连接&#xff0c;则推荐使用VPN) Anycast加速器 (any4ga.com) 优点&#xff1a;无限GB 缺点&#xff1a;较贵&#xff0c;通过银行卡充值9折后的价格是每月45元左右 …

JVM:双亲委派机制类加载器

JVM&#xff1a;双亲委派机制 1. 例子2. 类加载器总结3. 类加载过程4. 双亲委派模型的执行流程&#xff1a;5. 双亲委派模型的好处 1. 例子 Java运行时环境有一个java.lang包&#xff0c;里面有一个ClassLoader类 我们自定义一个String类在java.lang包下&#xff0c;下面的…

WEB 3D技术 three.js 点光源

本文的话 我们来设置一下点光源 点光源其实最直观的就是可以做萤火虫 也可以做星光 点点的效果 我们可以直接在官网中搜索 Pointlight 大家可以在官网这里看一下 其实 SpotLight 聚关灯中的属性 Pointlight 点光源也有的 我们先编写代码如下 import ./style.css import * a…

MySQL之单表查询

素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker ( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL, 工资 float(8,2) NOT NULL, 政治面貌 varchar(10) NO…

【Git】本地仓库管理远程库(GitHub)——clone(下载)、commit(添加到本地仓库)、push(提交到远程仓库)、pull(拉取)操作

目录 使用远程仓库的目的将本地仓库同步到git远程仓库 1.克隆远程仓库(clone)2.新建一个文件3.将工作区的文件添加到暂存区4.将暂存区的文件添加到本地仓库(commit)5.提交(同步)到远程仓库(push)6.远程库拉取到本地库(pull)7.团队协作开发和跨团队协作开发(开源项目) 使用远程…

PTA(浙大版《C语言程序设计(第3版)》题目集

PTA(浙大版《C语言程序设计&#xff08;第3版&#xff09;》题目集 学习C语言程序设计的PTA题目 目录 PTA(浙大版《C语言程序设计&#xff08;第3版&#xff09;》题目集PTA(浙大版《C语言程序设计&#xff08;第3版&#xff09;》题目集) 习题2-1 求整数均值 (10 分)输入格式:…

大型语言模型综述/总结 LLM A Survey of Large Language Models

A Survey of Large Language Model AbstractINTRODUCTIONOVERVIEW背景LLM的新兴能力LLM的关键技术GPT 系列模型的技术演进 大语言模型资源公开可用的模型检查点或 API常用语料库代码库资源 预训练数据收集架构 论文标题&#xff1a;A Survey of Large Language Model 论文地址&…

电子学会C/C++编程等级考试2023年09月(五级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:红与黑 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。 时间限制:1000 内存限制:65536 输入 包括多…

Linux的SSH远程管理和服务器之间的免密连接

目录 一、远程管理基础 1.ssh协议 2.ssh原理 3、使用ssh协议传输的命令 4.登录方法 二、免密连接 1.免密连接的原理 2.实战 一、远程管理基础 1.ssh协议 ssh协议是基于C/S机构的安全通道协议&#xff0c;通信数据进行加密处理&#xff0c;用于远程管理。 ssh的服务名…

Python文件读写与字符编码详解【第25篇—python基础知识】

文章目录 文件读写和字符编码在Python中的实现一、I/O操作概述二、文件读写实现原理与操作步骤1. 文件读写实现原理2. 文件读写操作步骤 三、文件打开模式四、Python文件操作步骤示例五、文件读取相关方法1. 读取指定长度的内容2. 读取文件中的一行内容3. 遍历打印一个文件中的…

Zookeeper使用详解

介绍 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布…

【搭建个人知识库-3】

搭建个人知识库-3 1 大模型开发范式1.1 RAG原理1.2 LangChain框架1.3 构建向量数据库1.4 构建知识库助手1.5 Web Demo部署 2 动手实践2.1 环境配置2.2 知识库搭建2.2.1 数据收集2.2.2 加载数据2.2.3 构建向量数据库 2.3 InternLM接入LangChain2.4 构建检索问答链1 加载向量数据…

鸿蒙应用开发学习:让page页面强制横屏

一、学习做了个适合横屏的页面但进入页面后是竖屏显示的 前几天在B站上跟着 黑马程序员的 HarmonyOS4.0开发应用教学视频学习了显式动画&#xff08;animateTo&#xff09;和属性动画&#xff08;animation&#xff09;功能&#xff0c;并参照教学视频的内容做了个小鱼动画。…

基于MOD02/MYD02获得亮度温度再转冰温

用HEG处理MOD02/MYD02,提取里面的EV_1KM_Emissive波段,band为11和12(其实就是band 31和32)。注意这里的band和output dile type 1. 获得之后,转辐射亮度。 参考:https://www.cnblogs.com/enviidl/p/16539422.html radiance_scales,和radiance_offset这两项参数代表波段…