深入理解Freecache
1. 简介
1.1 使用背景
由于项目要求,需要利用缓存技术来降低API延迟,所以选择freecache来作为本地缓存实现。这个开源框架是纯Golang写的,本人出于好奇、安全性考察等因素,就仔细的研究了一下代码实现。项目的代码量不多,但是设计的很巧妙,值的好好研究和学习。
1.2 freecache介绍
freecache是Golang版的本地缓存库,从github项目介绍看,freecache具有以下的优势:
- 能存储数亿条记录(entry) 。
- 零GC开销。
- 高并发线程安全访问。
- 纯Golang代码实现。
- 支持记录(entry)过期。
- 接近LRU的替换算法。
- 严格限制内存的使用。
- 提供一个测试用的服务器,支持一些基本 Redis 命令。
- 支持迭代器。
1.3 freecache与Golang Map对比
github官网给出的数据来看,set操作的性能是Golang内置Map的两倍,get操作是Golang内置Map的1/2。不过这个测试数据是单线程基准测出来的,在高并发的情况下,对比单锁保护的内置Map来说,性能会快好几倍。
1.4 freecache版本
本篇文章的内容都是基于freecache v1.1.1版本的代码来进行介绍的。
2. freecache内部设计
2.1 freecache整体架构设计
可以通过一下get(key)操作,来了解freecahe的架构设计。

- freecache将缓存划分为256个segment,对于一个key的操作,freecache通过hash方法(xxhash)计算得到一个64位的hashValue,并取低8位作为segId,定位到具体的segment,并对segment加锁。由于只对segment加锁,不同segment之间可以并发进行key操作,所以freecache支持高并发线程安全访问。
const (
// segmentCount represents the number of segments within a freecache instance.
segmentCount = 256
// segmentAndOpVal is bitwise AND applied to the hashVal to find the segment id.
segmentAndOpVal = 255
......
)
// Cache is a freecache instance.
type Cache struct {
locks [segmentCount]sync.Mutex // 每个segment都有自己的同步控制锁
segments [segmentCount]segment // 缓存划分为256个segment
}
// xxhash算法,算出64位哈希值
func hashFunc(data []byte) uint64 {
return xxhash.Sum64(data)
}
......
// Get returns the value or not found error.
func (cache *Cache) Get(key []byte) (value []byte, err error) {
// 1. 算出key的64位哈希值
hashVal := hashFunc(key)
// 2. 取低8位,得到segId
segID := hashVal & segmentAndOpVal
// 找到对应的segment,只对segment加锁
// 同个segment的操作是串行进行,不同segment的操作是并行进行的
cache.locks[segID].Lock()
value, _, err = cache.segments[segID].get(key, nil, hashVal, false)
cache.locks[segID].Unlock()
return
}
......
- segment底层实际上是由两个切片组成的复杂数据结构,其中一个切片用来实现环形缓冲区RingBuf,存储了所有的entry (entry=24 byte header + key + value)。另一个切片则是用于查找entry的索引切片slotData,slotData被逻辑上切分为256个slot,每个slot上的entry索引都是按照hash16有序排列的。可以看出,不管freecache缓存了多少数据,底层永远都只会有512个指针,所以freecache的对GC开销几乎为零。
// segment.go文件
type segment struct {
// 环形缓冲区RingBuf,由一个固定容量的切片实现
rb RingBuf
segId int
_ uint32
missCount int64
hitCount int64
entryCount int64
totalCount int64
totalTime int64
timer Timer
totalEvacuate int64
totalExpired int64
overwrites <
本文详细探讨了Golang本地缓存库freecache的设计与实现,包括其优势、与Golang Map的性能对比,以及内部的set操作、过期与删除策略、entry索引等关键部分。freecache采用接近LRU的置换算法,支持高并发线程安全访问,并具有零GC开销,但存在不能动态扩容和非完全LRU等问题。
最低0.47元/天 解锁文章
1064

被折叠的 条评论
为什么被折叠?



