我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Golang中的缓存库freecache怎么用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Golang中的缓存库freecache怎么用

这篇文章主要讲解了“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怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Golang中的缓存库freecache怎么用

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

下载Word文档

猜你喜欢

Golang中的缓存库freecache怎么用

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

go缓存库freecache怎么使用

本篇内容介绍了“go缓存库freecache怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!go开发缓存场景一般使用map或者缓存框架
2023-06-29

golang怎么使用mysql缓存

在 go 中使用 mysql 缓存对于提升性能至关重要。可通过第三方库或 mysql 自带的缓存功能实现。第三方库(如 github.com/go-sql-driver/mysql)使用 queryrow() 或 query() 方法和 m
golang怎么使用mysql缓存
2024-04-21

Golang函数在缓存机制中的应用

go函数可实现高效缓存机制:1. 使用函数作为缓存键:精细化缓存粒度;2. 使用函数计算缓存值:避免重复计算;3. 实战案例:实现内存缓存,使用go函数作为键和计算函数。利用 Go 语言函数实现高效缓存机制在高性能应用中,缓存起着至关重要
Golang函数在缓存机制中的应用
2024-05-02

怎么用Redis做预定库存缓存功能

这篇文章主要介绍了怎么用Redis做预定库存缓存功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用Redis做预定库存缓存功能文章都会有所收获,下面我们一起来看看吧。一、业务背景为了略去我们公司项目背景,
2023-06-29

java中怎么用redis缓存

Redis是一个高性能键值存储数据库,Java开发者可使用Jedis或Lettuce将其集成到应用程序中。连接Redis后,可通过set()设置值,get()获取值。为优化缓存性能,可定义失效策略,如到期时间或惰性失效。Redis还支持Sentinel提高可用性和Pub/Sub发布/订阅功能。最佳实践包括识别适合缓存的数据、设置失效策略、订阅过期键通知和监控缓存使用情况。
java中怎么用redis缓存
2024-04-02

C#中缓存System.Web.Caching怎么用

今天小编给大家分享一下C#中缓存System.Web.Caching怎么用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Sy
2023-06-30

java中怎么使用redis缓存

Java中使用Redis缓存可提高性能并减少数据库负载。通过集成Jedis库,Java应用程序可与Redis交互。缓存操作包括存储、检索和删除数据。Redis允许设置过期时间,自动删除过时数据。高级特性包括发布/订阅、事务和管道处理。最佳实践建议优先缓存读取频繁的数据,设置合理过期时间,采用缓存击穿保护并监控缓存性能。Redis的优点包括性能提升、数据库负载减少、高扩展性、数据实时更新支持以及对事务和管道处理的支持。
java中怎么使用redis缓存
2024-04-02

redis缓存在java中怎么用

Redis缓存是一种用于提高应用程序性能的高性能缓存系统。Java语言提供了多种库和框架,如Jedis、Redisson和SpringDataRedis,用于与Redis交互。通过这些库,开发者可以轻松连接到Redis服务器,存储、检索和删除数据。此外,Redis还支持复杂数据结构和发布/订阅功能。遵循最佳实践,如使用合理键前缀、设置超时时间和定期清理过期的键,可以优化Redis缓存的使用,充分发挥其优势,提升Java应用程序的性能。
redis缓存在java中怎么用
2024-04-02

SpringBoot2 中怎么利用Redis数据库实现缓存管理

SpringBoot2 中怎么利用Redis数据库实现缓存管理,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、Redis简介Spring Boot中除了对常用
2023-06-02

redis缓存在php中怎么运用

正文Redis是一种高性能内存数据存储,提供快速访问、多种数据结构和消息传递功能。在PHP中使用Redis,可通过pecl扩展连接服务器。常见的缓存策略包括读取缓存、写入缓存、缓存失效。实战示例展示了如何使用Redis缓存、更新和设置过期时间。Redis还可用于消息队列、计数器和会话管理等其他用途。通过在PHP中实施Redis,可以提升Web应用程序性能并改善用户体验。
redis缓存在php中怎么运用
2024-04-12

python中自带缓存lru_cache怎么用

这篇文章给大家分享的是有关python中自带缓存lru_cache怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1. lru_cache的使用1.1 参数详解以下是lru_cache方法的实现,我们看出可供
2023-06-20

SpringBoot中怎么使用Redis做缓存

在SpringBoot中使用Redis做缓存可以通过以下步骤实现:添加依赖:首先在pom.xml文件中添加Spring Data Redis的依赖,如下所示:org.springframework.
SpringBoot中怎么使用Redis做缓存
2024-04-09

java的redis缓存怎么使用

本文介绍了Redis缓存的使用方法,包括:Redis介绍Java中使用Redis缓存的步骤键值操作、哈希表操作、列表操作、集合操作过期策略和缓存失效Java中常用的Redis客户端库
java的redis缓存怎么使用
2024-04-02

DNS缓存中毒怎么工作的

本篇内容介绍了“DNS缓存中毒怎么工作的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!DNS缓存中毒是一种网络攻击,它使您的计算机误以为它会
2023-06-28

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录