Golang的本地缓存工具Fastcache - 文献综述
1.简介
Fastcache是一个用Go语言实现的快速、线程安全的内存缓存组件,用于缓存大量对象。
它具有:
- 具有可扩展性能的快速多核 CPU。
- 线程安全。并发 goroutine 可以读取和写入单个缓存实例。
- Fast Cache 旨在存储大量条目而无需 GC 开销。
- 当达到创建时设置的最大缓存大小时,Fastcache 会自动逐出旧条目。
- 简单的API
- 简单的源代码。
- 缓存可以保存到文件或从文件加载。
- 可与 Google App Engine 配合使用。
2。性能
有多快? :
GOMAXPROCS=4 go test github.com/VictoriaMetrics/fastcache -bench='Set|Get' -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/VictoriaMetrics/fastcache
BenchmarkBigCacheSet-4 2000 10566656 ns/op 6.20 MB/s 4660369 B/op 6 allocs/op
BenchmarkBigCacheGet-4 2000 6902694 ns/op 9.49 MB/s 684169 B/op 131076 allocs/op
BenchmarkBigCacheSetGet-4 1000 17579118 ns/op 7.46 MB/s 5046744 B/op 131083 allocs/op
BenchmarkCacheSet-4 5000 3808874 ns/op 17.21 MB/s 1142 B/op 2 allocs/op
BenchmarkCacheGet-4 5000 3293849 ns/op 19.90 MB/s 1140 B/op 2 allocs/op
BenchmarkCacheSetGet-4 2000 8456061 ns/op 15.50 MB/s 2857 B/op 5 allocs/op
BenchmarkStdMapSet-4 2000 10559382 ns/op 6.21 MB/s 268413 B/op 65537 allocs/op
BenchmarkStdMapGet-4 5000 2687404 ns/op 24.39 MB/s 2558 B/op 13 allocs/op
BenchmarkStdMapSetGet-4 100 154641257 ns/op 0.85 MB/s 387405 B/op 65558 allocs/op
BenchmarkSyncMapSet-4 500 24703219 ns/op 2.65 MB/s 3426543 B/op 262411 allocs/op
BenchmarkSyncMapGet-4 5000 2265892 ns/op 28.92 MB/s 2545 B/op 79 allocs/op
BenchmarkSyncMapSetGet-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。源码
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) *Cache
c := 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都有一个读写块来处理并发。
- 与Synchronization.Map相比,原理上并没有什么神秘之处。将数据分散到512个桶中相当于将并发量减少到原始大小的1/512。 ?第0个chunk被保存,gen字段递增,表示是新生代
- chunk是上面提到的mmap分配的64KB的块
- key+value数据会依次放入chunk中。并且注意数组中的下标
- 使用完一个chunk的空间后,通过getChunk()给出另一个64KB的块。直到块达到用户指定的上限。
- m map[uint64]uint64:存储每个哈希码对应的块中的偏移量。
- idx uint64:记录下一次插入片段的位置。插入完成后,跳转到数据的最后一位。
- gen uint64:当所有位都填满时,gen 的值加 1,并从 block 0 开始淘汰旧数据。桶的数量,会导致某些桶的数据被淘汰,而其他桶却剩下大量空间。不过,这只是一个假设,并没有数据证明这一现象。 ?由于恢复删除数据
- key+value序列化的方法非常简单,因此依次存储以下内容:
- 2字节密钥长度
- 2字节值长度 ❀♺❀值内容
- 写一块时加写锁
- 通过桶的idx字段找到插入位置,然后按照上面的序列化方法复制数据
- 插入完成后,'Offset获取位置,并获取键的哈希码作为键,使用以位为单位的偏移量作为值并将其写入字段 m
- value 的映射中 这里还有一个细节:value 是一个 64-位 uint64,值的低 40 位存储偏移量,高值 24 位存储生成信息。 ? 确定签名后key的位置
- 比较key的内容是否相等
- 最终返回值
4.3.4删除过程❀只删除map中的key,并且棋子中的相应位置仅移除卡上的钥匙。可以等到下一次倒带时再清理。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。