Go Library · 泛型 · 零外部依赖

256 分片并发 · 0 额外分配
比 golang-lru 快 4.2 倍

lru-next 是一个超高性能、泛型、带 TTL 过期和 LRU 淘汰的内存缓存库。256 个分片通过 FNV-1a 哈希路由,大幅降低锁竞争;无锁 LRU 核心消除嵌套锁开销。专为读密集、高并发的场景设计。

Golru-next
$ go get github.com/doc-war/lru-next
查看快速开始 →
FNV-1a key Shard[0] RWMutex Shard[1] RWMutex Shard[2] RWMutex Shard[3] RWMutex Shard[4] RWMutex Shard[5] RWMutex Shard[6] RWMutex ⇠ hit Shard[7] RWMutex Shard[8] RWMutex Shard[9] RWMutex Shard[10] RWMutex Shard[11] RWMutex Shard[12] RWMutex Shard[13] RWMutex Shard[14] RWMutex Shard[15] RWMutex lruCore 内部结构 hashmap 双向链表 head tail 256 shards (4×64) · FNV-1a 路由 · 无锁 LRU 命中分片 空闲分片
FNV-1a 路由到 Shard[6],RLock 快路径直接返回 hashmap + 双向链表 = LRU
§00概述

用分片代替排队

lru-next 把"一个全局锁的缓存"拆成 256 个独立分片,每把锁只覆盖 1/256 的数据——并发越高,收益越大。

核心目标

  • 256 个分片FNV-1a 哈希路由,单把锁覆盖仅 1/256 的数据
  • 无锁 LRU 核心lruCore 不内嵌锁,完全委托外层 RWMutex,零嵌套锁开销
  • 缓存雪崩防护慢路径在分片锁内串行化 loader 调用,等同内置 singleflight
  • 脏数据优雅降级loader 失败时返回旧过期值,不中断服务

架构一览

Cache[T] ← 泛型 API └─ Shard[0..255] ← 256 分片 └─ lruCore ← 无锁 LRU + hashmap

全局 vs 分片锁

全局锁: 1 把锁管全部 → 排队严重 分片锁: 256 把锁各管 1/256 → 接近无锁

同类对比

以下对比基于 Intel i5-8265U @ 1.60GHz · 10K key 固定池 · RunParallel 并发模式。

golang-lru v2lru-next
并发写入
670 ns/op · 0 alloc
158 ns/op · 0 alloc
并发读取
762 ns/op · 0 alloc
183 ns/op · 0 alloc
混合负载 (9:1)
833 ns/op
405 ns/op
LRU 淘汰
TTL 过期
雪崩防护
✓ 内置
脏数据降级
§01快速开始

安装与使用

零外部依赖,仅使用 Go 标准库 + 泛型。

bash
go get github.com/doc-war/lru-next
main.go
package main

import (
    "fmt"
    "time"
    "github.com/doc-war/lru-next"
)

func main() {
    // 创建一个最多缓存 ~1000 个 key、TTL 5 分钟的缓存实例
    c, err := cache.New[string](1000, 5*time.Minute)
    if err != nil {
        panic(err)
    }

    // 获取或通过 loader 回源加载
    val, err := c.GetOrLoad("hello", func(key string) (string, error) {
        return "world", nil
    })
    fmt.Println(val) // world

    fmt.Println(c.Len()) // 1
    c.Delete("hello")
    c.Clear()
}

核心概念

概念说明
maxKeys缓存最大条目数,超出后 LRU 淘汰最久未使用的 key
TTL过期时间,读取时检查;过期后触发 loader 刷新
Shard256 个分片之一,每片有独立 sync.RWMutex
Loader回源函数,key 过期或不存在时自动调用,返回值写入缓存
降级loader 失败时返回旧过期值,保证服务不中断
§02架构

分片设计与快慢路径

256 分片 + 无锁 LRU + 双层检查,每层设计都为了一个目标:让最频繁的操作尽可能无锁。

路由与数据结构

路由
shardIndex = FNV-1a(key) % 256
存储
每个 Shard = sync.RWMutex + lruCore(hashmap + 双向链表)
淘汰
链表头 = 最近使用,链表尾 = 最久未使用;超出容量淘汰尾部

GetOrLoad 快路径 / 慢路径

95% 以上的请求走快路径,仅加 RLock 即可返回。只有缓存过期或不存在时才走慢路径获取写锁。

GetOrLoad(key, loader) RLock 命中且 未过期? 是 ✓ RUnlock + 返回 否 ✗ Lock (写锁) 双重检查(可能已被其他 goroutine 刷新) loader 成功 → 写入缓存 → Unlock + 返回新值 快路径 (95%) 慢路径 (5%) loader 失败 → 返回旧过期值(降级)· Unlock
RLock 快路径直接返回值 Lock 慢路径串行化 loader,内置雪崩防护

算法组件

组件算法说明
分片路由FNV-1a非加密哈希,极低碰撞率,硬件友好
并发控制sync.RWMutex读多写少场景,RLock 无竞争
淘汰策略LRU 双向链表头 = 最近使用,尾 = 最久未使用
过期检查读取时检查惰性过期,无后台定时器开销
§03性能

基准测试

硬件:Intel i5-8265U @ 1.60GHz, Windows, Go 1.25.3。10K key 固定池,RunParallel 并发模式,取 5 次均值。

并发写入(Load+Set / Add / Set)

ns/opB/op
lru-next
158
2
golang-lru v2
670
0
go-cache
532
0

并发读取(Get 命中)

ns/opvs lru-next
lru-next
183
golang-lru v2
762
快 4.2×
go-cache *
98
慢 1.9×

* go-cache 无 LRU 淘汰,仅为 map + sync.RWMutex,功能不对等。

混合负载(90% 读 + 10% 写)

ns/opvs lru-next
lru-next
405
golang-lru v2
833
快 2.1×
go-cache
700
快 1.7×

关键结论

§04API

接口参考

cache.go
// 创建一个最多缓存 maxKeys 个 key、TTL 为 ttl 的缓存实例
func New[T any](maxKeys int, ttl time.Duration) (*Cache[T], error)

// 获取或通过 loader 回源加载。loader 失败时返回旧过期值。
func (c *Cache[T]) GetOrLoad(id string, loader func(string) (T, error)) (T, error)

// 删除指定 key
func (c *Cache[T]) Delete(id string)

// 清空所有缓存
func (c *Cache[T]) Clear()

// 当前缓存条目总数(近似值)
func (c *Cache[T]) Len() int
参数类型说明
maxKeysint缓存最大条目数,超出后 LRU 淘汰
ttltime.Duration过期时间,读取时惰性检查
loaderfunc(string) (T, error)回源函数,key 过期或不存在时自动调用

错误处理

降级行为
// GetOrLoad 在 loader 失败时的行为:
val, err := c.GetOrLoad("key", loader)

// ┌─ 缓存中无旧值 → 返回零值 + loader 错误
// ├─ 存在旧过期值 → 返回旧值 + loader 错误(降级模式)
// └─ loader 成功   → 返回新值 + nil

NewmaxKeys 小于 1 时会自动提升为 1,保证缓存正常工作。

§05特性

功能特性

已知限制