学习VictoriaMetrics源码的时候发现,VictoriaMetrics的缓存部分,使用了同一产品下的fastcache。下面分享阅读fastcache源码的的结论:
fastcache是一个用go语言实现的,很快的,线程安全的,内存缓存的,用于大量对象缓存的组件。
它的特点是:
作者valyala是fasthttp和VictoriaMetrics等作品的主要开发者。valyala大神有极其强悍的工程能力,很多看来已经很简单的成熟组件被他又一次妙手生花,YYDS!
究竟有多快呢?作者做了一个对比:(这里主要看set操作)
换个角度看:
快得我都不知道说啥好了……
当然也不是快就完美了,也是有些限制的。要根据这些限制来确定fastcache是否适合引入你的业务环境中:
malloc_mmap.go中使用了unix.Mmap()来分配内存:
var (
freeChunks []*[chunkSize]byte //相当于一个队列,保存了所有未使用的chunk
freeChunksLock sync.Mutex //chunk的锁
)
可以通过 func getChunk() []byte
函数获取一个64KB的块。如果freeChunks中没有chunk了,就再通过mmap申请64MB。
func putChunk(chunk []byte)
函数把有效的chunk放回freeChunks队列。绕过GC能带来性能上的好处,但是这里分配的内存再也不会被释放,直到进程重启。
fastcache.go中是fastcache的主要代码。
type Cache struct {
buckets [bucketsCount]bucket
bigStats BigStats
}
// func New(maxBytes int) *Cache
c := New(1024*1024*32) //cache的最小容量是32MB
New的源码如下:
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
}
func (c *Cache) Set(k, v []byte) {
h := xxhash.Sum64(k)
idx := h % bucketsCount
c.buckets[idx].Set(k, v, h)
}
非常简单:对key计算一个hash值,然后对hash值取模,转到具体的bucket对象里面去处理。
xxhash库用汇编实现,是目前最快的hashcode计算的库
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
}
mu sync.RWMutex
: 每个bucket有一个读写锁来处理并发。chunks [][]byte
: 这个是存储数据的chunk的数组getChunk()
再申请64KB的块。直到块达到用户规定的上限。m map[uint64]uint64
: 这里存储每个hashcode对应的chunk中的偏移量。idx uint64
: 这里记录下次插入chunk的位置,插入完成后跳转到数据的末位。gen uint64
: 当所有的chunks都写满以后,gen的值加1,从第0块开始淘汰旧数据。这里有个明显的缺点:假设hashcode都分布在较少的几个bucket中,那么就导致某几个bucket的数据频繁淘汰,而其他的bucket还剩挺多空间。不过,这只是假设,并未有数据证明会有这种现象。
源码太多,此处直接贴结论:
搞清楚了Set,Get就更简单了:
del仅删除map中的key,而chunks中对应的位置只能等到下次回绕才能清理。
删除的动作是滞后的,因此fastcache不适合删除很多的业务场景。
fastcache为什么快,因为用了这些手段:
希望对你有用,have fun :-)