Help us shape the future of Go! Take the 2026 Go Developer Survey

twolevel

package
v0.19.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package twolevel 是 oekit 的两级缓存组合件:L1 本地缓存 + L2 任意 CacheAside 远端缓存(典型为 redisx/cache.RedisCache)的读穿写透装饰器, 以及跨实例 L1 失效广播(Bus,Redis pubsub)。

TwoLevelCache 语义分层

  • L1 只缓存真值(命中即返回,不打 L2);null 防穿透完全委托 L2 的 null marker(Source 返回 (nil, nil) 时 marker 落在 L2,后续 GetOrLoad 每次 L1 miss 后查一跳 L2 即被 marker 挡住,不回源);
  • GetOrLoad/Get 在 L2 命中后回填 L1(TTL 归 L1 自身配置);
  • Set/SetWithTTL 写穿两级;SetWithTTL 的显式 TTL 同时作用于 L1 与 L2(否则 L1 长驻会吞掉显式短 TTL 的过期语义);
  • Del/DelBatch 双删两级,顺序固定先远端后本地:远端失败时本地已删, 读路径回填的是远端旧值(短窗口,TTL 兜底);反序则本地旧值会活过 整个 L1 TTL;
  • Refresh 走 L2 删除重载(marker 一并清除),新值回填 L1。

Bus 失效广播(多实例部署必备)

单实例的 Del 只能删掉自己的 L1——其他实例的 L1 副本要靠 TTL 才能过期。 Bus 在 Del/DelBatch 时向 Redis 频道发布失效消息,各实例的订阅循环收到 后驱逐自己的 L1 副本(含发布者自身,幂等)。只广播失效、不广播回填: Set/回填意味着 L2 有了新值,他实例旧 L1 由下一次写路径广播驱逐或 TTL 兜底——这与 cache-aside 的整体语义一致。

与 cached 编排的组合

TwoLevelCache 实现 CacheAside[T],可直接作为 cached.NewRepository 后端 工厂的产物:cached 管「写后失效编排」,twolevel 管「后端形态」——

repo := cached.NewRepository[User](rawRepo, func(src cache.SourceFunc[User]) cache.CacheAside[User] {
    return twolevel.NewTwoLevelCache(localL1, redisL2With(src), twolevel.WithBus[User](bus))
}, ...)

cached 写路径的 DelBatch 会经 TwoLevelCache 双删两级并广播给全部实例。

值语义沿用 pkg/cache 实现契约:返回值一律只读对待(L1 共享内部引用, L2 序列化型后端每次新副本);回填 best-effort,失败记日志不阻断。

TTL 前提(务必遵守)

上述一切"短窗口 + TTL 兜底"的论证都建立在 **L1/L2 TTL 均为有限值** 之上:LocalCache 的 TTL=0 表示永不过期——多实例部署下丢失一条失效 广播,该键的 L1 陈旧就不再有界。两级缓存场景禁止 TTL=0。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bus

type Bus struct {
	// contains filtered or unexported fields
}

Bus 是跨实例 L1 失效广播:Del/DelBatch 时向 Redis 频道发布失效消息, 订阅循环收到后驱逐本实例的 L1 副本(含发布者自身,幂等)。

只广播失效、不广播回填:Set/回填意味着 L2 有了新值,他实例旧 L1 由 下一次写路径广播驱逐或 TTL 兜底——与 cache-aside 的整体语义一致。 发布是 best-effort:失败记日志不阻断删除(远端实例靠 TTL 兜底)。

生命周期由 TwoLevelCache 管理(WithBus 挂接 + Start/Close),不单独 Start/Stop;单生命周期(Close 后复用需新建 Bus——ctx watcher 的 done 通道一次性)。

func NewBus

func NewBus(client *redisx.Client, channel string) *Bus

NewBus 创建失效广播。client 用于 Publish 与 Subscribe(redisx.Client 的键转换规则对两端一致);channel 建议按实体类型/键空间划分。

type CachedOption

type CachedOption[T any] func(*config[T])

CachedOption 配置 TwoLevelCache(命名对齐仓内硬规则,避免裸 Option)。

func WithBus

func WithBus[T any](bus *Bus) CachedOption[T]

WithBus 挂接失效广播:Del/DelBatch 时向 Redis 频道发布失效消息, Start 后订阅循环驱逐本实例(及其他实例)的 L1 副本。多实例部署必备; 单实例可不挂。

type TwoLevelCache

type TwoLevelCache[T any] struct {
	// contains filtered or unexported fields
}

