深入理解Freecache

本文详细探讨了Golang本地缓存库freecache的设计与实现,包括其优势、与Golang Map的性能对比,以及内部的set操作、过期与删除策略、entry索引等关键部分。freecache采用接近LRU的置换算法,支持高并发线程安全访问,并具有零GC开销,但存在不能动态扩容和非完全LRU等问题。
摘要由CSDN通过智能技术生成

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整体架构设计

  • 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    <
  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值