当你不断往内存添加数据时,大规模的数据往往对于缓存的效率有很大影响。同时,由大量数据操作引发的垃圾回收(GC)也会对拉低性能,甚至引起不可预料的问题。BigCache,这个能够在内存支持千兆级别数据的缓存,对于GC进行了优化,极大地提高了性能。

简介

Big cache ,是 allegro 在 Github 上开源的Go语言缓存库,项目仓库位于 ,目前版本为 2.2.2。BigCache 是在内存上的缓存,其使用了Go语言1.5所带来的特性,利用了 map[uint64]uint32 类型来避免GC带来的性能损耗,使得其在存储大量数据的同时保持高性能。

安装

BigCache 目前需要 Go 1.12 或以上,使用 go get 直接安装:

 go get -u github.com/allegro/bigcache  
示例

BigCache的基本使用和其他缓存库基本相同,一个简单的使用例子如下:

 import "github.com/allegro/bigcache"

cache, _ := bigcache.NewBigCache(bigcache.Default config (10 * time.Minute))

cache.Set("my-unique-key", [] byte ("value"))

entry, _ := cache.Get("my-unique-key")
fmt.Println(string(entry))  

首先进行了BigCache的实例化,使用默认配置,设置可清除时间为10分钟。然后,使用 Set 接口进行了一个key-value形式的缓存项的存储,再使用 Get 接口进行读取,最后输出。

对于不同的应用场景,我们可以进行更为细化的配置:

 import (
	"log"

	"github.com/allegro/bigcache"
)

 Config  := bigcache.Config {
		Shards: 1024,
    LifeWindow: 10 * time.Minute,
		CleanWindow: 5 * time.Minute,
		MaxEntriesInWindow: 1000 * 10 * 60,
		MaxEntrySize: 500,
		Verbose: true,
		HardMaxCacheSize: 8192,
		OnRemove: nil,
		OnRemoveWithReason: nil,
	}

cache, initErr := bigcache.NewBigCache(config)
if initErr != nil {
	log.Fatal(initErr)
}

cache.Set("my-unique-key", []byte("value"))

if entry, err := cache.Get("my-unique-key"); err == nil {
	fmt.Println(string(entry))
}  

具体的配置项包括:

  • Shards:切片数,必须为2的整数幂;
  • LifeWindow:数据可被清除(驱逐)的时间;
  • CleanWindow:扫描清除的时间间隔;
  • MaxEntriesInWindow:初始的数据规模;
  • MaxEntrySize:初始的数据字节大小;
  • Verbose:是否输出运行信息;
  • HardMaxCacheSize:硬性的最大规模;
  • OnRemove:清除数据的回调;
  • OnRemoveWithReason:包括清除原因的清除回调。

在使用时可以根据自身的运行环境进行配置,得到更高的性能效率。

性能测试

BigCache 进行了与 freecache 和 原生map 的性能测试比较,测试结果如下:

 go version
go version go1.13 linux/amd64

go test -bench=. -benchmem -benchtime=4s ./... -timeout 30m
goos: linux
goarch: amd64
pkg: github.com/allegro/bigcache/v2/caches_bench
BenchmarkMapSet-8                     	12999889	       376 ns/op	     199 B/op	       3 allocs/op
BenchmarkConcurrentMapSet-8           	 4355726	      1275 ns/op	     337 B/op	       8 allocs/op
BenchmarkFreeCacheSet-8               	11068976	       703 ns/op	     328 B/op	       2 allocs/op
BenchmarkBigCacheSet-8                	10183717	       478 ns/op	     304 B/op	       2 allocs/op
BenchmarkMapGet-8                     	16536015	       324 ns/op	      23 B/op	       1 allocs/op
BenchmarkConcurrentMapGet-8           	13165708	       401 ns/op	      24 B/op	       2 allocs/op
BenchmarkFreeCacheGet-8               	10137682	       690 ns/op	     136 B/op	       2 allocs/op
BenchmarkBigCacheGet-8                	11423854	       450 ns/op	     152 B/op	       4 allocs/op
BenchmarkBigCacheSetParallel-8        	34233472	       148 ns/op	     317 B/op	       3 allocs/op
BenchmarkFreeCacheSetParallel-8       	34222654	       268 ns/op	     350 B/op	       3 allocs/op
BenchmarkConcurrentMapSetParallel-8   	19635688	       240 ns/op	     200 B/op	       6 allocs/op
BenchmarkBigCacheGetParallel-8        	60547064	        86.1 ns/op	     152 B/op	       4 allocs/op
BenchmarkFreeCacheGetParallel-8       	50701280	       147 ns/op	     136 B/op	       3 allocs/op
BenchmarkConcurrentMapGetParallel-8   	27353288	       175 ns/op	      24 B/op	       2 allocs/op
PASS
ok  	github.com/allegro/bigcache/v2/caches_bench	256.257s  

BigCache在读和写都比freecache更快。

另外,BigCache还对于GC停止时间进行了测试:

 go version
go version go1.13 linux/amd64

go run caches_gc_overhead_comparison.go

Number of entries:  20000000
GC pause for bigcache:  1.506077ms
GC pause for freecache:  5.594416ms
GC pause for map:  9.347015ms  
 go version
go version go1.13 linux/arm64

go run caches_gc_overhead_comparison.go
Number of entries:  20000000
GC pause for bigcache:  22.382827ms
GC pause for freecache:  41.264651ms
GC pause for map:  72.236853ms  

可以看到,BigCache拥有最短的GC停止时间。

总结

BigCache充分利用了Go语言的GC策略,实现了在存储大量数据的同时保持高速的读写性能,对于大规模数据的单节点缓存场景有很大的应用价值。

BigCache的代码中包含了大量的并发、锁、内存操作和信道消息相关逻辑,对于对底层操作感兴趣的开发者而言值得研究学习,其特殊的GC机制也有进一步的研究价值。