Skip to content

分布式缓存

分布式缓存(Distributed Cache) 是一种跨多个节点存储和访问数据的缓存机制。Spire 提供了统一的缓存接口,支持多种过期策略、自动清理机制以及灵活的配置选项。未来,我们将通过集成更成熟的缓存产品来实现该接口,以满足用户多样化的使用场景需求。目前,Spire 内置了一套轻量级的内存缓存实现方案。

快速启动

只需 4 步即可使用分布式缓存:

cangjie
import spire_extensions_caching.*
import spire_extensions_options.*

// 1. 创建默认的内存缓存实例
let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))

// 2. 设置缓存项
cache.setString("access_token", "fkgyzj&2j&62jlg==")

// 3. 获取缓存项
let access_token = cache.getString("access_token")

// 4. 删除缓存项
cache.remove("access_token")

过期策略配置

三种不同过期策略选项

策略类型说明典型场景
slidingExpiration 滑动过期每次访问重置计时器会话、Token
absoluteExpiration 绝对过期固定时间点过期临时验证码
absoluteExpirationRelativeToNow 相对过期从设置时刻起计时过期缓存数据

绝对过期与相对过期策略

适用于验证码派发场景

cangjie
import std.time.*
import spire_extensions_options.Options

main() {
    let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))

    //多个过期策略,满足其中一个过期策略,就会被标记为垃圾对象
    cache.setString("access_token", "fkgyzj&2j&62jlg==", DistributedCacheEntryOptions(
        absoluteExpiration: DateTime.now() + Duration.second * 10, // 绝对过期时间点:10秒后
        absoluteExpirationRelativeToNow: Duration.second * 5 // 相对过期:5秒后
    ))

    println("初次写入:access_token -> fkgyzj&2j&62jlg==")
    sleep(Duration.second * 3)
    println("3秒后访问:access_token = " + (cache.getString("access_token") ?? "None"))

    sleep(Duration.second * 3)
    println("再等3秒访问:access_token = " + (cache.getString("access_token") ?? "None"))
}
bash
初次写入:access_token -> fkgyzj&2j&62jlg==
3秒后访问:access_token = fkgyzj&2j&62jlg==
再等3秒访问:access_token = None

组合策略

使用混合策略时,满足其中一个过期策略即为过期

滑动过期策略

适用于会话场景

cangjie
import std.time.*
import spire_extensions_options.Options

main() {
    let cache = MemoryDistributedCache(Options.create(DistributedCacheOptions()))

    // 设置滑动过期时长为5s
    cache.setString("access_token", "fkgyzj&2j&62jlg==", DistributedCacheEntryOptions(slidingExpiration: Duration.second * 5))

    // 有效期限内访问会重置滑动过期计时器
    println("初次写入:access_token -> fkgyzj&2j&62jlg==")
    sleep(Duration.second * 3)
    println("3秒后访问:access_token = " + (cache.getString("access_token") ?? "None"))

    // 此时由于滑动计时器被重置为5s,因此任可访问
    sleep(Duration.second * 4)
    println("再等4秒访问:access_token = " + (cache.getString("access_token") ?? "None"))

    //由于距离上次访问时间超过5s,被标记为垃圾对象
    sleep(Duration.second * 6)
    println("再等6秒访问:access_token = " + (cache.getString("access_token") ?? "None"))
}
bash
初次写入:access_token -> fkgyzj&2j&62jlg==
3秒后访问:access_token = fkgyzj&2j&62jlg==
再等4秒访问:access_token = fkgyzj&2j&62jlg==
再等6秒访问:access_token = None

滑动过期策略内部使用 refresh 刷新缓存项:

垃圾回收

  1. 访问指定 key 的缓存项时,如果检查为失效会被立即销毁
  2. 对于没有被访问到的缓存项,如果过期会被标记为垃圾对象,等待内置的销毁线程去回收
  3. 内置的销毁线程启动策略是任意 key 被访问,并且距离上次扫描时间间隔大于配置的时间间隔(默认为 1 小时)
  4. 可以通过 DistributedCacheOptions 来配置扫描的时间间隔

性能提示

垃圾回收线程是触发式的,这样做可以达到性能最佳,避免机械式的扫描

容器集成

通过与容器集成可以更方便的通过依赖注入的方式来使用

cangjie
// 原代码
let services = ServiceCollection()

// 注册分布式缓存服务
services.addDistributedMemoryCache{ configureOptions => 
    configueOptions.expirationScanFrequency = Duration.second * 180
} 

let provider = services.build()

//手动解析服务,或者使用构造注入
let cache = provider.getOrThrow<IDistributedCache>()

说明

后续我们会提供 Redis、MemoryCache 之类的分布式缓存,通过类似 addDistributedRedisCache() 即可替换,无需修改业务代码

最佳实践

  • 合理设置过期时间:会话用滑动过期,验证码用相对过期
  • 监控内存使用:定期检查缓存项数量,防止溢出
  • 依赖注入集成:推荐通过 DI 管理缓存实例
  • 错误处理:注意 get/getString 可能返回 None
  • 清理频率调整:根据业务需求调整 expirationScanFrequency

注意事项

  • 线程安全:所有操作均为线程安全
  • 内存限制:大数据量场景需关注内存占用
  • 过期策略组合:可同时设置多种过期条件,最先满足者生效
  • refresh 仅重置滑动过期,不影响绝对过期
  • 删除/过期项自动清理,无需手动干预