expire_cache 0.1.16

High-performance generational cache for Rust / 高性能分代缓存
Documentation
# expire_cache: 高性能分代缓存

`expire_cache` 实现了基于分代收集策略的高效过期缓存。它不追踪单个条目的过期时间,而是维护两个数据桶(代),显著降低过期检查的内存开销和 CPU 使用率。

## 目录

- [特性]#特性
- [安装]#安装
- [快速开始]#快速开始
- [API 参考]#api-参考
- [设计架构]#设计架构
- [技术堆栈]#技术堆栈
- [目录结构]#目录结构
- [历史背景]#历史背景

## 特性

- **高性能**:每条目摊销 O(1) 过期开销
- **并发访问**:基于 DashMap 的线程安全操作
- **异步支持**:原生异步初始化 `get_or_init_async`
- **灵活存储**:支持键值映射和集合
- **简洁 API**:清晰的 `get``insert` 和初始化接口

## 安装

在 `Cargo.toml` 中添加:

```toml
[dependencies]
expire_cache = "0.1.15"
```

按需启用特性:

```toml
[dependencies]
expire_cache = { version = "0.1.15", features = ["dashmap", "get_or_init_async"] }
```

可用特性:
- `dashmap`:启用 DashMap 支持
- `dashset`:启用 DashSet 支持
- `get_or_init`:启用同步初始化
- `get_or_init_async`:启用异步初始化

## 快速开始

### 基本映射用法

```rust
use expire_cache::Expire;
use dashmap::DashMap;
use std::time::Duration;

#[tokio::main]
async fn main() {
  let cache: Expire<DashMap<&str, &str>> = Expire::new(60);
  
  cache.insert("键", "值");
  
  if let Some(val) = cache.get("键") {
    println!("找到: {}", *val);
  }
  
  // 等待过期
  tokio::time::sleep(Duration::from_secs(65)).await;
  assert!(cache.get("键").is_none());
}
```

### 异步初始化

```rust
use expire_cache::{AsyncInit, Expire};
use dashmap::DashMap;

struct 数据库加载器;

impl AsyncInit for 数据库加载器 {
  type Key = &'static str;
  type Val = String;
  type Error = aok::Error;

  async fn init(&self, key: &Self::Key) -> Result<Self::Val, Self::Error> {
    // 模拟数据库查询
    Ok(format!("数据_{}", key))
  }
}

#[tokio::main]
async fn main() -> aok::Result<()> {
  let cache: Expire<DashMap<&str, String>> = Expire::new(60);
  
  let value = cache.get_or_init_async::<数据库加载器>("用户_123", 数据库加载器).await?;
  println!("已加载: {}", *value);
  
  Ok(())
}
```

### 集合用法

```rust
use expire_cache::Expire;
use dashmap::DashSet;

#[tokio::main]
async fn main() {
  let cache: Expire<DashSet<&str>> = Expire::new(60);
  
  cache.insert("活跃会话", ());
  
  if cache.get("活跃会话").is_some() {
    println!("会话存在");
  }
}
```

## API 参考

### 核心类型

#### `Expire<T: Map>`

提供过期功能的主缓存包装器。

**方法:**
- `new(expire: u64) -> Self`:创建带过期周期的缓存(秒)
- `get(&self, key) -> Option<RefVal>`:从缓存检索值
- `insert(&self, key, val)`:向缓存插入值
- `get_or_init(&self, key, func) -> Result<RefVal, E>`:同步初始化
- `get_or_init_async(&self, key, init) -> Result<RefVal, E>`:异步初始化

#### `Map` Trait

存储后端的核心抽象。

**必需方法:**
- `clear(&self)`:清除所有条目
- `insert(&self, key, val)`:插入键值对
- `get(&self, key) -> Option<RefVal>`:按键获取值

#### `AsyncInit` Trait

异步值初始化接口。

**必需方法:**
- `init(&self, key) -> impl Future<Output = Result<Val, Error>>`:初始化值

## 设计架构

### 分代收集策略

缓存使用双缓冲方法,包含两个代:

```mermaid
graph TD
    A[新插入] --> B[活跃代]
    B --> C[检查活跃代]
    C -->|找到| D[返回值]
    C -->|未找到| E[检查被动代]
    E -->|找到| D
    E -->|未找到| F[返回空]
    
    G[定时任务] --> H[清空被动代]
    H --> I[交换活跃/被动角色]
    I --> B
    
    style B fill:#e1f5fe
    style H fill:#fff3e0
```

### 数据流

1. **插入**:新条目总是进入活跃代
2. **查找**:先检查活跃代,再检查被动代
3. **过期**:后台任务定期清空被动代并交换角色
4. **生命周期**:条目存活时间在 `expire``2 * expire` 秒之间

### 内存管理

缓存使用原子操作进行代切换,利用 `boxleak` 实现内部结构的稳定内存分配。

## 技术堆栈

- **核心语言**:Rust 2024 Edition
- **并发**:DashMap 实现无锁并发访问
- **异步运行时**:Tokio 处理异步操作和定时器管理
- **内存管理**`boxleak` 稳定分配,`sendptr` 安全指针共享
- **错误处理**`aok` 轻量级错误类型

## 目录结构

```
src/
├── lib.rs              # 公共 API 导出和核心 Expire 结构体
├── map.rs              # DashMap 实现和 RefVal 包装器
├── set.rs              # DashSet 实现
├── get_or_init.rs      # 同步初始化 trait
└── get_or_init_async.rs # 异步初始化 trait

tests/
└── main.rs             # 综合测试套件
```

## 历史背景

分代缓存的概念借鉴了硬件设计和垃圾收集策略的智慧。早期的大型机系统如 IBM System/360 引入缓存以弥合 CPU 与内存的速度差异。随着时间的推移,策略从简单的 LRU(最近最少使用)演变为更复杂的分代方法。

在硬件设计中,"缓存衰减"技术通过关闭长时间未访问的缓存行来降低功耗。类似地,`expire_cache` 将时间区间视为代。通过批量丢弃整代数据,它避免了追踪单个条目时间戳的巨大开销——这让人联想到分代垃圾收集器,它们假设"年轻"对象往往早夭,因此可以批量回收。

这种方法牺牲了绝对精度(精确的过期时间),换取了显著的吞吐量提升和内存碎片减少,使其非常适合对近似过期时间可接受的高吞吐量场景。

## 许可证

MulanPSL-2.0