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. 功能概览

本文计划先以自然语言描述下列功能的实现方式,再接下来的章节中深入源码,扒出其具体实现

  1. 如何做到零 gc?
  2. 数据结构
  3. 动态索引
  4. 缓存失效

03. 如何做到 0 GC?

这个库之所以做到了 0 gc,是因为设定了很巧妙的数据结构,这个数据结构有以下的特点:

  • 无论存多少数据,都只会存在 512 个指针
  • 所有的具体数据的存储空间,都是预先就分配好的,而不是动态分配的
  • 使用了一些黑科技,比如强制类型转换以及结构体对齐等技术,将所有的动态数据都管理在预先分配好的连续空间内

04. 数据结构

可以将这个数据结构大体上抽象成一个哈希表。即你会看到哈希函数以及对应的数组空间。但是他和传统的哈希表是有区别的,主要有以下几点:

  • 线程安全,支持高并发,内存空间高度优化:
  1. 为了做到支持高并发以及线程安全,并且保证内存空间的可用性,freecache 实际上划分出了 256 个独立数组空间用来存储对应的底层数据。
  2. 这样在并发访问的时候,通过对 key 进行分片,使得请求只会打到一个数组空间上,即只会对这 256 个空间中的一个空间加锁,这样就大大降低了资源争抢。
  • 数据的组织方式与传统哈希表不同:
  1. 传统的哈希表只在数组空间中存 value。而 freecache 则不同,他将 key,value 全部存在了数组空间中。
  2. 传统哈希表的数组是稀疏的。freecache 数据并不是稀疏的,而是连续的,即新的值会不断 append 到最后。
  3. 传统哈希表使用 hash func 对 key 取索引,索引到稀疏数组中的位置。而 freecache 则通过维护了一个叫“slot(插槽)”的数据结构,通过对 key 进行 hash func,先拿到对应的 slot,然后 slot 中维护着一个索引,可以定位到具体的数据在数组中的位置。
  4. 解决哈希冲突的方式不同。当遇到哈希冲突的时候,哈希表需要对底层的稀疏数组进行扩容,会导致可用性大大降低。而 freecache 则是只需要对“slot”的指针数组进行扩容,而无需改变底层数组。因为 slot 指针数组的大小远小于底层数组,所以扩容的成本是非常非常低的。
  • 为了实现缓存淘汰以及定时失效,它的数组空间在逻辑上是一个环状的。这么做有以下原因
  1. 数组存的数据逻辑上是连续的,而非稀疏数组。充分利用了CPU对数组读取的缓存优化
  2. 通过使用了一系列的首尾计算方式,是足以保证读取和存储在首尾的连续性。比如读数据的时候读到结尾如果还没读完,会跳转到头部继续往下读。
  3. 能以 O(1) 的时间复杂度完成新数据对旧数据的淘汰。我们假设如果数组在逻辑上不是环形的,那么当数组写满的时候再写入新的数据,就需要把数组头部的数据删除掉,然后再把之后的数据统统向左移动删除数据的长度,然后再从最右端写入新的数据。反之,如果数组是环形的,只需要在数组头部把旧数据覆盖即可
  4. 通过一些算法手段,可以实现一个很 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 中定位到具体的 slotkeyLen   uint16 // used to compare a keyreserved uint32
}// 每个分片的数据结构
type segment struct {rb            RingBuf // 环形数组segId         inthitCount      int64missCount     int64entryCount    int64totalCount    int64      // 之后计算 lru 的时候会用到,用于衡量一个数据是否是热点数据totalTime     int64      // 之后计算 lru 的时候会用到,用于衡量一个数据是否是热点数据totalEvacuate int64      // used for debugtotalExpired  int64      // used for debugoverwrites    int64      // used for debugvacuumLen     int64      // 环形数组可用容量,主要用于环形数组首尾相接的算法slotLens      [256]int32 // 每个 slotId 的长度的数组slotCap       int32      // 每个 slotId 的容量slotsData     []entryPtr // 存储 slots 的切片,根据 hash16 进行顺序排列。相同的 hash16 拥有相同的 slotId
}

06. 缓存失效

缓存失效主要包括两种:

  1. 基于过期时间的失效
  2. 基于最近最少使用的失效

这两种失效都有缺陷。

首先它是一种被动失效,而不是通过一个额外的线程定期check。而是每次 set 新的值的时候,如果发现空间不够用了,他才会尝试从环形数组的头端进行check。如果发现当前check的数据过期了,或者使用频率过低,就会将记录有效数据的头指针进行偏移,即相当于“失效”。如果检查多次都没能找到需要失效的数据,那么他会将这些检查过的数据转移到尾部,并强制当前的头部的数据失效。

这种失效是不可靠的。比如一个数据,如果在环形数组的中间,那么即便它过期了,也很难被 check 到。并且存在一定的失误概率,即将一个热点数据给失效了。

