广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Golang中的缓存库freecache怎么用
  • 429
分享到

Golang中的缓存库freecache怎么用

2023-06-29 06:06:35 429人浏览 泡泡鱼
摘要

这篇文章主要讲解了“golang中的缓存库freecache怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中的缓存库freecache怎么用”吧!go开发缓存场景一般使用m

这篇文章主要讲解了“golang中的缓存库freecache怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中的缓存库freecache怎么用”吧!

Golang中的缓存库freecache怎么用

go开发缓存场景一般使用map或者缓存框架,为了线程安全会使用sync.Map或线程安全的缓存框架。

缓存场景中如果数据量大于百万级别,需要特别考虑数据类型对于GC的影响(注意string类型底层是指针+Len+Cap,因此也算是指针类型),如果缓存key和value都是非指针类型的话就无需多虑了。

但实际应用场景中,key和value是(包含)指针类型数据是很常见的,因此使用缓存框架需要特别注意其对gc影响,从是否对GC影响角度来看缓存框架大致分为2类:

  • 零GC开销:比如freecache或bigcache这种,底层基于ringbuf,减小指针个数;

  • 有GC开销:直接基于Map来实现的缓存框架。

对于map而言,gc时会扫描所有key/value键值对,如果其都是基本类型,那么gc便不会再扫描。

下面以freecache为例分析下其实现原理,代码示例如下:

func main() {   cacheSize := 100 * 1024 * 1024   cache := freecache.NewCache(cacheSize)   for i := 0; i < N; i++ {      str := strconv.Itoa(i)      _ = cache.Set([]byte(str), []byte(str), 1)   }   now := time.Now()   runtime.GC()   fmt.Printf("freecache, GC took: %s\n", time.Since(now))   _, _ = cache.Get([]byte("aa"))   now = time.Now()   for i := 0; i < N; i++ {      str := strconv.Itoa(i)      _, _ = cache.Get([]byte(str))   }   fmt.Printf("freecache, Get took: %s\n\n", time.Since(now))}

1 初始化

freecache.NewCache会初始化本地缓存,size表示存储空间大小,freecache会初始化256个segment,每个segment是独立的存储单元,freecache加维度也是基于segment的,每个segment有一个ringbuf,初始大小为size/256。freecache号称零GC的来源就是其指针是固定的,只有512个,每个segment有2个,分别是rb和slotData(注意切片为指针类型)。