TwoLevelCache 是两级缓存装饰器:L1 本地缓存 + L2 远端 CacheAside (如 redisx/cache.RedisCache)读穿写透组合。实现 cache.CacheAside[T], 可作为 cached.NewRepository 的后端工厂产物,也可独立使用。

语义分层见包文档;Del 先远后本的理由、null marker 委托 L2 的机制、 SetWithTTL 双层传导的必要性均在类型与包文档中固化。

func NewTwoLevelCache

func NewTwoLevelCache[T any](
	local cache.Cache[T],
	remote cache.CacheAside[T],
	opts ...CachedOption[T],
) (*TwoLevelCache[T], error)

NewTwoLevelCache 创建两级缓存。local/remote 均必填,构造期 nil 检查 fail-fast(而非首个操作时深层 panic);remote 必须自带 Source (Cache-Aside 回源配置在远端实现上,本类型不重复持有回源函数)。

func (*TwoLevelCache[T]) Close

func (c *TwoLevelCache[T]) Close(ctx context.Context) error

Close 停止订阅循环(若有)并透传 L1 的 Close(ctx) error(Once 幂等, 重复调用安全;L1 不实现 Close 时为 no-op)。

func (*TwoLevelCache[T]) Del

func (c *TwoLevelCache[T]) Del(ctx context.Context, key string) error

Del 双删两级(先远端后本地,理由见包文档);挂 bus 时发布失效消息 (best-effort,发布失败由远端实例 TTL 兜底)。key 不存在时幂等不报错。

func (*TwoLevelCache[T]) DelBatch

func (c *TwoLevelCache[T]) DelBatch(ctx context.Context, keys ...string) error

DelBatch 双删两级(批量口径同 Del)。空 keys 为幂等 no-op。

func (*TwoLevelCache[T]) Exists

func (c *TwoLevelCache[T]) Exists(ctx context.Context, key string) (bool, error)

Exists 存在性检查:L1 命中为 true,否则查 L2(L2 的 null marker 计存在)。

func (*TwoLevelCache[T]) Get

func (c *TwoLevelCache[T]) Get(ctx context.Context, key string) (*T, error)

Get 读穿获取:L1 命中直接返回;L2 命中后回填 L1 并返回。

func (*TwoLevelCache[T]) GetOrLoad

func (c *TwoLevelCache[T]) GetOrLoad(ctx context.Context, key string) (*T, error)

GetOrLoad 读穿回源:L1 命中直接返回;L1 miss 时 L2 负责回源与 null marker 防穿透,拿到真值后回填 L1。Source 返回 (nil, nil) 时透传 (nil, nil),L1 不缓存 null。

func (*TwoLevelCache[T]) MGet added in v0.8.0

func (c *TwoLevelCache[T]) MGet(ctx context.Context, keys ...string) (map[string]*T, error)

MGet 跨层批量读:L1 具备批量能力则单次锁区间取回,缺席键交 L2 单次 批量(两层都不具备时退化为逐键读),L2 命中逐键回填 L1。缺席语义与 (nil, nil) 双关一致:不存在与 null marker 一律缺席——L2 的 marker 短路 不参与 MGet(纯读),缺席键的消费方应回退 GetOrLoad。

func (*TwoLevelCache[T]) Refresh

func (c *TwoLevelCache[T]) Refresh(ctx context.Context, key string) (*T, error)

Refresh 刷新:清 L1 后走 L2 的删除重载(null marker 一并清除),新值回填 L1。

func (*TwoLevelCache[T]) Set

func (c *TwoLevelCache[T]) Set(ctx context.Context, key string, value *T) error

Set 写穿两级(各自使用实现的默认 TTL)。

func (*TwoLevelCache[T]) SetWithTTL

func (c *TwoLevelCache[T]) SetWithTTL(ctx context.Context, key string, value *T, ttl time.Duration) error

SetWithTTL 写穿两级,显式 TTL 同时作用于 L1 与 L2(否则 L1 长驻会 吞掉显式短 TTL 的过期语义)。

func (*TwoLevelCache[T]) Start

func (c *TwoLevelCache[T]) Start(ctx context.Context) error

Start 启动失效广播订阅循环(无 bus 时为 no-op)。调用方负责在应用 生命周期内持有 ctx;Close 或 ctx 取消都会停止循环。

注意:Start 本身不报错——go-redis 的订阅错误是异步的,Redis 不可达时 循环会静默重连(Channel 自带重连);可观测性靠 Bus 内部日志。 Bus 为单生命周期:Close 后复用需新建(WithBus 重新挂接)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL