golang缓存框架学习
golang主流缓存组件
本地缓存场景中,一般使用map或者缓存框架,为了线程安全,会使用sync.Map或线程安全的缓存框架。缓存组件主要需要解决的几个问题:
- 需要较高的读写性能+命中率
- 支持按写入时间过期
- 支持淘汰策略
- 解决GC问题,大量对象写入会引起STW扫描标记时间过长,CPU毛刺严重
需要考虑数据类型对GC的影响,如果缓存key和value都是基本类型(不包含指针,string类型底层也是指针+Len+Cap),gc便不会进行扫描,因此在进行缓存框架的使用时,需要根据业务数据量的数量级,考虑数据类型对GC的影响,选定合适的缓存框架。常见的缓存框架分为两类:
- 零GC: 例如freecache,bigcache,底层数据基于ringbuf,减小指针个数
- 有GC:直接基于Map实现的缓存框架
常见开源本地缓存组件汇总:

解决GC问题的方案:
- 无GC:分配堆外内存(Mmap)
- 避免GC:map非指针优化(map[uint64]unit32)或者采用slice实现一套无指针的map
- 避免GC:数据存入[]byte slice(循环队列)
解决性能问题的方案:
- 数据分片(降低锁粒度)
freecache
实现
freecache中通过segment对数据进行分片,freecache内部包含256个segment,每个segment维护一把互斥锁,每条key/value数据进来后会先根据key计算hash值,然后根据hash值决定当前的这条数据落入到哪个segment中。
对于每个segment,由索引和数据两部分构成。
索引: 索引常用的最简单的方式是采用map[uint64]unit32来维护,但freecache并没有采用这种方式,而是通过slice来底层实现一套无指针的map,来避免GC扫描。
数据: 数据采用缓冲区ringBuf来循环使用,底层采用[]byte进行封装实现。数据进入ringBuf后,记录写入的位置index作为索引,读取时首先读取数据header信息,再读取key/value数据。
数据传递过程: freecahe -> segment -> slot,ringbuffer
框架结构

总结
freecache利用数据分片减小锁的粒度,在存储数据时采用自建的map减少指针避免GC,数据存储时采用预先分配内存然后循环使用。
bigcache
实现
与freecache类似,数据进行分片,每个bigcache包含2^n个cacheShard(默认1024),每个cacheShard对象维护一把sync.RWLock锁,数据分散到不同的cacheShard中。
每个cacheShard由索引和数据两部分构成。
索引: 索引采用map[uint64]uint32存储,索引中存储的是该条数据在entryBuffer写入的位置pos。
数据: 数据采用entry([]byte)环形队列存储,每条key/value数据按照TLV格式写入队列。
框架结构

总结
bigcache实现方法大致与freecache相同,不同点在于索引存储时更为巧妙,采用go内置的map结构加上基础数据类型来实现。同时底层存储数据的队列也可以根据空间大小来决定是否扩容。唯一缺陷是无法针对每个key进行设置不同的过期时间。
bigcache性能优化

浙公网安备 33010602011771号