type segment struct {   rb            RingBuf // ring buffer that stores data   segId         int   _             uint32  // 占位   missCount     int64   hitCount      int64   entryCount    int64   totalCount    int64      // number of entries in ring buffer, including deleted entries.   totalTime     int64      // used to calculate least recent used entry.   timer         Timer      // Timer giving current time   totalEvacuate int64      // used for debug   totalExpired  int64      // used for debug   overwrites    int64      // used for debug   touched       int64      // used for debug   vacuumLen     int64      // up to vacuumLen, new data can be written without overwriting old data.   slotLens      [256]int32 // The actual length for every slot.   slotCap       int32      // max number of entry pointers a slot can hold.   slotsData     []entryPtr // 索引指针}func NewCacheCustomTimer(size int, timer Timer) (cache *Cache) {    cache = new(Cache)    for i := 0; i < segmentCount; i++ {        cache.segments[i] = newSegment(size/segmentCount, i, timer)    }}func newSegment(bufSize int, segId int, timer Timer) (seg segment) {    seg.rb = NewRingBuf(bufSize, 0)    seg.segId = segId    seg.timer = timer    seg.vacuumLen = int64(bufSize)    seg.slotCap = 1    seg.slotsData = make([]entryPtr, 256*seg.slotCap) // 每个slotData初始化256个单位大小}

2 读写流程

freecache的key和value都是[]byte数组,使用时需要自行序列化和反序列化,如果缓存复杂对象不可忽略其序列化和反序列化带来的影响,首先看下Set流程:

_ = cache.Set([]byte(str), []byte(str), 1)

Set流程首先对key进行hash,hashVal类型uint64,其低8位segID对应segment数组,低8-15位表示slotId对应slotsData下标,高16位表示slotsData下标对应的[]entryPtr某个数据,这里需要查找操作。注意[]entryPtr数组大小为slotCap(初始为1),当扩容时会slotCap倍增。

每个segment对应一个lock(sync.Mutex),因此其能够支持较大并发量,而不像sync.Map只有一个锁。

func (cache *Cache) Set(key, value []byte, expireSeconds int) (err error) {   hashVal := hashFunc(key)   segID := hashVal & segmentAndOpVal // 低8位   cache.locks[segID].Lock() // 加锁   err = cache.segments[segID].set(key, value, hashVal, expireSeconds)   cache.locks[segID].Unlock()}func (seg *segment) set(key, value []byte, hashVal uint64, expireSeconds int) (err error) {   slotId := uint8(hashVal >> 8)   hash26 := uint16(hashVal >> 16)   slot := seg.getSlot(slotId)   idx, match := seg.lookup(slot, hash26, key)   var hdrBuf [ENTRY_HDR_SIZE]byte   hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))   if match { // 有数据更新操作      matchedPtr := &slot[idx]      seg.rb.ReadAt(hdrBuf[:], matchedPtr.offset)      hdr.slotId = slotId      hdr.hash26 = hash26      hdr.keyLen = uint16(len(key))      originAccessTime := hdr.accessTime      hdr.accessTime = now      hdr.expireAt = expireAt      hdr.valLen = uint32(len(value))      if hdr.valCap >= hdr.valLen {         // 已存在数据value空间能存下此次value大小         atomic.AddInt64(&seg.totalTime, int64(hdr.accessTime)-int64(originAccessTime))         seg.rb.WriteAt(hdrBuf[:], matchedPtr.offset)         seg.rb.WriteAt(value, matchedPtr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))         atomic.AddInt64(&seg.overwrites, 1)         return      }      // 删除对应entryPtr,涉及到slotsData内存copy,ringbug中只是标记删除      seg.delEntryPtr(slotId, slot, idx)      match = false      // increase capacity and limit entry len.      for hdr.valCap < hdr.valLen {         hdr.valCap *= 2      }      if hdr.valCap > uint32(maxKeyValLen-len(key)) {         hdr.valCap = uint32(maxKeyValLen - len(key))      }   } else { // 无数据      hdr.slotId = slotId      hdr.hash26 = hash26      hdr.keyLen = uint16(len(key))      hdr.accessTime = now      hdr.expireAt = expireAt      hdr.valLen = uint32(len(value))      hdr.valCap = uint32(len(value))      if hdr.valCap == 0 { // avoid infinite loop when increasing capacity.         hdr.valCap = 1      }   }      // 数据实际长度为 ENTRY_HDR_SIZE=24 + key和value的长度       entryLen := ENTRY_HDR_SIZE + int64(len(key)) + int64(hdr.valCap)   slotModified := seg.evacuate(entryLen, slotId, now)   if slotModified {      // the slot has been modified during evacuation, we need to looked up for the 'idx' again.      // otherwise there would be index out of bound error.      slot = seg.getSlot(slotId)      idx, match = seg.lookup(slot, hash26, key)      // assert(match == false)   }   newOff := seg.rb.End()   seg.insertEntryPtr(slotId, hash26, newOff, idx, hdr.keyLen)   seg.rb.Write(hdrBuf[:])   seg.rb.Write(key)   seg.rb.Write(value)   seg.rb.Skip(int64(hdr.valCap - hdr.valLen))   atomic.AddInt64(&seg.totalTime, int64(now))   atomic.AddInt64(&seg.totalCount, 1)   seg.vacuumLen -= entryLen   return}

seg.evacuate会评估ringbuf是否有足够空间存储key/value,如果空间不够,其会从空闲空间尾部后一位(也就是待淘汰数据的开始位置)开始扫描(oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size()),如果对应数据已被逻辑deleted或者已过期,那么该块内存可以直接回收,如果不满足回收条件,则将entry从环头调换到环尾,再更新entry的索引,如果这样循环5次还是不行,那么需要将当前oldHdrBuf回收以满足内存需要。

执行完seg.evacuate所需空间肯定是能满足的,然后就是写入索引和数据了,insertEntryPtr就是写入索引操作,当[]entryPtr中元素个数大于seg.slotCap(初始1)时,需要扩容操作,对应方法见seg.expand,这里不再赘述。

写入ringbuf就是执行rb.Write即可。

func (seg *segment) evacuate(entryLen int64, slotId uint8, now uint32) (slotModified bool) {   var oldHdrBuf [ENTRY_HDR_SIZE]byte   consecutiveEvacuate := 0   for seg.vacuumLen < entryLen {      oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size()      seg.rb.ReadAt(oldHdrBuf[:], oldOff)      oldHdr := (*entryHdr)(unsafe.Pointer(&oldHdrBuf[0]))      oldEntryLen := ENTRY_HDR_SIZE + int64(oldHdr.keyLen) + int64(oldHdr.valCap)      if oldHdr.deleted { // 已删除         consecutiveEvacuate = 0         atomic.AddInt64(&seg.totalTime, -int64(oldHdr.accessTime))         atomic.AddInt64(&seg.totalCount, -1)         seg.vacuumLen += oldEntryLen         continue      }      expired := oldHdr.expireAt != 0 && oldHdr.expireAt < now      leastRecentUsed := int64(oldHdr.accessTime)*atomic.LoadInt64(&seg.totalCount) <= atomic.LoadInt64(&seg.totalTime)      if expired || leastRecentUsed || consecutiveEvacuate > 5 {      // 可以回收         seg.delEntryPtrByOffset(oldHdr.slotId, oldHdr.hash26, oldOff)         if oldHdr.slotId == slotId {            slotModified = true         }         consecutiveEvacuate = 0         atomic.AddInt64(&seg.totalTime, -int64(oldHdr.accessTime))         atomic.AddInt64(&seg.totalCount, -1)         seg.vacuumLen += oldEntryLen         if expired {            atomic.AddInt64(&seg.totalExpired, 1)         } else {            atomic.AddInt64(&seg.totalEvacuate, 1)         }      } else {         // evacuate an old entry that has been accessed recently for better cache hit rate.         newOff := seg.rb.Evacuate(oldOff, int(oldEntryLen))         seg.updateEntryPtr(oldHdr.slotId, oldHdr.hash26, oldOff, newOff)         consecutiveEvacuate++         atomic.AddInt64(&seg.totalEvacuate, 1)      }   }}

freecache的Get流程相对来说简单点,通过hash找到对应segment,通过slotId找到对应索引slot,然后通过二分+遍历寻找数据,如果找不到直接返回ErrNotFound,否则更新一些time指标。Get流程还会更新缓存命中率相关指标。

func (cache *Cache) Get(key []byte) (value []byte, err error) {   hashVal := hashFunc(key)   segID := hashVal & segmentAndOpVal   cache.locks[segID].Lock()   value, _, err = cache.segments[segID].get(key, nil, hashVal, false)   cache.locks[segID].Unlock()   return}func (seg *segment) get(key, buf []byte, hashVal uint64, peek bool) (value []byte, expireAt uint32, err error) {   hdr, ptr, err := seg.locate(key, hashVal, peek) // hash+定位查找   if err != nil {      return   }   expireAt = hdr.expireAt   if cap(buf) >= int(hdr.valLen) {      value = buf[:hdr.valLen]   } else {      value = make([]byte, hdr.valLen)   }   seg.rb.ReadAt(value, ptr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))}

定位到数据之后,读取ringbuf即可,注意一般来说读取到的value是新创建的内存空间,因此涉及到[]byte数据的复制操作。

感谢各位的阅读,以上就是“Golang中的缓存库freecache怎么用”的内容了,经过本文的学习后,相信大家对Golang中的缓存库freecache怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

您可能感兴趣的文档:

--结束END--

本文标题: Golang中的缓存库freecache怎么用

本文链接: https://www.lsjlt.com/news/323026.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Golang中的缓存库freecache怎么用
    这篇文章主要讲解了“Golang中的缓存库freecache怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang中的缓存库freecache怎么用”吧!go开发缓存场景一般使用m...
    99+
    2023-06-29
  • go缓存库freecache怎么使用
    本篇内容介绍了“go缓存库freecache怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!go开发缓存场景一般使用map或者缓存框架...
    99+
    2023-06-29
  • 深入理解go缓存库freecache的使用
    目录1初始化2读写流程3总结go开发缓存场景一般使用map或者缓存框架,为了线程安全会使用sync.Map或线程安全的缓存框架。 缓存场景中如果数据量大于百万级别,需要特别考虑数据类...
    99+
    2022-11-13
  • AngularJS中缓存怎么用
    这篇文章主要介绍了AngularJS中缓存怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。缓存篇一个缓存就是一个组件,它可以透明地储存数...
    99+
    2022-10-19
  • MySQL数据库中怎么查询缓存
    这期内容当中小编将会给大家带来有关MySQL数据库中怎么查询缓存,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、缓存条件,原理MySQL Query Cache是用来缓...
    99+
    2022-10-18
  • 怎么用Redis做预定库存缓存功能
    这篇文章主要介绍了怎么用Redis做预定库存缓存功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用Redis做预定库存缓存功能文章都会有所收获,下面我们一起来看看吧。一、业务背景为了略去我们公司项目背景,...
    99+
    2023-06-29
  • C#中缓存System.Web.Caching怎么用
    今天小编给大家分享一下C#中缓存System.Web.Caching怎么用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Sy...
    99+
    2023-06-30
  • Redis中的缓存穿透、缓存雪崩、缓存击穿和缓存一致性怎么理解
    这篇文章主要介绍“Redis中的缓存穿透、缓存雪崩、缓存击穿和缓存一致性怎么理解”,在日常操作中,相信很多人在Redis中的缓存穿透、缓存雪崩、缓存击穿和缓存一致性怎么理解问题上存在疑惑,小编查阅了各式资料...
    99+
    2022-10-19
  • 怎么打造高性能的 Go 缓存库
    本篇内容主要讲解“怎么打造高性能的 Go 缓存库”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么打造高性能的 Go 缓存库”吧!我在看一些优秀的开源库的时候看...
    99+
    2022-10-19
  • SpringBoot2 中怎么利用Redis数据库实现缓存管理
    SpringBoot2 中怎么利用Redis数据库实现缓存管理,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、Redis简介Spring Boot中除了对常用...
    99+
    2023-06-02
  • Golang程序中怎么使用Prometheus的client_golang库
    本文小编为大家详细介绍“Golang程序中怎么使用Prometheus的client_golang库”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang程序中怎么使用Prometheus的client_golang库”文章能帮助大家...
    99+
    2023-07-05
  • 怎么手写一个基于Proxy的缓存库
    本篇内容主要讲解“怎么手写一个基于Proxy的缓存库”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么手写一个基于Proxy的缓存库”吧!项目演进任何项目都不是...
    99+
    2022-10-19
  • python中自带缓存lru_cache怎么用
    这篇文章给大家分享的是有关python中自带缓存lru_cache怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1. lru_cache的使用1.1 参数详解以下是lru_cache方法的实现,我们看出可供...
    99+
    2023-06-20
  • DNS缓存中毒怎么工作的
    本篇内容介绍了“DNS缓存中毒怎么工作的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!DNS缓存中毒是一种网络攻击,它使您的计算机误以为它会...
    99+
    2023-06-28
  • Redis缓存中怎么改善数据库查询性能
    这期内容当中小编将会给大家带来有关Redis缓存中怎么改善数据库查询性能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。因为Redis具有在数据存储中快速读写数据的能力,所...
    99+
    2022-10-18
  • jQuery中数据缓存$.data怎么使用
    这篇文章主要介绍“jQuery中数据缓存$.data怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“jQuery中数据缓存$.data怎么使用”文章能帮助大家解决问题。一、实现原理:对于DOM...
    99+
    2023-07-04
  • Vue3中怎么用WeakMap作为缓存区
    这篇文章主要介绍“Vue3中怎么用WeakMap作为缓存区”,在日常操作中,相信很多人在Vue3中怎么用WeakMap作为缓存区问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue3中怎么用WeakMap作为...
    99+
    2023-06-22
  • odoo中怎么使用redis实现缓存
    本篇内容主要讲解“odoo中怎么使用redis实现缓存”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“odoo中怎么使用redis实现缓存”吧!Odoo中使用Redis实现缓存可以提高系统性能,避...
    99+
    2023-07-05
  • SpringBoot项目中怎么使用缓存Cache
    本文小编为大家详细介绍“SpringBoot项目中怎么使用缓存Cache”,内容详细,步骤清晰,细节处理妥当,希望这篇“SpringBoot项目中怎么使用缓存Cache”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧...
    99+
    2023-07-06
  • 怎么使用Spring提供的不同缓存注解实现缓存
    这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用Spring提供的不同缓存注解实现缓存文章都会有所收获,下面我们一起来看看吧。前言缓存可以通...
    99+
    2023-07-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作