# expire_cache

高效的双缓冲过期缓存。

`expire_cache` 实现了一种基于“双缓冲”（或分代）策略的高性能缓存。它不追踪单个条目的过期时间，而是维护两个数据桶（代）。这种方法显著降低了过期检查的内存开销和 CPU 使用率，非常适合对精确过期时间要求不高但追求高吞吐量的场景。

## 特性

- **高性能**：使用双缓冲策略，每条目的过期开销为摊销 O(1)。无需后台扫描所有条目。
- **并发安全**：基于 `DashMap` 构建，支持高并发访问。
- **异步支持**：支持 `get_or_init_async` 进行异步值初始化。
- **灵活**：支持键值缓存 (`DashMap`) 和集合缓存 (`DashSet`)。
- **简单 API**：易于使用的 `get`, `insert`, `get_or_init` 接口。

## 安装

在 `Cargo.toml` 中添加：

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

启用特定特性：

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

可用特性：
- `dashmap`: 启用 `DashMap` 支持（默认）。
- `dashset`: 启用 `DashSet` 支持。
- `get_or_init`: 启用同步 `get_or_init`。
- `get_or_init_async`: 启用异步 `get_or_init_async`。

## 使用方法

### 基本用法 (Map)

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

#[tokio::main]
async fn main() {
    // 创建一个过期周期为 60 秒的缓存
    let cache: Expire<DashMap<String, String>> = Expire::new(60);

    cache.insert("key".to_string(), "value".to_string());

    if let Some(val) = cache.get("key") {
        println!("Found: {}", *val);
    }
}
```

### 异步初始化 (`get_or_init_async`)

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

struct DbInit;

impl Map for DbInit {
    type Key = String;
    type Val = String;
    type RefVal<'a> = &'a String;

    fn clear(&self) {}
    fn insert(&self, _key: Self::Key, _val: Self::Val) {}
    fn get<'a>(&'a self, _key: &Self::Key) -> Option<Self::RefVal<'a>> {
        None
    }
}

impl Default for DbInit {
    fn default() -> Self {
        Self
    }
}

impl AsyncInit for DbInit {
    type Error = anyhow::Error;

    async fn init(_key: &Self::Key) -> Result<Self::Val, Self::Error> {
        // 模拟异步工作，例如数据库查询
        Ok("computed_value".to_string())
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cache: Expire<DashMap<String, String>> = Expire::new(60);

    let val = cache.get_or_init_async::<DbInit>("key").await?;

    println!("Value: {}", *val);
    Ok(())
}
```

### 集合用法 (Set)

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

#[tokio::main]
async fn main() {
    let set: Expire<DashSet<String>> = Expire::new(60);

    set.insert("item".to_string(), ());

    if set.get("item").is_some() {
        println!("Item exists");
    }
}
```

## 工作原理

1.  **双缓冲**：缓存内部维护两个底层容器（例如 `DashMap`），我们称之为 `A` 和 `B`。
2.  **活跃与被动**：在任何时候，其中一个是“活跃”的（接收新插入），另一个是“被动”的（只读，包含较旧的数据）。
3.  **读取**：`get` 操作首先检查活跃容器。如果未找到，再检查被动容器。
4.  **写入**：`insert` 总是写入活跃容器。
5.  **轮转**：每隔 `expire` 秒，后台任务会清空被动容器，并交换 `A` 和 `B` 的角色。之前的活跃容器变为被动容器（将其数据保留一个周期），而被清空的容器变为新的活跃容器。

这意味着一个条目的存活时间至少为 `expire` 秒，至多为 `2 * expire` 秒。

## 历史小故事

“分代缓存”或“缓存轮转”的概念与硬件设计及垃圾回收策略中的智慧不谋而合。早期的主机系统如 IBM System/360 引入缓存以弥合 CPU 与内存的速度差异。随着时间推移，策略从简单的 LRU（最近最少使用）演变为更复杂的分代方法。

在硬件领域，“缓存衰减（Cache Decay）”技术通过关闭长时间（一个“代”）未被访问的缓存行来降低功耗。与之类似，`expire_cache` 将时间区间视为“代”。通过批量丢弃整代数据，它避免了追踪单个项目时间戳的巨大开销——这一技术让人联想到分代垃圾回收器，它们假设“年轻”对象往往早夭，因此可以批量回收。这种方法牺牲了绝对的精度（精确的过期时间），换取了巨大的吞吐量提升和内存碎片减少。

## 许可证

MulanPSL-2.0