Golang 高性能无 GC 的缓存库 bigcache 是怎么实现的?(Golang 高性能无GC缓存库bigcache的实现原理揭秘)
原创
一、引言
在Go语言中,垃圾回收(GC)是一个重要的特性,但也或许成为高并发应用中的性能瓶颈。为了解决这个问题,许多开发者寻求使用无GC的缓存库来减成本时间应用的性能。bigcache就是这样一个高性能、无GC的缓存库。本文将深入剖析bigcache的实现原理,帮助读者更好地懂得和使用这个库。
二、bigcache的设计理念
bigcache的设计理念重点有以下几点:
- 避免使用Go的内置map,由于内置map在扩容时会触发GC。
- 使用固定大小的数组来存储缓存数据,避免内存碎片。
- 使用双缓冲机制,缩减锁的竞争。
- 使用自定义的哈希函数,减成本时间哈希效能。
三、bigcache的核心组件
bigcache重点由以下几个核心组件构成:
- Segment:缓存分段,用于存储缓存数据。
- Shards:分片,用于并发访问。
- Hash:哈希函数,用于计算键的哈希值。
- LRU:最近最少使用算法,用于淘汰旧的缓存项。
四、Segment的实现
Segment是bigcache中用于存储缓存数据的核心组件。每个Segment包含一个固定大小的数组,数组的每个元素是一个缓存项。
type Segment struct {
items []item
size int
capacity int
CAS int64
lock sync.RWMutex
}
type item struct {
key string
value []byte
expiration int64
}
Segment通过数组索引来迅捷定位缓存项,从而避免了内置map的扩容问题。同时,使用CAS(Compare-And-Swap)机制来确保并发访问时的数据一致性。
五、Shards的实现
Shards是bigcache用于并发访问的组件。bigcache将缓存分为多个分片,每个分片包含一个Segment。这样,多个goroutine可以同时访问不同的分片,从而缩减锁的竞争。
type Shards struct {
shards []*Segment
capacity int
}
func NewShards(capacity int) *Shards {
shards := make([]*Segment, 0, capacity)
for i := 0; i < capacity; i++ {
shards = append(shards, NewSegment())
}
return &Shards{shards: shards}
}
Shards通过哈希函数计算键的哈希值,然后结合哈希值选择对应的分片进行操作。
六、哈希函数的实现
bigcache使用自定义的哈希函数来计算键的哈希值。这个哈希函数基于FNV-1a算法,具有较高的哈希效能。
func hash(key string) uint64 {
hash := fnv.New64a()
hash.Write([]byte(key))
return hash.Sum64()
}
FNV-1a算法是一种迅捷、稳定的哈希算法,适用于分布式缓存等场景。
七、LRU淘汰算法的实现
bigcache使用最近最少使用(LRU)算法来淘汰旧的缓存项。LRU算法通过维护一个双向链表来实现,链表的头部是最近访问的缓存项,链表的尾部是最久未访问的缓存项。
type LRU struct {
capacity int
head *node
tail *node
items map[string]*node
}
type node struct {
key string
value []byte
prev *node
next *node
}
当缓存大致有上限时,bigcache会删除链表尾部的缓存项,为新缓存项腾出空间。
八、总结
bigcache通过避免使用内置map、使用固定大小的数组、双缓冲机制、自定义哈希函数和LRU淘汰算法等技术,实现了高性能、无GC的缓存。这些设计理念令bigcache在高并发场景下具有优异的性能表现。懂得和掌握bigcache的实现原理,有助于我们在实际项目中更好地使用这个库,减成本时间应用性能。