go io.reader 多次读取_Go 语言进阶:freecache 源码学习(1)相关推荐

  1. go io.reader 多次读取_Go 经典入门系列 24:Select

    点击上方蓝色"Go语言中文网"关注,每天一起学 Go 欢迎来到 Golang 系列教程[1]的第 24 篇. 什么是 select? select 语句用于在多个发送/接收信道操作 ...

  2. [Go语言]从Docker源码学习Go——init()方法和identifier首字母大小写区分

    init()方法 如果想在一个go文件里,进行一些初始化的工作,可以把代码放到init()方法中. init()方法先被执行. func init() { // initialization of p ...

  3. Go 语言 bytes.Buffer 源码详解之1

    转载地址:Go 语言 bytes.Buffer 源码详解之1 - lifelmy的博客 前言 前面一篇文章 Go语言 strings.Reader 源码详解,我们对 strings 包中的 Reade ...

  4. c调用易语言串口,易语言串口API源码

    易语言串口API源码系统结构:ReadCommPure,BuildCommDCB,CreateFilea,关闭句柄a,SetCommState,ReadFileA,GetCommState,Write ...

  5. 字符串固定长度 易语言_易语言字符串操作源码

    易语言字符串操作源码 系统结构:字符串_取长度,字符串_取中间,字符串_取左边,字符串_取右边,字符串_替换,到宽字符,到多字节,取文本数据地址,取字节集数据地址,MultiByteToWideCha ...

  6. 易语言修改虚拟机硬盘id_易语言本地虚拟机源码

    易语言本地虚拟机源码 系统结构:显示工具路径,读入未用分区,获取分区位置,设置虚拟机分区,读入虚拟分区,操作并显示日志,处理显示错误提示,取驱动器文本列表,写配置目录,处理结果文件,格式化时间,取Do ...

  7. 易语言模拟器中控源码 全新手游模拟器通用中控源码, 适用于各种游戏, 源码现成的只需要更换游戏就可以用哦

    易语言模拟器中控源码 全新手游模拟器通用中控源码, 适用于各种游戏, 源码现成的只需要更换游戏就可以用哦, 带修改教程,带讲解说明, 简单易懂不需要别人指导在家可以自学. 降低新手编写多线程中控的门槛 ...

  8. 易语言 普通填表 html5,易语言网页填表源码

    易语言网页填表源码系统结构:passport_tianya,passport_xinlang,passport_baidu,Automatic_modification,Insert_text,Loa ...

  9. c语言字符动画源码下载,C语言动画程序源码.docx

    C语言动画程序源码 C语言动画程序#include #include #include #include #define pi 3.1415926535 double ca3mm1(double m1 ...

最新文章

  1. php开发编程中心,Php编程
  2. Homebrew学习(六)之替换及重置homebrew、Homebred Core、Homebrew cask默认源
  3. JZOJ 5400. 【NOIP2017提高A组模拟10.7】Repulsed
  4. 环状进度条progress bar circle
  5. jquery lazy load
  6. 美国新WiFi技术功耗低于蓝牙LE和Zigbee
  7. mysql 表与表之间的条件比对_《MySQL数据库》关联查询
  8. 「CSS」常见的清除浮动方法
  9. clock函数,计算程序运行时间
  10. Vancouver wechat
  11. Qt平台下使用QJson解析和构建JSON字符串
  12. android 计步器acc,基于加速度的门限检测计步算法设计
  13. Linux性能基础:CPU、内存、磁盘等概述
  14. 中级软件设计师知识点总结
  15. 测试人必备的工具(常用的测试平台)
  16. 51学习记录基于51单片机的简单音乐盒
  17. 写口算用计算机作文600字,口算比赛作文600字
  18. 北京精雕现状_6秒精密加工,日本走下神坛,北京精雕也做了一个!
  19. 对于阿里云的oss上传本地图片的相关注意点
  20. 关于共享单车乱摆放问题的调研报告

热门文章

  1. Nature子刊评论:2020年后,微生物组将如何发展?
  2. Nature子刊:细菌和古菌从域到种的完整分类
  3. Cell子刊:中科院遗传发育所周俭民组发现特异靶向病原菌致病力的植物天然产物并阐明作用机制
  4. 肠·道 | 朱元方:产检消毒恐误伤菌脉,6大举措则促菌脉相承
  5. 玩转花式截图、录屏——FastStoneCapture使用指南
  6. R包reshape2,轻松实现长、宽数据表格转换
  7. R语言ggplot2可视化:使用geom_line函数将dataframe中数据可视化为时间序列(或折线图)(Time Series Plot From a Data Frame)、添加标题、副标题
  8. R语言ggplot2可视化分面图(faceting)、设置每个分面的标题在右侧(right side)、并在右侧分面图的外侧添加整图的标题信息(facet title)
  9. R语言使用ggplot2包的快速可视化函数qplot绘制散点图(设置每个数据点的文本标签信息)实战
  10. Python时间转换函数:时间转化为时间戳、时间戳转化为时间、当前日期、当前时间、星期几、前面或者后面多少天、年、月、日等