Code前端首页关于Code前端联系我们

Golang的本地缓存工具Fastcache - 文献综述

terry 2年前 (2023-09-24) 阅读数 64 #后端开发

1.简介

Fastcache是​​一个用Go语言实现的快速、线程安全的内存缓存组件,用于缓存大量对象。

它具有:

  1. 具有可扩展性能的快速多核 CPU。
  2. 线程安全。并发 goroutine 可以读取和写入单个缓存实例。
  3. Fast Cache 旨在存储大量条目而无需 GC 开销。
  4. 当达到创建时设置的最大缓存大小时,Fastcache 会自动逐出旧条目。
  5. 简单的API
  6. 简单的源代码。
  7. 缓存可以保存到文件或从文件加载。
  8. 可与 Google App Engine 配合使用。

2。性能

有多快? :

GOMAXPROCS=4 go test github.com/VictoriaMetrics/fastcache -bench='Set|Get' -benchtime=10sgoos: linuxgoarch: amd64pkg: github.com/VictoriaMetrics/fastcacheBenchmarkBigCacheSet-4            2000    10566656 ns/op     6.20 MB/s   4660369 B/op         6 allocs/opBenchmarkBigCacheGet-4            2000     6902694 ns/op     9.49 MB/s    684169 B/op    131076 allocs/opBenchmarkBigCacheSetGet-4         1000    17579118 ns/op     7.46 MB/s   5046744 B/op    131083 allocs/opBenchmarkCacheSet-4               5000     3808874 ns/op    17.21 MB/s      1142 B/op         2 allocs/opBenchmarkCacheGet-4               5000     3293849 ns/op    19.90 MB/s      1140 B/op         2 allocs/opBenchmarkCacheSetGet-4            2000     8456061 ns/op    15.50 MB/s      2857 B/op         5 allocs/opBenchmarkStdMapSet-4              2000    10559382 ns/op     6.21 MB/s    268413 B/op     65537 allocs/opBenchmarkStdMapGet-4              5000     2687404 ns/op    24.39 MB/s      2558 B/op        13 allocs/opBenchmarkStdMapSetGet-4            100   154641257 ns/op     0.85 MB/s    387405 B/op     65558 allocs/opBenchmarkSyncMapSet-4              500    24703219 ns/op     2.65 MB/s   3426543 B/op    262411 allocs/opBenchmarkSyncMapGet-4             5000     2265892 ns/op    28.92 MB/s      2545 B/op        79 allocs/opBenchmarkSyncMapSetGet-4          1000    14595535 ns/op     8.98 MB/s   3417190 B/op    262277 allocs/op

将 Fastcache 性能与 BigCache、标准 Go 映射和 Sync.map 进行比较。可以看出FashCache的写入效率是最快的。换句话说,Fastcache 比 Standard Map 快 2.77 倍,比 Sync.Map 库快 6.49 倍,比 BigCache 快 2.78 倍。

3. 局限性

当然,Fastcache 并不完美,也有缺陷。您可以根据以下限制决定是否引入自己的业务环境:

  • key和value都只能是[]byte类型,不能是的话自己序列化
  • 超过64KB的大条目必须缓存给。 SetBig
  • 没有缓存过期。仅当缓存大小溢出时,条目才会从缓存中逐出。输入要存储在缓存到期值中的到期日期。

4。源码

Golang本地缓存利器fastcache一文学透4.1 使用mmap分配内存

malloc_mmap.go使用unix.Mmap()分配内存:

  • 内存映射方法可以直接应用到操作系统中。区域不由 GC 管理。所以无论你在这块内存中缓存了多少数据,性能都不会受到GC扫描的影响。
  • 每次使用mmap申请内存,就是申请1024*64KB = 64MB的内存。
每64KB称为一个chunk所有的chunk放在一个队列中当队列中所有的chunk都用完后,再申请64MB
  • Chunk 管理:
