tibba_cache/ttl_lru_store.rs
1// Copyright 2026 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use lru::LruCache;
16use std::num::NonZeroUsize;
17use tokio::sync::RwLock;
18
19/// 用于判断缓存数据是否已过期的 trait。
20pub trait Expired {
21 /// 返回 `true` 表示数据已过期,应从缓存中移除。
22 fn is_expired(&self) -> bool;
23}
24
25/// 线程安全的 TTL + LRU 两级淘汰缓存存储。
26/// 同时支持多读或单写并发访问。
27pub struct TtlLruStore<T> {
28 cache: RwLock<LruCache<String, T>>,
29}
30
31impl<T: Expired + Clone> TtlLruStore<T> {
32 /// 创建指定容量的 TtlLruStore,容量必须大于 0。
33 pub fn new(size: NonZeroUsize) -> Self {
34 Self {
35 cache: RwLock::new(LruCache::new(size)),
36 }
37 }
38
39 /// 向缓存写入键值对。容量已满时自动淘汰最久未使用的条目。
40 pub async fn set(&self, key: &str, value: T) {
41 let mut cache = self.cache.write().await;
42 cache.put(key.to_string(), value);
43 }
44
45 /// 读取未过期的缓存值,键不存在或已过期时返回 `None`。
46 /// 内部使用 peek 而非 get,不更新 LRU 顺序,性能更优。
47 pub async fn get(&self, key: &str) -> Option<T> {
48 let cache = self.cache.read().await;
49 // 使用 peek 避免更新 LRU 顺序,读多写少场景性能更佳
50 cache.peek(key).filter(|v| !v.is_expired()).cloned()
51 }
52
53 /// 删除指定键,键不存在时为空操作。
54 pub async fn del(&self, key: &str) {
55 let mut cache = self.cache.write().await;
56 cache.pop(key);
57 }
58
59 /// 清除所有已过期的条目,应定期调用以释放内存。
60 pub async fn purge_expired(&self) {
61 let mut cache = self.cache.write().await;
62 // LruCache 不支持迭代中删除,需先收集过期键再批量移除
63 let keys: Vec<String> = cache
64 .iter()
65 .filter(|(_, v)| v.is_expired())
66 .map(|(k, _)| k.clone())
67 .collect();
68 for key in keys {
69 cache.pop(&key);
70 }
71 }
72}