当前位置: 首页 > news >正文

Golang的本地缓存freecache

1.什么是freecache

freecache 是一个针对 Go 语言设计的高性能、低 GC 开销内存缓存库,专为解决大数据量缓存场景下的 GC 压力问题而设计。

核心特点

  • 零 GC 开销:通过特殊的内存布局,避免大量指针产生,使 GC 扫描工作量与缓存数据量无关(O (1) 复杂度)。
  • 高并发支持:内置并发安全机制,可直接在多协程环境中使用。
  • 内存高效:基于环形缓冲区(ring buffer)管理内存,减少内存碎片。
  • 支持过期策略:可设置键值对的过期时间,自动淘汰过期数据。
  • 限制最大内存:允许指定缓存占用的最大内存,当超出时通过 LRU(最近最少使用)策略淘汰旧数据。

适用场景

适用于缓存数据量达到百万级以上、对 GC 延迟敏感的场景,例如:

  • 高并发 API 服务的热点数据缓存
  • 日志聚合系统的临时数据存储
  • 分布式系统中的本地缓存层

2. freecache怎么实现的并发安全机制

  1. 分段锁(Sharding Lock
    • freecache 将缓存空间分成多个片段(默认 256 个)
    • 每个片段拥有独立的互斥锁(sync.Mutex)
    • 对键进行哈希计算,确定其归属的片段,只锁定对应片段而非整个缓存
    • 这样不同片段的操作可以并行进行,大幅提升并发性能
  2. 哈希计算与分片策略
    • 使用高散列性的哈希函数(如 fnv 哈希)计算键的哈希值
    • 通过哈希值的高 8 位(默认情况下)确定分片索引
    • 这种设计保证了键在分片间的均匀分布,减少锁竞争
  3. 锁的粒度控制
    • 每个分片内部使用 sync.Mutex 保证原子操作
    • 针对读多写少场景做了优化,可配置读写锁(sync.RWMutex)
    • 锁只在操作期间持有,操作完成后立即释放
  4. 内存管理的并发安全
    • 每个分片维护独立的内存池和淘汰策略
    • 内存分配和释放操作在分片锁保护下进行
    • 使用环形缓冲区(ring buffer)减少内存碎片,提高分配效率
  5. 无锁数据结构的运用

    • 对于一些统计信息(如命中率、总大小)使用原子操作(sync/atomic)
    • 避免了为全局统计信息加锁带来的性能损耗

3. go缓存的垃圾回收

3.1 GC的分类

缓存场景中如果数据量大于百万级别,需要特别考虑数据类型对于gc的影响(注意string类型底层是指针+Len+Cap,因此也算是指针类型),如果缓存key和value都是非指针类型的话就无需多虑了。但实际应用场景中,key和value是(包含)指针类型数据是很常见的,因此使用缓存框架需要特别注意其对gc影响,从是否对GC影响角度来看缓存框架大致分为2类:

  1. GC开销:比如freecachebigcache这种,底层基于ringbuf,减小指针个数;
    • freecache/bigcache 预先向操作系统申请一大段 连续的字节切片(ring buffer)。
    • 所有 key/value 都 序列化后 直接塞到这段 buffer 里,彼此之间没有指针。
  2. 有GC开销:直接基于Map来实现的缓存框架。

    • 传统map 的 key、value 在内存里都是“指针指向的堆对象”。GC 每次扫描时,要沿着 map 里所有的 key/value 指针把整片对象图都走一遍。
    • 当 map 里有几百万条记录、每条记录里还存着 *User、*Order、*Item 等层层指针时,扫描工作量巨大,GC 暂停就会肉眼可见

3.2 零GC开销的底层原理:以 freecache 为例

3.2.1. freecache 中的内存布局

freecache 的核心是一块连续的 []byte(ring buffer),其内存结构如下:

  • 外层是 freecache 实例,内部持有 []byte 的 “切片头”(包含一个指向底层内存的指针、长度和容量)。
  • []byte 内部是序列化后的 key/value 裸字节,没有任何指针指向外部对象。

3.2.2. GC 扫描的 “简化路径”

当 GC 扫描到 freecache 时,只会处理:
freecache 实例 → []byte 切片头 → 连续内存块

由于 []byte 内部是裸字节,GC 不会递归扫描其中的内容(无需关心里面存储的是用户数据还是订单信息)。因此,无论缓存中有 1 万条还是 100 万条记录,GC 工作量都是 O(1),与数据量无关。

3.2.3 什么情况下会回收?

触发 GC 的时机跟普通 Go 程序一样:

  1. • 堆内存占用达到 GOGC 阈值;
  2. • runtime.GC() 手动触发;
  3. • 系统空闲时后台 GC。

回收的粒度是“不再被任何根对象可达的堆对象”。
具体到 freecache:

  1. • 只要 freecache 实例本身还被业务代码引用,那块 ring buffer 就不会被 GC 回收;
  2. • 如果你把整个 freecache 实例设为 nil、或程序退出,引用消失后,整块 []byte 才会在一次 GC 中被整体回收——注意这是“整块一次性”释放,而不是逐条记录释放。

对比传统缓存:

  • 传统缓存:当 map 中的某个 key 被删除,且该 key/value 不再被其他地方引用时,对应的堆对象会在下次 GC 中被回收(逐条回收)。
  • freecache:缓存的 key/value 存储在连续的 []byte 中,不会被单独回收。只有当整个 freecache 实例不再被引用(如设为 nil)时,整块 []byte 才会被一次性回收。

4. 如何选择缓存方案

  • 小数据量(万级以下):基于 map 的框架(如 sync.Mapgo-cache)足够用,实现简单,无需过度关注 GC。
  • 大数据量(百万级以上):优先选择 freecache 或 bigcache,通过减少指针数量降低 GC 压力,避免性能瓶颈。
http://www.dtcms.com/a/322475.html

相关文章:

  • Linux中Docker redis介绍以及应用
  • Kubernetes(K8s)不同行业的典型应用场景及价值分析 原创
  • 【31】C#实战篇——获取路径下的文件名(不包含路径和扩展名),并分离出文件名`fileName` ,文件名编号`SN`,文件名前缀`WMT`
  • 功能测试中常见的面试题-二
  • kettle插件-kettle MinIO插件,轻松解决文件上传到MinIO服务器
  • Nginx高性能web服务器
  • 如何衡量需求的紧急程度
  • 单片机输出高电平的两种方式
  • Spring Boot自定义Starter:从原理到实战全解析
  • TDengine IDMP 产品基本概念
  • Redis面试题及详细答案100道(01-15) --- 基础认知篇
  • 原生Vim操作大全
  • 分享一个基于Spark的眼科疾病临床数据可视化分析与应用研究Hadoop基于Vue和Echarts的眼科疾病统计数据交互式可视化系统的设计与实现
  • 麦当秀|MINDSHOW:在线AI PPT设计工具
  • linux 操作ppt
  • OceanBase架构设计
  • 7、docker |其余命令
  • 机器学习——08 特征降维
  • Android MVP架构详解:从理论到实践
  • (第三篇)spring cloud之Zookeeper注册中心
  • 观远BI 工具驱动零售消费行业精益增长的实践路径
  • 从反射到方法句柄:深入探索Java动态编程的终极解决方案
  • 【3D图像技术分析与实现】如何进行基于3DGS的城市道路重建?
  • 疯狂星期四文案网第34天运营日记
  • 计算机网络:如何将/22的CIDR地址块划分为4个子网
  • CosyVoice 语音合成模型性能优化实战:从 CPU 瓶颈到 GPU 加速的完整解决方案
  • Nginx 性能优化与动态内容处理
  • LeetCode 面试经典 150_数组/字符串_分发糖果(15_135_C++_困难)(贪心算法)
  • 关于开发语言的一些效率 从堆栈角度理解一部分c java go python
  • nginx的安装