Documentation
¶
Overview ¶
Package cache 提供生产可用的进程内缓存原语: 无第三方依赖, 全部类型线程安全.
设计理念 ¶
- 读不阻塞: 读路径只做一次原子加载, 不抢锁, 不等待网络; 慢速数据源不影响读性能.
- 后台刷新: 由后台协程按时间间隔或手动触发刷新, 读到的永远是"上一次刷新好的快照".
- 失败保留旧值: 刷新失败(含 Loader panic)时沿用上一次成功的数据, 同时把错误 写进快照 Snapshot.Err 并回调错误钩子, 即 stale-while-error.
- 条件更新: 通过 SetParams 改变数据源参数, 或用 Condition 决定本次是否真的拉取 (例如只在工作时间刷新), 跳过时快照保持不变, 也不会污染成功时间.
- 合并并发拉取: 同一时刻的多个刷新请求合并为一次 Loader 调用, 防止缓存击穿.
- 生命周期确定: Close 幂等且可等待; 可选 WithAutoStop 在对象被 GC 回收时自动关闭 后台协程(依赖 runtime finalizer, 不保证退出前一定执行, 推荐显式 Close).
- 按数据分档: WithValueCondition 决定"值在什么区间才继续后台刷新", WithReadPolicy 决定"每次 Read 走缓存还是同步穿透数据源", 两者配对即可实现 "安全区间缓存 + 周期刷新, 敏感区间每次直连".
分档用法的读入口是 Read:
c := cache.NewAsync(time.Minute, load,
cache.WithValueCondition(func(s cache.Snapshot[int]) bool { return s.Value > 5 }),
cache.WithReadPolicy(func(s cache.Snapshot[int]) bool { return s.Loaded && s.Value > 5 }))
v, err := c.Read(ctx) // 值大走缓存, 值小同步穿透(并发读会合并)
两种缓存 ¶
- AsyncCache: 面向"定期/条件远程更新"的场景(爬虫页面, 配置表, 榜单, 远程表数据). 后台按 Interval 刷新, 业务侧 Get 拿到最近一次成功的值, 从不阻塞.
- CacheLRU: 面向"减少频繁操作"的场景(按 key 查询/去重/会话). 容量受限, 支持 TTL, GetOrLoad 提供带单飞(singleflight)的加载穿透.
数据形态 ¶
库的核心只有一件事: 定期/按条件把数据源拉回来, 发布成不可变快照, 读侧永不阻塞. AsyncCache 的类型参数 T 就是数据形态:
T = Config 单个值(配置对象/版本号) T = []Row 列表(榜单/全量行/页面内容) T = map[K]V 集合(按 key 查, 最常用)
集合形态请让 Loader 每次返回一个全新的 map 并保证读侧只读, 这样无需任何锁:
c := cache.NewAsync(time.Minute, func(ctx *cache.Context[map[string]User]) (map[string]User, error) {
return db.LoadAllUsers(ctx.Ctx())
})
users := c.Get() // 不可变快照
u, ok := users["u1"]
需要按 key 淘汰/单 key TTL/负缓存时, 说明数据不是一个整体, 改用 CacheLRU.
快速开始 ¶
// 每 30 秒刷新一次远程表; 读永远不阻塞
c := cache.NewAsync(30*time.Second, func(ctx *cache.Context[[]Row]) ([]Row, error) {
return fetchRows(ctx.Ctx(), ctx.Params())
}, cache.WithTimeout(5*time.Second))
defer c.Close()
rows := c.Get() // 拿到最近一次成功的快照, 失败时是旧值
更多示例见 example/ 目录与 readme.md.
Index ¶
- Constants
- Variables
- type AsyncCache
- func (c *AsyncCache[T]) Close() error
- func (c *AsyncCache[T]) CloseWithContext(ctx context.Context) error
- func (c *AsyncCache[T]) Closed() bool
- func (c *AsyncCache[T]) Done() <-chan struct{}
- func (c *AsyncCache[T]) ForceUpdate()
- func (c *AsyncCache[T]) Get() T
- func (c *AsyncCache[T]) GetSync(f func(current T))
- func (c *AsyncCache[T]) GetWithOK() (T, bool)
- func (c *AsyncCache[T]) HasValue() bool
- func (c *AsyncCache[T]) Interval() time.Duration
- func (c *AsyncCache[T]) Jitter() float64
- func (c *AsyncCache[T]) Next(ctx context.Context) (Snapshot[T], error)
- func (c *AsyncCache[T]) Params() any
- func (c *AsyncCache[T]) Read(ctx context.Context) (T, error)
- func (c *AsyncCache[T]) Refresh(ctx context.Context) (Snapshot[T], error)
- func (c *AsyncCache[T]) SetCondition(cond Condition)
- func (c *AsyncCache[T]) SetError(handler func(ctx *Context[T]))
- func (c *AsyncCache[T]) SetErrorHandler(handler ErrorHandler[T])
- func (c *AsyncCache[T]) SetInterval(interval time.Duration)
- func (c *AsyncCache[T]) SetParams(params any)
- func (c *AsyncCache[T]) SetReadPolicy(policy ReadPolicy[T])
- func (c *AsyncCache[T]) SetValueCondition(cond func(s Snapshot[T]) bool)
- func (c *AsyncCache[T]) Snapshot() Snapshot[T]
- func (c *AsyncCache[T]) Stats() Stats
- func (c *AsyncCache[T]) Timeout() time.Duration
- func (c *AsyncCache[T]) Trigger() bool
- func (c *AsyncCache[T]) ValueOr(def T) T
- func (c *AsyncCache[T]) View(f func(s Snapshot[T]))
- func (c *AsyncCache[T]) WaitFirst(ctx context.Context) error
- func (c *AsyncCache[T]) WaitFirstUpdate(waitTime time.Duration) error
- func (c *AsyncCache[T]) WaitValue(ctx context.Context) (T, error)
- type AsyncCacheContext
- type CacheLRU
- func (l *CacheLRU[K, V]) All() iter.Seq2[K, V]
- func (l *CacheLRU[K, V]) Capacity() int
- func (l *CacheLRU[K, V]) Cleanup() int
- func (l *CacheLRU[K, V]) Clear()
- func (l *CacheLRU[K, V]) DefaultTTL() time.Duration
- func (l *CacheLRU[K, V]) Get(key K) (V, bool)
- func (l *CacheLRU[K, V]) GetOrLoad(ctx context.Context, key K, loader func(ctx context.Context) (V, error)) (V, error)
- func (l *CacheLRU[K, V]) GetOrLoadWithTTL(ctx context.Context, key K, ttl time.Duration, ...) (V, error)
- func (l *CacheLRU[K, V]) Keys() []K
- func (l *CacheLRU[K, V]) Len() int
- func (l *CacheLRU[K, V]) Peek(key K) (V, bool)
- func (l *CacheLRU[K, V]) Range(f func(key K, value V) bool)
- func (l *CacheLRU[K, V]) Remove(key K) (V, bool)
- func (l *CacheLRU[K, V]) ResetStats()
- func (l *CacheLRU[K, V]) Set(key K, value V)
- func (l *CacheLRU[K, V]) SetIfAbsent(key K, value V) bool
- func (l *CacheLRU[K, V]) SetWithTTL(key K, value V, ttl time.Duration)
- func (l *CacheLRU[K, V]) Shards() int
- func (l *CacheLRU[K, V]) Size() int
- func (l *CacheLRU[K, V]) Stats() LRUStats
- func (l *CacheLRU[K, V]) Values() []V
- type Condition
- type Context
- func (c *Context[T]) Ctx() context.Context
- func (c *Context[T]) Current() T
- func (c *Context[T]) Deadline() (time.Time, bool)
- func (c *Context[T]) Done() <-chan struct{}
- func (c *Context[T]) Error() error
- func (c *Context[T]) GetCurrent() T
- func (c *Context[T]) HasCurrent() bool
- func (c *Context[T]) Params() any
- func (c *Context[T]) Snapshot() Snapshot[T]
- type ErrorHandler
- type LRUStats
- type Loader
- type Option
- func WithAutoStop() Option
- func WithCondition(cond Condition) Option
- func WithInterval(d time.Duration) Option
- func WithJitter(ratio float64) Option
- func WithLazyStart() Option
- func WithOnError(f func(err error)) Option
- func WithParams(params any) Option
- func WithReadPolicy[T any](policy ReadPolicy[T]) Option
- func WithTimeout(d time.Duration) Option
- func WithValueCondition[T any](cond func(s Snapshot[T]) bool) Option
- type Options
- type ReadPolicy
- type RefreshInfo
- type Snapshot
- type Stats
Examples ¶
Constants ¶
const DefaultInterval = time.Minute
DefaultInterval 是 NewAsyncCache 未显式指定间隔时使用的刷新间隔.
Variables ¶
var ErrClosed = errors.New("cache: async cache closed")
ErrClosed 表示 AsyncCache 已经关闭.
Functions ¶
This section is empty.
Types ¶
type AsyncCache ¶ added in v1.3.0
type AsyncCache[T any] struct { // contains filtered or unexported fields }
AsyncCache 是"后台刷新 + 非阻塞读取"的快照缓存.
读接口 Get/Snapshot 只做一次原子加载, 因此永远不会被慢速数据源阻塞; 后台按 Interval 刷新, 失败时保留上一次成功的值。
请使用 NewAsync(或兼容的 NewAsyncCache) 创建实例, 并在不再使用时 Close。
Example ¶
ExampleAsyncCache 演示后台定时刷新 + 非阻塞读取。
package main
import (
"context"
"fmt"
"time"
"github.com/474420502/cache"
)
func main() {
c := cache.NewAsync(time.Second, func(ctx *cache.Context[string]) (string, error) {
return "v1", nil
})
defer c.Close()
// 等首次刷新完成; 生产中也可以不等待, 直接读旧值/空值。
_ = c.WaitFirst(context.Background())
fmt.Println(c.Get())
fmt.Println(c.Snapshot().Loaded)
}
Output: v1 true
Example (Adaptive) ¶
ExampleAsyncCache_adaptive 演示"按当前值分档": 值大走缓存, 值小每次穿透。
package main
import (
"context"
"fmt"
"github.com/474420502/cache"
)
func main() {
source := 100
c := cache.NewAsync(0, func(ctx *cache.Context[int]) (int, error) {
return source, nil
},
cache.WithLazyStart(),
// 值 <= 5 时后台不再周期刷新
cache.WithValueCondition(func(s cache.Snapshot[int]) bool { return s.Value > 5 }),
// 值 <= 5 时每次读都同步穿透
cache.WithReadPolicy(func(s cache.Snapshot[int]) bool { return s.Loaded && s.Value > 5 }),
)
defer c.Close()
v, _ := c.Read(context.Background()) // 冷启动: 无值, 穿透
fmt.Println("首次:", v, "穿透次数:", c.Stats().PassThroughs)
v, _ = c.Read(context.Background()) // 值 100 > 5: 走缓存
fmt.Println("第二次:", v, "穿透次数:", c.Stats().PassThroughs)
source = 3
_, _ = c.Refresh(context.Background()) // 把 3 带进快照(真实场景由一次穿透完成)
v, _ = c.Read(context.Background()) // 值 3 <= 5: 穿透
source = 1
v, _ = c.Read(context.Background()) // 每次都是数据源最新值
fmt.Println("低档:", v, "穿透次数:", c.Stats().PassThroughs)
}
Output: 首次: 100 穿透次数: 1 第二次: 100 穿透次数: 1 低档: 1 穿透次数: 3
Example (Condition) ¶
ExampleAsyncCache_condition 演示"条件更新": 只在条件满足时自动刷新。
package main
import (
"context"
"fmt"
"time"
"github.com/474420502/cache"
)
func main() {
c := cache.NewAsync(10*time.Millisecond, func(ctx *cache.Context[int]) (int, error) {
return 42, nil
}, cache.WithCondition(func(info cache.RefreshInfo) bool {
return false // 例如: 只在交易时段刷新
}))
defer c.Close()
_ = c.WaitFirst(context.Background())
deadline := time.Now().Add(2 * time.Second)
for c.Stats().Skipped == 0 && time.Now().Before(deadline) {
time.Sleep(time.Millisecond)
}
fmt.Println(c.Get())
fmt.Println(c.Stats().Skipped > 0)
fmt.Println(c.Snapshot().Version) // 条件为假 -> 版本不再增长
}
Output: 42 true 1
Example (Dataset) ¶
ExampleAsyncCache_dataset 演示"定期更新一个数据集合"的推荐形态: T = map[K]V。
Loader 每次返回一个全新的 map, 读侧只读不写, 因此天生无锁、无撕裂; 旧的 map 在下一次快照发布后自然变成垃圾。
package main
import (
"context"
"fmt"
"time"
"github.com/474420502/cache"
)
func main() {
loadAllRows := func(ctx *cache.Context[map[string]int]) (map[string]int, error) {
return map[string]int{"alice": 1, "bob": 2}, nil // 真实场景: 一次拉回整张表
}
c := cache.NewAsync(time.Minute, loadAllRows)
defer c.Close()
_ = c.WaitFirst(context.Background())
rows := c.Get() // 一份不可变快照
fmt.Println(len(rows), rows["bob"], rows["nobody"])
}
Output: 2 2 0
func NewAsync ¶ added in v1.3.0
NewAsync 创建一个后台刷新的快照缓存并立即启动刷新循环。
interval <= 0 表示不做定时刷新, 只做首次刷新与手动刷新(Trigger/Refresh)。 opts 中的 Interval 会覆盖 interval 参数。
后台协程不会自行结束, 除非调用 Close(或启用 WithAutoStop)。
func NewAsyncCache
deprecated
added in
v1.3.0
func NewAsyncCache[T any](loader Loader[T], opts ...Option) *AsyncCache[T]
NewAsyncCache 等同于 NewAsync(DefaultInterval, loader, opts...), 用于兼容旧代码。
Deprecated: 请使用 NewAsync 明确指定刷新间隔(旧实现是"疯狂循环"刷新, 没有间隔)。
func (*AsyncCache[T]) Close ¶ added in v1.3.0
func (c *AsyncCache[T]) Close() error
Close 关闭后台刷新协程并等待其退出(幂等)。
关闭后 Get/Snapshot 仍可读到最后一次快照, Refresh 返回 ErrClosed。 若 Loader 不尊重 ctx 且未设置 Timeout, Close 会一直等待。 不要在 Loader 或错误回调里调用 Close。
func (*AsyncCache[T]) CloseWithContext ¶ added in v1.3.0
func (c *AsyncCache[T]) CloseWithContext(ctx context.Context) error
CloseWithContext 关闭后台刷新协程, 最多等待 ctx 结束。
func (*AsyncCache[T]) Closed ¶ added in v1.3.0
func (c *AsyncCache[T]) Closed() bool
Closed 报告是否已经 Close。
func (*AsyncCache[T]) Done ¶ added in v1.3.0
func (c *AsyncCache[T]) Done() <-chan struct{}
Done 返回的通道在后台刷新循环退出后关闭。
func (*AsyncCache[T]) ForceUpdate ¶ added in v1.3.0
func (c *AsyncCache[T]) ForceUpdate()
ForceUpdate 是 Refresh(context.Background()) 的旧 API 写法, 会阻塞到刷新完成。
func (*AsyncCache[T]) Get ¶ added in v1.3.0
func (c *AsyncCache[T]) Get() T
Get 返回最近一次成功刷新的值. 读路径无锁, 永不阻塞。 若从未成功刷新过, 返回 T 的零值(可用 HasValue 或 Snapshot 区分)。
func (*AsyncCache[T]) GetSync ¶ added in v1.3.0
func (c *AsyncCache[T]) GetSync(f func(current T))
GetSync 用当前值调用 f. 兼容旧 API; 值本身是只读快照, f 不会被缓存内部加锁。
func (*AsyncCache[T]) GetWithOK ¶ added in v1.3.0
func (c *AsyncCache[T]) GetWithOK() (T, bool)
GetWithOK 返回最近一次成功刷新的值以及是否有效(即是否成功刷新过)。
func (*AsyncCache[T]) HasValue ¶ added in v1.3.0
func (c *AsyncCache[T]) HasValue() bool
HasValue 报告是否至少成功刷新过一次。
func (*AsyncCache[T]) Interval ¶ added in v1.3.0
func (c *AsyncCache[T]) Interval() time.Duration
Interval 返回当前刷新间隔。
func (*AsyncCache[T]) Jitter ¶ added in v1.3.0
func (c *AsyncCache[T]) Jitter() float64
Jitter 返回当前间隔抖动比例。
func (*AsyncCache[T]) Next ¶ added in v1.3.0
func (c *AsyncCache[T]) Next(ctx context.Context) (Snapshot[T], error)
Next 返回当前快照, 并阻塞到"下一次刷新完成"(成功或失败)或 ctx 结束。 返回的第二个 error 只表示等待本身失败(ctx 结束/缓存关闭), 刷新错误在快照里。
func (*AsyncCache[T]) Read ¶ added in v1.4.0
func (c *AsyncCache[T]) Read(ctx context.Context) (T, error)
Read 按读策略取数, 是"按数据分档"场景的统一读入口:
- 策略判定可以走缓存时, 直接返回当前快照值(不阻塞, 与 Get 同价);
- 否则同步刷新一次(同一时刻的并发读会合并成一次 Loader 调用), 返回刷新后的值。
策略来自 WithReadPolicy / SetReadPolicy; 未配置时的默认语义是 "已有值就返回, 没有值就同步加载一次"(冷启动首次读不返回零值)。
刷新失败时返回 (旧值, err): 调用方可以自行决定降级使用旧值还是报错。 缓存已关闭时返回 (旧值, ErrClosed)。
策略回调在调用方协程里执行, 请保证它足够廉价且不 panic; 需要"必定同步刷新" 时不要用策略, 直接调用 Refresh。
func (*AsyncCache[T]) Refresh ¶ added in v1.3.0
func (c *AsyncCache[T]) Refresh(ctx context.Context) (Snapshot[T], error)
Refresh 同步刷新一次并返回新快照, 不受 Condition 约束, 用于"强制拉取"。
并发调用会合并为一次 Loader 调用(单飞); 等待者若 ctx 先结束则返回 ctx.Err()。 缓存已关闭时返回 ErrClosed。
func (*AsyncCache[T]) SetCondition ¶ added in v1.3.0
func (c *AsyncCache[T]) SetCondition(cond Condition)
SetCondition 设置自动刷新的前置条件; 传 nil 表示不限制。
func (*AsyncCache[T]) SetError ¶ added in v1.3.0
func (c *AsyncCache[T]) SetError(handler func(ctx *Context[T]))
SetError 设置刷新失败回调(旧 API)。回调里可用 ctx.Error() 取错误。
func (*AsyncCache[T]) SetErrorHandler ¶ added in v1.3.0
func (c *AsyncCache[T]) SetErrorHandler(handler ErrorHandler[T])
SetErrorHandler 设置刷新失败回调, 回调在刷新协程中同步执行。 回调自身 panic 会被捕获, 不会影响后台刷新。
func (*AsyncCache[T]) SetInterval ¶ added in v1.3.0
func (c *AsyncCache[T]) SetInterval(interval time.Duration)
SetInterval 动态修改刷新间隔(<=0 表示停止定时刷新, 仍可手动刷新)。
func (*AsyncCache[T]) SetParams ¶ added in v1.3.0
func (c *AsyncCache[T]) SetParams(params any)
SetParams 替换传给 Loader 的参数. 下一次刷新即可看到新参数; 需要立刻生效时再调用 Trigger 或 Refresh。
func (*AsyncCache[T]) SetReadPolicy ¶ added in v1.4.0
func (c *AsyncCache[T]) SetReadPolicy(policy ReadPolicy[T])
SetReadPolicy 设置 Read 使用的读策略, 传 nil 恢复默认("有值即返回")。
func (*AsyncCache[T]) SetValueCondition ¶ added in v1.4.0
func (c *AsyncCache[T]) SetValueCondition(cond func(s Snapshot[T]) bool)
SetValueCondition 设置"按当前值"决定自动刷新是否执行的判断, 传 nil 取消。 与 SetCondition 是逻辑与的关系; 它能看到完整快照(Unloaded/Version/Err 等)。
// 值 <= 5 时不再周期刷新(反正每次 Read 都会穿透)
c.SetValueCondition(func(s cache.Snapshot[int]) bool { return s.Value > 5 })
func (*AsyncCache[T]) Snapshot ¶ added in v1.3.0
func (c *AsyncCache[T]) Snapshot() Snapshot[T]
Snapshot 返回当前快照(值 + 版本 + 时间 + 错误)。
func (*AsyncCache[T]) Timeout ¶ added in v1.3.0
func (c *AsyncCache[T]) Timeout() time.Duration
Timeout 返回当前单次刷新超时。
func (*AsyncCache[T]) Trigger ¶ added in v1.3.0
func (c *AsyncCache[T]) Trigger() bool
Trigger 请求一次异步刷新并立即返回。
返回 false 表示请求已被合并(已有待处理的刷新)或缓存已关闭。 与手动 Refresh 不同, Trigger 触发的刷新仍然受 Condition 约束。
func (*AsyncCache[T]) ValueOr ¶ added in v1.3.0
func (c *AsyncCache[T]) ValueOr(def T) T
ValueOr 在已有值(无论是否过期)时返回缓存值, 否则返回 def。
func (*AsyncCache[T]) View ¶ added in v1.3.0
func (c *AsyncCache[T]) View(f func(s Snapshot[T]))
View 用当前快照调用 f, 可以看到 Version/UpdatedAt/Err 等元信息。
func (*AsyncCache[T]) WaitFirst ¶ added in v1.3.0
func (c *AsyncCache[T]) WaitFirst(ctx context.Context) error
WaitFirst 阻塞到首次刷新"尝试"完成(成功或失败都算), 用于拿到确定性的初值。
func (*AsyncCache[T]) WaitFirstUpdate ¶ added in v1.3.0
func (c *AsyncCache[T]) WaitFirstUpdate(waitTime time.Duration) error
WaitFirstUpdate 是 WaitFirst 带超时的旧 API, 兼容旧错误文案。
type AsyncCacheContext ¶ added in v1.3.0
AsyncCacheContext 是 Context 的旧名字, 保留以兼容旧代码.
type CacheLRU ¶ added in v1.3.0
type CacheLRU[K comparable, V any] struct { // contains filtered or unexported fields }
CacheLRU 是线程安全、容量受限的 LRU 缓存, 支持 TTL 与带单飞的加载穿透。
实现要点:
- 命中时把节点移到表尾(MRU); 容量溢出时一次性淘汰表头的 1/8(至少 1 个), 用批量淘汰摊还链表操作成本, 代价是容量会在 [Capacity-batch+1, Capacity] 波动。
- TTL 采用惰性过期: 访问时判定, 另外可用 Cleanup 主动清理(适合配合定时器)。
- GetOrLoad 用 per-key 单飞防止缓存击穿: 同一 key 的并发加载只调用一次 loader, 共享结果; 等待者可以自己用 ctx 超时退出。
- 内部没有后台协程, 不会因为忘记释放而泄漏。
默认构造的是单分片(严格全局 LRU)。高并发场景可以用 NewShardedCacheLRU 按 key 分片, 把锁竞争拆开: 分片后每个分片独立淘汰, 容量被平均分到各分片, 因此淘汰顺序是 "分片内 LRU" 而不是全局 LRU, 总容量也会按分片向上取整而略大于请求值。 同一 key 始终落在同一分片, 因此 GetOrLoad 的单飞语义不受影响。
Example ¶
ExampleCacheLRU 演示基本读写与容量淘汰。
package main
import (
"fmt"
"github.com/474420502/cache"
)
func main() {
c := cache.NewCacheLRU[int, string](4)
for i := 1; i <= 5; i++ {
c.Set(i, fmt.Sprintf("v%d", i))
}
_, ok := c.Get(1)
fmt.Println("key 1 还在吗:", ok)
vals := c.Values()
fmt.Printf("最新 key 5 排第一: %s, 共 %d 个\n", vals[0], len(vals))
}
Output: key 1 还在吗: false 最新 key 5 排第一: v5, 共 4 个
func NewCacheLRU ¶ added in v1.3.0
func NewCacheLRU[K comparable, V any](capacity int) *CacheLRU[K, V]
NewCacheLRU 创建一个容量为 capacity 的单分片 LRU 缓存, 元素永不过期。 capacity 必须大于 0, 否则 panic。
func NewCacheLRUWithTTL ¶ added in v1.3.0
NewCacheLRUWithTTL 创建一个容量为 capacity 的单分片 LRU 缓存, Set 写入的元素 默认在 ttl 后过期; ttl <= 0 表示不过期。可用 SetWithTTL 为单个元素指定 TTL。
func NewShardedCacheLRU ¶ added in v1.3.0
func NewShardedCacheLRU[K comparable, V any](capacity int, ttl time.Duration, shards int) *CacheLRU[K, V]
NewShardedCacheLRU 创建分片 LRU 缓存, 用于降低高并发下的锁竞争。
shards <= 1 时等价于单分片; 实际分片数会被向上取整到 2 的幂(便于位与取模), 并且不会超过 capacity。容量会平均分到各分片, 每个分片至少 1。
Example ¶
ExampleNewShardedCacheLRU 演示分片容量分配。
package main
import (
"fmt"
"github.com/474420502/cache"
)
func main() {
c := cache.NewShardedCacheLRU[int, string](64, 0, 4)
defer c.Clear()
fmt.Println("分片数:", c.Shards())
fmt.Println("总容量:", c.Capacity())
}
Output: 分片数: 4 总容量: 64
func (*CacheLRU[K, V]) All ¶ added in v1.3.0
All 返回标准迭代器, 与 range 语法配合 (Go 1.23+):
for key, value := range lru.All() {
if key == target {
break // 提前退出会正常释放锁
}
}
顺序与 Range 一致; 迭代期间持有分片锁, 因此循环体内不要调用本缓存的其它方法。
Example ¶
ExampleCacheLRU_All 演示 Go 1.23+ 的迭代器。
package main
import (
"fmt"
"github.com/474420502/cache"
)
func main() {
c := cache.NewCacheLRU[int, string](8)
c.Set(1, "one")
c.Set(2, "two")
for key, value := range c.All() {
fmt.Println(key, value)
}
}
Output: 2 two 1 one
func (*CacheLRU[K, V]) Clear ¶ added in v1.3.0
func (l *CacheLRU[K, V]) Clear()
Clear 清空全部元素. 进行中的 GetOrLoad 不受影响, 其返回值仍会写入缓存。 统计计数不会被重置, 需要时调用 ResetStats。
func (*CacheLRU[K, V]) DefaultTTL ¶ added in v1.3.0
DefaultTTL 返回构造时指定的默认 TTL。
func (*CacheLRU[K, V]) GetOrLoad ¶ added in v1.3.0
func (l *CacheLRU[K, V]) GetOrLoad(ctx context.Context, key K, loader func(ctx context.Context) (V, error)) (V, error)
GetOrLoad 先查缓存, 未命中时调用 loader 加载并写入缓存(TTL 取默认值)。
同一 key 的并发调用只会触发一次 loader; 等待者如果 ctx 先结束会返回 ctx.Err()。 加载失败(含 panic)不会写入缓存, 返回的 error 会共享给所有等待者。
loader 在锁外执行, 但不要在 loader 内对同一 key 再次调用 GetOrLoad(会等待自己), 也不要在 loader 内调用本缓存的其它方法。
Example ¶
ExampleCacheLRU_GetOrLoad 演示加载穿透时的并发合并(singleflight)。
package main
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/474420502/cache"
)
func main() {
c := cache.NewCacheLRUWithTTL[int, string](16, time.Minute)
defer c.Clear()
var queries atomic.Int64
done := make(chan string, 8)
for i := 0; i < 8; i++ {
go func() {
v, _ := c.GetOrLoad(context.Background(), 1, func(ctx context.Context) (string, error) {
queries.Add(1)
time.Sleep(20 * time.Millisecond)
return "row", nil
})
done <- v
}()
}
for i := 0; i < 8; i++ {
<-done
}
fmt.Println("8 个并发只查询了", queries.Load(), "次")
}
Output: 8 个并发只查询了 1 次
func (*CacheLRU[K, V]) GetOrLoadWithTTL ¶ added in v1.3.0
func (l *CacheLRU[K, V]) GetOrLoadWithTTL(ctx context.Context, key K, ttl time.Duration, loader func(ctx context.Context) (V, error)) (V, error)
GetOrLoadWithTTL 与 GetOrLoad 相同, 但为本次加载指定 TTL。
func (*CacheLRU[K, V]) Keys ¶ added in v1.3.0
func (l *CacheLRU[K, V]) Keys() []K
Keys 返回全部未过期的 key。
单分片时按最近使用到最久未用的顺序返回; 分片时按分片拼接, 全局顺序无意义。
func (*CacheLRU[K, V]) Range ¶ added in v1.3.0
Range 遍历未过期元素, f 返回 false 时停止。
单分片时按最近使用到最久未用的顺序遍历; 分片时按分片遍历, 全局顺序无意义。 f 在持有分片锁的情况下执行, 因此 f 内不要调用本缓存的其它方法。
func (*CacheLRU[K, V]) ResetStats ¶ added in v1.3.0
func (l *CacheLRU[K, V]) ResetStats()
ResetStats 把命中/淘汰等计数清零(不影响缓存内容)。
func (*CacheLRU[K, V]) Set ¶ added in v1.3.0
func (l *CacheLRU[K, V]) Set(key K, value V)
Set 写入元素, TTL 取构造时的默认值, 并把该 key 标记为最近使用。
func (*CacheLRU[K, V]) SetIfAbsent ¶ added in v1.3.0
SetIfAbsent 仅在 key 不存在(或已过期)时写入, 返回是否写入成功。
func (*CacheLRU[K, V]) SetWithTTL ¶ added in v1.3.0
SetWithTTL 写入元素并指定 TTL; ttl <= 0 表示永不过期。
type Condition ¶ added in v1.3.0
type Condition func(info RefreshInfo) bool
Condition 决定一次自动刷新是否真的执行.
type Context ¶ added in v1.3.0
type Context[T any] struct { // contains filtered or unexported fields }
Context 是一次刷新的上下文. 所有方法都可以安全并发调用.
func (*Context[T]) Current ¶ added in v1.3.0
func (c *Context[T]) Current() T
Current 返回本次刷新开始前的值; 首次刷新(或从未成功过)时是 T 的零值.
func (*Context[T]) Done ¶ added in v1.3.0
func (c *Context[T]) Done() <-chan struct{}
Done 等价于 c.Ctx().Done().
func (*Context[T]) GetCurrent ¶ added in v1.3.0
func (c *Context[T]) GetCurrent() T
GetCurrent 是 Current 的旧名字, 保留以兼容旧代码.
func (*Context[T]) HasCurrent ¶ added in v1.3.0
HasCurrent 报告本次刷新开始前是否已经有成功的值.
type ErrorHandler ¶ added in v1.3.0
ErrorHandler 是刷新失败时的回调, err 同时可以通过 ctx.Error() 获取.
type LRUStats ¶ added in v1.3.0
type LRUStats struct {
Len int // 当前元素个数
Capacity int // 容量上限(分片时是各分片之和)
Shards int // 分片数
Hits uint64 // Get/Peek/GetOrLoad 命中
Misses uint64 // 未命中(含已过期)
Evictions uint64 // 因容量淘汰
Expirations uint64 // 因 TTL 过期被惰性删除
Loads uint64 // GetOrLoad 成功加载
LoadErrors uint64 // GetOrLoad 失败(含 panic)
}
LRUStats 是 CacheLRU 的累计统计(自创建或上次 ResetStats 起)。
type Loader ¶ added in v1.3.0
Loader 是刷新数据的函数.
ctx 携带本次刷新的上下文: Cancel/Deadline、参数 Params、上一次的值 Current 与 上一次的快照 Snapshot。返回的 error 非 nil 时不会覆盖旧值, 只会记录错误。
Loader 会在后台刷新协程中串行执行, 因此实现里不要调用 Close; 另外应当尊重 ctx 的取消与超时(WithTimeout), 否则 Close 与超时都无法及时生效。
也不要在 Loader 内调用 Refresh/ForceUpdate: 那会等待自己发起的那次刷新。
type Option ¶ added in v1.3.0
type Option func(*Options)
Option 用于配置 AsyncCache:
c := cache.NewAsync(time.Minute, loader, cache.WithTimeout(5*time.Second), cache.WithJitter(0.2), )
也可以直接写匿名函数:
c := cache.NewAsync(time.Minute, loader, func(o *cache.Options) {
o.Timeout = 5 * time.Second
})
func WithCondition ¶ added in v1.3.0
WithCondition 设置自动刷新的前置条件(只能看到状态, 见 Condition).
func WithInterval ¶ added in v1.3.0
WithInterval 设置后台刷新间隔.
func WithJitter ¶ added in v1.3.0
WithJitter 设置间隔抖动比例, 取值 [0,1].
func WithLazyStart ¶ added in v1.3.0
func WithLazyStart() Option
WithLazyStart 表示构造后不立即刷新, 等第一个间隔或手动触发.
func WithReadPolicy ¶ added in v1.4.0
func WithReadPolicy[T any](policy ReadPolicy[T]) Option
WithReadPolicy 设置默认读策略, 配合 Read 使用:
// 值大于 5 时读缓存; 值 <= 5 时每次 Read 都同步穿透数据源
c := cache.NewAsync(30*time.Second, load,
cache.WithReadPolicy(func(s cache.Snapshot[int]) bool { return s.Loaded && s.Value > 5 }))
v, err := c.Read(ctx)
不配置策略时 Read 的默认语义是"有值即返回, 无值同步加载一次"。 T 必须与 AsyncCache 的类型参数一致, 否则 NewAsync 会 panic。
func WithValueCondition ¶ added in v1.4.0
WithValueCondition 设置"按当前值"决定自动刷新是否执行的判断, 与 WithCondition 是逻辑与的关系; 运行期可用 SetValueCondition 替换。
// 值大于 5 才周期刷新; 值 <= 5 时不再后台拉取
c := cache.NewAsync(30*time.Second, load,
cache.WithValueCondition(func(s cache.Snapshot[int]) bool { return s.Value > 5 }))
T 必须与 AsyncCache 的类型参数一致, 否则 NewAsync 会 panic。
type Options ¶ added in v1.3.0
type Options struct {
// Interval 是后台自动刷新的间隔. <=0 表示不做定时刷新, 只做首次刷新与手动刷新,
// 适合"按事件/条件驱动"的场景.
//
// 语义是"固定延迟": 下一次刷新在本次刷新结束之后才开始计时, 因此慢数据源不会
// 造成刷新堆积; 若一次刷新耗时超过间隔, 实际间隔就是刷新耗时。
Interval time.Duration
// Timeout 是单次刷新的超时时间, <=0 表示不限制.
// 超时通过传给 Loader 的 context 实现, 因此 Loader 必须尊重 ctx.
Timeout time.Duration
// Jitter 是间隔抖动比例, 取值 [0,1]. 实际间隔在
// [Interval*(1-Jitter/2), Interval*(1+Jitter/2)) 之间随机,
// 用于避免大量实例在同一时刻集中刷新(惊群).
Jitter float64
// Lazy 为 true 时不立即刷新首次, 等到第一个间隔或手动触发.
Lazy bool
// AutoStop 为 true 时, AsyncCache 被 GC 回收时自动关闭后台协程.
// 依赖 runtime.SetFinalizer, 不保证进程退出前一定执行, 推荐显式 Close.
AutoStop bool
// Params 是传给 Loader 的初始参数, 运行期可用 SetParams 替换.
Params any
// Condition 只在自动刷新(定时/Trigger 之外的后台刷新)时生效:
// 返回 false 表示本次跳过, 快照与成功时间保持不变. 手动 Refresh 不受其约束.
//
// 它只能看到"状态"(时间/参数/失败次数); 若要按"当前值"决定是否刷新,
// 用 WithValueCondition. 两者都设置时必须同时为真才会刷新.
Condition Condition
// OnError 在刷新失败(含 Loader panic)时回调. 回调在刷新协程中同步执行,
// 不要在其中做阻塞操作, 也不要调用 Close.
OnError func(err error)
// contains filtered or unexported fields
}
Options 是 AsyncCache 的配置集合. 零值语义: Interval=0(不自动刷新, 仅首次+手动), Timeout=0(不限制), Jitter=0, Lazy=false(立即刷新首次), AutoStop=false, Params=nil, Condition=nil, OnError=nil.
type ReadPolicy ¶ added in v1.4.0
ReadPolicy 决定一次读取是否可以由缓存直接满足: 返回 true 表示直接用当前快照(不阻塞), false 表示本次读取同步刷新数据源。
典型用法是"按当前值分档"(见 WithReadPolicy 的示例): 值在安全区间走缓存, 进入敏感区间就每次直连数据源。
type RefreshInfo ¶ added in v1.3.0
type RefreshInfo struct {
Now time.Time // 当前时间
Params any // 当前参数
Interval time.Duration // 当前刷新间隔
Version uint64 // 已成功刷新次数
Loaded bool // 是否已有值
UpdatedAt time.Time // 上次成功时间
RefreshedAt time.Time // 上次尝试时间
Err error // 上次刷新错误
ConsecutiveFailures uint64 // 连续失败次数
}
RefreshInfo 描述一次自动刷新之前的缓存状态, 供 Condition 判断.
type Snapshot ¶ added in v1.3.0
type Snapshot[T any] struct { Value T // 最近一次成功刷新的值 Version uint64 // 成功刷新的次数, 从 0 开始递增 Loaded bool // 是否至少成功刷新过一次(Value 是否有效) UpdatedAt time.Time // 最近一次成功刷新的时间 RefreshedAt time.Time // 最近一次尝试刷新的时间(即使失败也会更新) Err error // 最近一次刷新的错误; 成功时为 nil }
Snapshot 是 AsyncCache 在某个时刻对外的只读视图.
快照一旦发布就不会再被修改; 若 T 是引用类型(切片/指针/map), 其内部状态由 Loader 负责保证不可变, 或者由调用方自行加锁.
type Stats ¶ added in v1.3.0
type Stats struct {
Version uint64 // 当前成功刷新次数
Refreshes uint64 // 成功刷新次数
Failures uint64 // 失败刷新次数(含 Loader panic)
Skipped uint64 // 被 Condition/ValueCondition 跳过的自动刷新次数
ConditionErrors uint64 // Condition/ValueCondition panic 次数
PassThroughs uint64 // Read 因策略判定而同步穿透数据源的次数
ConsecutiveFailures uint64 // 连续失败次数, 成功后归零
LastAttempt time.Time // 最近一次尝试刷新
LastSuccess time.Time // 最近一次成功刷新
LastError error // 最近一次刷新错误
Closed bool // 是否已 Close
}
Stats 是 AsyncCache 的运行统计. 字段都在一次原子读中获取, 可能存在极小的 时间偏差.