缓存
缓存用于把读取频繁、计算昂贵、短时间内变化不大的数据暂存在更快的存储中,从而减少重复计算和下游访问开销。
在 soulsoft_extensions_caching 中,缓存能力有 4 个最重要的特性:
- 用
IDistributedCache统一抽象缓存读写接口 - 当前内置
MemoryDistributedCache作为默认实现 - 支持滑动过期、绝对过期、相对过期三种策略
- 支持通过依赖注入注册缓存,实现按接口使用、按实现切换
先建立一个整体认识
这一页最重要的不是“内存缓存怎么用”,而是“业务代码应该依赖什么”。
Spire 在缓存这一层优先提供的是统一接口 IDistributedCache。 当前默认实现是内存缓存;未来会继续支持 Redis 等实现来完成同一套接口。
这意味着:
- 业务代码只依赖
IDistributedCache - 当前可以先用内置内存实现快速落地
- 将来切到 Redis 之类的实现时,通常只需要改注册方式,不需要重写业务缓存调用
这正是这套设计的价值所在:减少开发者学习、使用和适配不同缓存产品的压力,并尽量实现零成本切换缓存实现。
最小示例
如果你只是想先把缓存跑通,可以从这个例子开始:
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
main(): Int64 {
// 创建内存缓存实现,并传入缓存系统选项。
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 写入一个字符串缓存项。
cache.setString("access_token", "token-value")
println(cache.getString("access_token") ?? "None")
// 删除缓存后再次读取会拿到 None。
cache.remove("access_token")
println(cache.getString("access_token") ?? "None")
return 0
}这个例子里有三个关键点:
- 当前直接创建的是
MemoryDistributedCache - 常见字符串场景可以直接用
setString()/getString() - 删除后再次读取会返回
None
核心类型
| 类型 | 作用 |
|---|---|
IDistributedCache | 缓存统一接口,定义 get / set / refresh / remove |
MemoryDistributedCache | 当前内置的默认实现 |
DistributedCacheEntryOptions | 单个缓存项的过期策略 |
DistributedCacheOptions | 缓存系统级选项,例如过期扫描频率 |
最常见的使用流程只有 4 步:
- 获取一个
IDistributedCache实例 - 调用
set()或setString()写入数据 - 调用
get()或getString()读取数据 - 需要时调用
refresh()或remove()
基本读写
存取字符串
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
// 创建一个可直接使用的内存缓存实例。
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
cache.setString("user.profile", "alice")
let value = cache.getString("user.profile")
println(value ?? "None")存取字节数组
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 以字节数组形式写入缓存。
cache.set("binary.data", "hello".toArray())
if (let Some(bytes) <- cache.get("binary.data")) {
// 读取到字节后再转回字符串。
println(String.fromUtf8(bytes))
}刷新与删除
refresh() 的作用是刷新滑动过期计时器;remove() 用于主动删除缓存项。
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
cache.setString("session.token", "abc123")
// refresh() 会刷新滑动过期计时。
cache.refresh("session.token")
// remove() 用于主动删除缓存项。
cache.remove("session.token")为什么要面向 IDistributedCache 编程
如果你直接在业务代码里到处依赖 MemoryDistributedCache,那未来切 Redis、切多节点缓存、切其他外部缓存产品时,替换成本会快速变高。
更推荐的方式是:
- 应用层、领域层、基础设施适配层统一依赖
IDistributedCache - 把“到底用内存、Redis 还是其他实现”的决定放到注册阶段
这样做有三个直接收益:
- 业务代码更稳定,不和某个具体缓存产品绑死
- 开发者只需要学习一套缓存调用方式
- 后续替换实现时,适配压力主要集中在基础设施注册层,而不是扩散到业务层
过期策略
缓存项支持三种过期条件。 它们可以单独使用,也可以组合使用;只要任意一个条件满足,就会过期。
| 策略 | 字段 | 适合场景 |
|---|---|---|
| 滑动过期 | slidingExpiration | 会话、在线状态、短期热点数据 |
| 绝对过期 | absoluteExpiration | 某个固定时间点必须失效的数据 |
| 相对过期 | absoluteExpirationRelativeToNow | 从写入时刻开始计时的数据 |
相对过期
import std.time.*
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 写入一个 5 分钟后过期的验证码。
cache.setString(
"sms.code",
"9527",
DistributedCacheEntryOptions(
absoluteExpirationRelativeToNow: Some(Duration.minute * 5)
)
)这类写法适合验证码、临时 token、短时结果缓存。
绝对过期
import std.time.*
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 写入一个在固定时间点失效的缓存项。
cache.setString(
"daily.report",
"ready",
DistributedCacheEntryOptions(
absoluteExpiration: Some(DateTime.nowUTC() + Duration.hour)
)
)这类写法适合“到某个时间点必须失效”的数据。
滑动过期
import std.time.*
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 只要持续访问,就会不断延长过期时间。
cache.setString(
"session.user",
"alice",
DistributedCacheEntryOptions(
slidingExpiration: Some(Duration.minute * 30)
)
)只要在窗口期内访问,滑动过期时间就会被续延。
组合策略
import std.time.*
import soulsoft_extensions_caching.*
import soulsoft_extensions_options.*
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))
// 同时设置滑动过期和最长存活时间。
cache.setString(
"login.challenge",
"value",
DistributedCacheEntryOptions(
slidingExpiration: Some(Duration.minute * 5),
absoluteExpirationRelativeToNow: Some(Duration.minute * 10)
)
)这个例子表示:
- 5 分钟内不访问会失效
- 即使一直访问,10 分钟后也一定会失效
过期清理机制
当前内置的 MemoryDistributedCache 不是简单的“只在读取时才发现过期”。 它同时有两层清理机制:
- 访问某个 key 时,如果发现已经过期,会立即移除
- 缓存里长期没有被访问到的过期项,会通过后台扫描异步清理
扫描频率由 DistributedCacheOptions.expirationScanFrequency 控制,默认值是 Duration.minute。
import std.time.*
import soulsoft_extensions_caching.*
// 调整后台过期扫描频率。
let options = DistributedCacheOptions()
options.expirationScanFrequency = Duration.minute * 30一般来说:
- 对实时性要求高,可以把扫描频率调小
- 更关注后台开销,可以把扫描频率调大
与依赖注入集成
在实际项目里,更推荐通过 DI 使用缓存,而不是在业务代码里手动 new MemoryDistributedCache(...)。
import soulsoft_extensions_caching.*
import soulsoft_extensions_injection.*
import std.time.*
let services = ServiceCollection()
// 通过 DI 注册内存缓存,并调整扫描频率。
services.addDistributedMemoryCache { options =>
options.expirationScanFrequency = Duration.minute * 30
}
// 从容器里解析统一的缓存接口。
let root = services.build()
let cache = root.getOrThrow<IDistributedCache>()
cache.setString("app.settings", "loaded")这样做的关键价值是:
- 业务侧拿到的是
IDistributedCache - 当前实现是
MemoryDistributedCache - 未来接入其他实现时,业务代码基本不用改
未来扩展:Redis 等缓存实现
当前 soulsoft_extensions_caching 内置的是 MemoryDistributedCache。 它适合:
- 单进程应用
- 本地开发
- 中小规模、无需跨节点共享的数据缓存
但从设计上,这一层并不是为了把开发者永久绑定在内存缓存上。 相反,IDistributedCache 的目的就是给后续接入 Redis 等缓存产品留出统一抽象。
未来扩展时,我们会沿着这个方向演进:
- 保持
IDistributedCache作为统一业务接口 - 新增 Redis 等实现来满足跨节点、分布式部署和更大规模缓存需求
- 通过类似
addDistributedRedisCache()的注册方式完成实现切换
对业务代码来说,理想状态是:
- 不需要重新学习另一套缓存 API
- 不需要在业务逻辑里到处写针对不同缓存产品的分支
- 只调整基础设施注册,就能完成缓存实现切换
这会显著减少开发者的学习成本、使用成本和适配压力。
使用建议
- 业务代码优先依赖
IDistributedCache,不要直接把MemoryDistributedCache写死在服务内部 - 会话类数据优先考虑滑动过期,验证码和一次性令牌优先考虑相对过期
- 对“最长有效期”明确的场景,使用组合策略更稳妥
- 当前是内存实现,大数据量或多节点场景要提前考虑后续切换到外部缓存
- 通过 DI 注册缓存,比手动创建实例更利于后续演进
注意事项
get()/getString()在 key 不存在或已过期时会返回Nonerefresh()只对滑动过期有意义,不会改变绝对过期时间- 当前默认实现是进程内内存缓存,不天然跨节点共享
- 后续如果切换到 Redis 等实现,推荐保持业务层继续面向
IDistributedCache