Go 语言进阶freecache源码学习教程
作者:隔热城市 发布时间:2023-08-06 03:05:20
00. 什么是 freecache?
freecache 是一个用 go 语言实现的本地缓存系统(类似于 lru)。
相关的 github 地址:https://github.com/coocood/freecache
它有几个特性值得注意:
通过优秀的内存管理方案,实现了 go 语言的零 gc
是线程安全的,同时支持一定程度的并发,非常适合并发场景
支持设置失效时间,动态失效过期缓存
在一定程度上支持 lru,即“最近最少使用”,会在容量不足的时候优先淘汰较早的数据
这几个优秀特性使得他非常适合用在生产环境中作为本地缓存。
01. 简单用法
cacheSize := 100 * 1024 * 1024
cache := freecache.NewCache(cacheSize)
debug.SetGCPercent(20)
key := []byte("abc")
val := []byte("def")
expire := 60 // expire in 60 seconds
cache.Set(key, val, expire)
got, err := cache.Get(key)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(got))
}
affected := cache.Del(key)
fmt.Println("deleted key ", affected)
fmt.Println("entry count ", cache.EntryCount())
02. 功能概览
本文计划先以自然语言描述下列功能的实现方式,再接下来的章节中深入源码,扒出其具体实现
如何做到零 gc?
数据结构
动态索引
缓存失效
03. 如何做到 0 GC?
这个库之所以做到了 0 gc,是因为设定了很巧妙的数据结构,这个数据结构有以下的特点:
无论存多少数据,都只会存在 512 个指针
所有的具体数据的存储空间,都是预先就分配好的,而不是动态分配的
使用了一些黑科技,比如强制类型转换以及结构体对齐等技术,将所有的动态数据都管理在预先分配好的连续空间内
04. 数据结构
可以将这个数据结构大体上抽象成一个哈希表。即你会看到哈希函数以及对应的数组空间。但是他和传统的哈希表是有区别的,主要有以下几点:
线程安全,支持高并发,内存空间高度优化:
为了做到支持高并发以及线程安全,并且保证内存空间的可用性,freecache 实际上划分出了 256 个独立数组空间用来存储对应的底层数据。
这样在并发访问的时候,通过对 key 进行分片,使得请求只会打到一个数组空间上,即只会对这 256 个空间中的一个空间加锁,这样就大大降低了资源争抢。
数据的组织方式与传统哈希表不同:
传统的哈希表只在数组空间中存 value。而 freecache 则不同,他将 key,value 全部存在了数组空间中。
传统哈希表的数组是稀疏的。freecache 数据并不是稀疏的,而是连续的,即新的值会不断 append 到最后。
传统哈希表使用 hash func 对 key 取索引,索引到稀疏数组中的位置。而 freecache 则通过维护了一个叫“slot(插槽)”的数据结构,通过对 key 进行 hash func,先拿到对应的 slot,然后 slot 中维护着一个索引,可以定位到具体的数据在数组中的位置。
解决哈希冲突的方式不同。当遇到哈希冲突的时候,哈希表需要对底层的稀疏数组进行扩容,会导致可用性大大降低。而 freecache 则是只需要对“slot”的指针数组进行扩容,而无需改变底层数组。因为 slot 指针数组的大小远小于底层数组,所以扩容的成本是非常非常低的。
为了实现缓存淘汰以及定时失效,它的数组空间在逻辑上是一个环状的。这么做有以下原因
数组存的数据逻辑上是连续的,而非稀疏数组。充分利用了CPU对数组读取的缓存优化
通过使用了一系列的首尾计算方式,是足以保证读取和存储在首尾的连续性。比如读数据的时候读到结尾如果还没读完,会跳转到头部继续往下读。
能以 O(1) 的时间复杂度完成新数据对旧数据的淘汰。我们假设如果数组在逻辑上不是环形的,那么当数组写满的时候再写入新的数据,就需要把数组头部的数据删除掉,然后再把之后的数据统统向左移动删除数据的长度,然后再从最右端写入新的数据。反之,如果数组是环形的,只需要在数组头部把旧数据覆盖即可
通过一些算法手段,可以实现一个很 hack 的 lru 功能。在一定程度上能保证“最近最少使用”的淘汰。
05. 动态索引
通过上面对数据结构的分析,我们知道他和传统的哈希表的实现方式大相径庭。它实际上是使用了一种叫“插槽”的数据结构,专门负责通过 key 索引到数据空间中的某个位置。他的实现比较有意思。它的底层是一个结构体指针数组。他是可以动态扩容的。每个插槽有一个 id,叫 slotId,范围是 0~255。当遇到哈希冲突的时候,比如出现了2个 slotId 为 100 的 slot,他就会检测当前的最大重复 slotID 数量 n。如果这个 2 大于 n,他就会扩容到 2n。
// slot 的数据结构
type entryPtr struct {
offset int64 // 记录了当前 slot 在环形数组中的偏移量
hash16 uint16 // 一个 hash 值,用于在 segment 中定位到具体的 slot
keyLen uint16 // used to compare a key
reserved uint32
}
// 每个分片的数据结构
type segment struct {
rb RingBuf // 环形数组
segId int
hitCount int64
missCount int64
entryCount int64
totalCount int64 // 之后计算 lru 的时候会用到,用于衡量一个数据是否是热点数据
totalTime int64 // 之后计算 lru 的时候会用到,用于衡量一个数据是否是热点数据
totalEvacuate int64 // used for debug
totalExpired int64 // used for debug
overwrites int64 // used for debug
vacuumLen int64 // 环形数组可用容量,主要用于环形数组首尾相接的算法
slotLens [256]int32 // 每个 slotId 的长度的数组
slotCap int32 // 每个 slotId 的容量
slotsData []entryPtr // 存储 slots 的切片,根据 hash16 进行顺序排列。相同的 hash16 拥有相同的 slotId
}
06. 缓存失效
缓存失效主要包括两种:
基于过期时间的失效
基于最近最少使用的失效
这两种失效都有缺陷。
首先它是一种被动失效,而不是通过一个额外的线程定期check。而是每次 set 新的值的时候,如果发现空间不够用了,他才会尝试从环形数组的头端进行check。如果发现当前check的数据过期了,或者使用频率过低,就会将记录有效数据的头指针进行偏移,即相当于“失效”。如果检查多次都没能找到需要失效的数据,那么他会将这些检查过的数据转移到尾部,并强制当前的头部的数据失效。
这种失效是不可靠的。比如一个数据,如果在环形数组的中间,那么即便它过期了,也很难被 check 到。并且存在一定的失误概率,即将一个热点数据给失效了。
来源:https://zhuanlan.zhihu.com/p/67298011
猜你喜欢
- 废话少说,直接上代码<script type="text/javascript"> &
- Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务。本文参考自Flask官方文档,
- OpenCV+python3将视频分解成图片,供大家参考,具体内容如下我们在工作或学习时,偶尔需要将视频分解成图片,只取其中一段的图片就行了
- 详解python里使用正则表达式的全匹配功能python中很多匹配,比如搜索任意位置的search()函数,搜索边界的match()函数,现
- 显示图像: Image img = Image.From
- 在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成。为了解决这些问题,在2003年I
- 背景为了更好的发展自身的测试技能,应对测试行业以及互联网行业的迭代变化。自学python以及自动化测试。虽然在2017年已经开始接触了sel
- 1. 前言 本文介绍一个贝叶斯推断的pytho
- javaScript 代码如下:$(document).ready(function(){ $(".message_list .m
- 在SQL Server数据库中,主要是通过角色来继承相关的权限。但是,这个权限继承很容易造成权限上的冲突。如现在有个销售员账户SALE1,有
- 环境:winxp sp2 ,mysql5.0.18,mysql odbc 3.51 driver 表采用 myisam引擎。access 2
- 一,集群搭建步骤1.先在一台虚拟机配置jdk,hadoop2.克隆3.修改网络等相关配置当我们使用虚拟机时,可能自然而然的会想上面的步骤一样
- Python实现完整邮件先上效果:一、邮箱端设置首先,要对邮件进行一下设置,在邮箱端获取一个授权码。1、首先登录网页版126邮箱
- 今天写了一个获取当前公网ip并且自动断开宽带连接的文件,和大家分享下。这个文件的具体用途大家懂的,可以尽管拿去用,不过目前只适用于Windo
- split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串,并返回分割后的字符串列
- 使用input和raw_input都可以读取控制台的输入,但是input和raw_input在处理数字时是有区别的当输入为纯数字时:inpu
- 前言今天在使用 8.0.12 版的 mysql 驱动时遇到了各种各样的坑,在使用 JDBC 连接上遇到的问题可以参考我的上一篇博客。我在使用
- 连接MySQL时出现1449与1045异常解决办法mysql 1449 : The user specified as a definer
- 前言最近网站从HTTPS转为HTTP,更换了网址,旧网址做了301重定向,折腾有点大,于是在百度站长平台提交网址,不管是主动推送还是手动提交
- 上次 li 把 dl 模拟了~dl不知道要干什么了:green:~找了ol一起来做复合列表~:这个练习除了css外~外加用了点JS :shi