var (      freeChunks     []*[chunkSize]byte  //相当于一个队列,保存了所有未使用的chunk      freeChunksLock sync.Mutex  //chunk的锁)

可以通过 func getChunk() []byte 函数获取 64KB 的 chunk。如果freeChunks中没有chunk,则通过mmap申请64MB。

块的返回func putChunk(Chunk []byte)函数将有效块返回到freeChunks队列。

绕过GC可以带来性能优势,但是这里分配的内存在进程重新启动之前永远不会被释放。 4.2 Cache类实现

fastcache.go是Fastcache的主要代码。 4.2.1 缓存对象的结构

type Cache struct {      buckets [bucketsCount]bucket
      bigStats BigStats}
  • bucketsCount 该常数值为512,即缓存对象中分布了512个桶。
  • bigStats 用于内部监控和报告

4.2.2 创建新的缓存对象

// func New(maxBytes int) *Cachec := New(1024*1024*32)  //cache的最小容量是32MB

新源码如下:

func New(maxBytes int) *Cache {      if maxBytes <= 0 {            panic(fmt.Errorf("maxBytes must be greater than 0; got %d", maxBytes))  }      var c Cache      maxBucketBytes := uint64((maxBytes + bucketsCount - 1) / bucketsCount)      for i := range c.buckets[:] {            c.buckets[i].Init(maxBucketBytes)  }      return &c}
  • maxBytes 在 512 之后先向上划分❙,然后划分❝ 为 512部分,假设应用内存为512MB,则每次复制1MB。即每个存储桶 1 MB 内存。
  • 分为512个桶,每个桶单独初始化

4.2.3 SET方法

func (c *Cache) Set(k, v []byte) {      h := xxhash.Sum64(k)      idx := h % bucketsCount      c.buckets[idx].Set(k, v, h)}

很简单:为key计算一个哈希值,然后对哈希值取模,传递给要处理的特定存储桶对象。 4.3 Bucket类实现

4.3.1 Bucket结构

type bucket struct {      mu sync.RWMutex
      // chunks is a ring buffer with encoded (k, v) pairs.      // It consists of 64KB chunks.      chunks [][]byte
      // m maps hash(k) to idx of (k, v) pair in chunks.      m map[uint64]uint64
      // idx points to chunks for writing the next (k, v) pair.      idx uint64
      // gen is the generation of chunks.      gen uint64
      getCalls    uint64  // 以下都是用于统计的变量      setCalls    uint64      misses      uint64      collisions  uint64      corruptions uint64}
  • musync.RWMutex:每个bucket都有一个读写块来处理并发。
  1. 与Synchronization.Map相比,原理上并没有什么神秘之处。将数据分散到512个桶中相当于将并发量减少到原始大小的1/512。 ?第0个chunk被保存,gen字段递增,表示是新生代
  2. chunk是上面提到的mmap分配的64KB的块
  3. key+value数据会依次放入chunk中。并且注意数组中的下标
  4. 使用完一个chunk的空间后,通过getChunk()给出另一个64KB的块。直到块达到用户指定的上限。
  • m map[uint64]uint64:存储每个哈希码对应的块中的偏移量。
  • idx uint64:记录下一次插入片段的位置。插入完成后,跳转到数据的最后一位。
  • gen uint64:当所有位都填满时,gen 的值加 1,并从 block 0 开始淘汰旧数据。桶的数量,会导致某些桶的数据被淘汰,而其他桶却剩下大量空间。不过,这只是一个假设,并没有数据证明这一现象。 ?由于恢复删除数据
    • key+value序列化的方法非常简单,因此依次存储以下内容:
  1. 2字节密钥长度
  2. 2字节值长度 ❀♺❀值内容
  • 写一块时加写锁
  • 通过桶的idx字段找到插入位置,然后按照上面的序列化方法复制数据
  • 插入完成后,'Offset获取位置,并获取键的哈希码作为键,使用以位为单位的偏移量作为值并将其写入字段 m
  1. value 的映射中 这里还有一个细节:value 是一个 64-位 uint64,值的低 40 位存储偏移量,高值 24 位存储生成信息。 ? 确定签名后key的位置
  2. 比较key的内容是否相等
  3. 最终返回值
  4. 4.3.4删除过程❀只删除map中的key,并且棋子中的相应位置仅移除卡上的钥匙。可以等到下一次倒带时再清理。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门