分布式缓存与业务失效
约 1150 字大约 4 分钟
2025-03-03
平台 share/caches 提供“进程内缓存 → Redis → 数据源”的分层缓存,并通过事件总线传播本地失效。用于频繁读取、允许短暂最终一致的数据,不替代数据库约束和权限判定。
读取与失效流程
进程内同键并发读取通过 singleflight 合并。默认共享过期时间约 10 分钟并带随机抖动,内存层 TTL 较短;业务可以显式设置 ExpireTime。TTL 不是权限撤销立即生效的承诺。
初始化顺序
- 配置并调用
caches.InitStore(c.CacheRedis)初始化共享 Redis store。 - 创建 FastEvent,检查初始化错误。
- 在 ServiceContext 中创建业务 Cache,注册失效订阅。
- 启动 FastEvent 消费,之后才对外提供依赖缓存的业务。
同一进程中 KeyType 对应同一个缓存实例。必须使用稳定且唯一的业务命名空间;不能让相同 KeyType 对应不同泛型或加载函数,也不能每个请求重建实例。
一个带企业范围的缓存
以下文件示例提供完整的缓存类型与构造函数,加载器由本服务仓储注入:
// 本文件定义演示业务的企业隔离缓存。
package democache
import (
"context"
"time"
"gitee.com/unitedrhino/share/caches"
)
// Key 同时包含企业与对象编号,避免跨企业缓存命中。
type Key struct {
TenantCode string // 对象所属企业。
ID int64 // 对象编号。
}
// Info 保存该业务允许缓存的只读快照。
type Info struct {
Name string // 业务名称,不含凭据。
}
// New 创建一次业务缓存;load 必须按 Key 全部范围查询并返回快照。
func New(bus caches.Event, load func(context.Context, Key) (*Info, error)) (*caches.Cache[Info, Key], error) {
return caches.NewCache(caches.CacheConfig[Info, Key]{
KeyType: "demo.info.v1",
FastEvent: bus,
ExpireTime: 5 * time.Minute,
GetData: load,
})
}GetData 的回源上下文由缓存内部管理,不能只依赖入站用户上下文过滤。Key 必须携带加载所需企业、项目等范围,加载器按键显式约束查询。
读取与写后失效
在已经初始化的缓存上调用:
// 使用已校验的企业和对象编号读取缓存。
info, err := cache.GetData(ctx, Key{TenantCode: tenantCode, ID: recordID})修改数据库并成功提交后使相关缓存失效:
// nil 表示删除缓存并传播失效;必须检查错误。
err := cache.SetData(ctx, Key{TenantCode: tenantCode, ID: recordID}, nil)传入非 nil 数据可更新缓存,但必须是完整且正确的业务快照。涉及多个派生键时逐个失效,不能只刷新当前详情而保留列表或权限缓存。
事务内不要提前写入缓存,避免未提交数据被其他请求读取。若失效失败,返回、补偿和告警策略由业务明确处理;需要可靠传播时引入持久任务,不能只记一条日志当作完成。
并发与一致性边界
当前实现使用代次与发布屏障,防止本实例已失效后旧回源或异步 Redis 回填再次发布过时结果。该机制不等于跨实例强一致:网络中断、总线不可用或 Redis 操作失败仍需通过重试、TTL 与业务补偿收敛。
缓存返回的是指针对象,调用方不要原地修改共享缓存内容。需要裁剪、脱敏或计算响应时复制数据,避免一个请求改变其他请求看到的快照。
权限、设备控制等高风险操作应在关键节点重新校验权威状态;缓存未命中、业务对象不存在和底层依赖失败必须分别处理,不能全部缓存为“空对象”。
验证清单
覆盖冷热缓存、TTL、同键并发回源、写后失效、多实例通知、旧回源与失效交错,以及 Redis/总线失败。重点验证跨企业同 ID 不会混用、错误不会被伪装成合法数据、权限撤销能按业务要求收敛。
平台业务缓存和公共缓存机制在公开 ur-kit 中也有相应接入能力;独立应用按固定 SDK 版本使用,不能导入平台私有实现。
更新日志
2026/10/16 19:46
查看所有更新日志
b3b4d-docs(平台开发): 恢复有效专题并更新当前实现示例于
