Skip to main content

Cache

Struct Cache 

Source
pub struct Cache { /* private fields */ }
Expand description

Cache facade(对齐 PHP think\facade\Cache

通过全局单例 + 委托 CacheManager 提供 PHP facade 风格 API。

§使用方式

§1. 全局使用(对齐 PHP Cache::set(...) 静态调用)

use sz_rust_cache_facade::{init_default_cache, default_cache, MemoryCacheDriver};

init_default_cache(MemoryCacheDriver::new());
default_cache().set("key", "value", None).unwrap();

§2. 独立实例(用于测试隔离)

use sz_rust_cache_facade::{Cache, MemoryCacheDriver};

let cache = Cache::new();
cache.register_default(MemoryCacheDriver::new());
cache.set("key", "value", None).unwrap();

Implementations§

Source§

impl Cache

Source

pub fn new() -> Self

创建空的 Cache facade 实例

Source

pub fn register_default(&self, driver: MemoryCacheDriver)

注册默认驱动

等价于 PHP think\App::get('cache') + 注册默认 store。

Source

pub fn register_store( &self, name: impl Into<String>, driver: Box<dyn CacheDriver>, )

注册命名驱动

Source

pub fn set_default_store( &self, name: impl Into<String>, ) -> Result<(), CacheError>

设置默认驱动名

Source

pub fn set<T: Serialize>( &self, key: &str, value: T, ttl: Option<Duration>, ) -> Result<(), CacheError>

写入缓存(对齐 PHP Cache::set($name, $value, $ttl = null)

PHP Driver::set($name, $value, $ttl = null) 第 110 行:

public function set($name, $value, $expire = null): bool
{
    $this->writeTimes++;
    if (is_null($expire)) {
        $expire = $this->options['expire'];
    }
    $data = $this->serialize($value);
    // ... 写入底层存储
}
§参数
  • key:缓存键
  • value:缓存值(实现 Serialize
  • ttl:过期时间(None 永不过期,对齐 PHP $expire = null
Source

pub fn get<T: DeserializeOwned>( &self, key: &str, ) -> Result<Option<T>, CacheError>

读取缓存(对齐 PHP Cache::get($name, $default = null)

PHP Driver::get($name, $default = null) 第 90 行:

public function get($name, $default = null)
{
    $this->readTimes++;
    $value = $this->read($name);  // 读取原始字节
    if (is_null($value)) {
        return $default;
    }
    return $this->unserialize($value);  // ⚠️ numeric 返回 string
}
§泛型
  • T = String:对齐 PHP unserialize 对 numeric 返回 string 的行为
  • T = Other:通过 serde_json::from_str 还原
§PHP bug 复刻

PHP unserializeis_numeric 的值返回 string,而非 int。 调用方若想获取 i64,需自行 .parse::<i64>(),对齐 PHP 业务代码 (int) Cache::get('count') 的强转模式。

§参数
  • key:缓存键
§返回
  • Ok(Some(value)):缓存命中
  • Ok(None):缓存未命中或已过期
Source

pub fn get_or<T: DeserializeOwned>( &self, key: &str, default: T, ) -> Result<T, CacheError>

读取缓存,未命中时返回默认值(对齐 PHP Cache::get($name, $default)

Source

pub fn delete(&self, key: &str) -> Result<(), CacheError>

删除缓存(对齐 PHP Cache::delete($name)

Source

pub fn has(&self, key: &str) -> Result<bool, CacheError>

判断键是否存在(对齐 PHP Cache::has($name)

PHP Driver::has($name) 第 222 行:

public function has($name): bool
{
    return $this->read($name) !== null;
}
§注意

PHP has 通过 read 检查是否为 null,会同时检查 TTL 过期。

Source

pub fn inc(&self, key: &str, step: i64) -> Result<i64, CacheError>

自增(对齐 PHP Cache::inc($name, $step = 1)

PHP Redis 驱动直接 INCRBY;File 驱动读取 → 加减 → 写回。 本驱动默认实现采用 File 驱动行为。

§行为
  • 键不存在:初始化为 step
  • 键存在:解析为 i64 → 加 step → 写回
Source

pub fn dec(&self, key: &str, step: i64) -> Result<i64, CacheError>

自减(对齐 PHP Cache::dec($name, $step = 1)

Source

pub fn increment(&self, key: &str) -> Result<i64, CacheError>

自增 1(便捷方法,对齐 PHP Cache::inc($name) 默认参数)

Source

pub fn decrement(&self, key: &str) -> Result<i64, CacheError>

自减 1(便捷方法,对齐 PHP Cache::dec($name) 默认参数)

Source

pub fn pull<T: DeserializeOwned>( &self, key: &str, ) -> Result<Option<T>, CacheError>

读取并删除(对齐 PHP Cache::pull($name, $default = null)

PHP Driver::pull($name, $default = null) 第 332 行:

public function pull(string $name, $default = null)
{
    $result = $this->get($name, $default);
    $this->delete($name);
    return $result;
}
Source

pub fn push<T: Serialize + DeserializeOwned + PartialEq + Clone>( &self, key: &str, value: T, ttl: Option<Duration>, ) -> Result<(), CacheError>

追加到数组缓存(对齐 PHP Cache::push($name, $value, $expire = null)

PHP Driver::push($name, $value, $expire = null) 第 339-358 行:

public function push(string $name, $value, $expire = null)
{
    $data = $this->get($name, []);
    if (!is_array($data)) {
        $data = [];
    }
    $data[] = $value;
    if (count($data) > 1000) {
        array_shift($data);
    }
    $data = array_unique($data);
    $this->set($name, $data, $expire);
    return $this;
}
§行为
  • 缓存不存在 → 创建 vec![value]
  • 缓存非数组 → 创建 vec![value]
  • 缓存为数组 → 追加 value
  • 长度 > 1000 → 丢弃最旧(FIFO)
  • array_unique 去重(保留首次出现的元素)
Source

pub async fn remember<T, F>( &self, key: &str, ttl: Option<Duration>, callback: F, ) -> Result<T, CacheError>
where T: Serialize + DeserializeOwned, F: FnOnce() -> T,

缓存击穿防护读取(对齐 PHP Cache::remember($name, callable, $expire = null)

PHP Driver::remember 第 287-310 行 + PHP bug 复刻:

  1. get($name),命中直接返回
  2. 抢锁 set($name . '_lock', 1)(无 TTL,PHP 源码 bug)
  3. 等待锁释放,200ms 轮询,5 秒超时
  4. 锁释放后 get($name),命中则返回
  5. 超时仍未释放:直接调用 callback()(防止永久阻塞)
  6. 抢到锁:调用 callback()set($name, $data, $expire) → 释放锁
§PHP bug 复刻
  1. 锁 key 无 TTL:若进程崩溃,锁永久存在 → 死锁
  2. has() + get() 双查 TOCTOU:先 hasget
§异步安全

本方法为 async fn,等待锁释放时使用 tokio::time::sleep 让出 worker, 不会阻塞 tokio 运行时。

§remember_async 的差异
维度rememberremember_async
callback 类型FnOnce() -> T(同步)async fn -> T(异步)
适用场景纯计算 / 已缓存值构造IO 密集型回源(DB / HTTP)
§参数
  • key:缓存键
  • ttl:缓存过期时间
  • callback:未命中时的回调函数
§异步安全

本方法为 async fn,等待锁释放时使用 tokio::time::sleep 让出 worker, 不会阻塞 tokio 运行时。对齐 Cache::remember_async 的非阻塞行为。

Source

pub async fn remember_async<T, F, Fut>( &self, key: &str, ttl: Option<Duration>, callback: F, ) -> Result<T, CacheError>
where T: Serialize + DeserializeOwned + Clone, F: FnOnce() -> Fut, Fut: Future<Output = T>,

缓存击穿防护读取(异步 callback 版本)

Cache::remember 行为一致(同样使用 tokio::time::sleep 让出 worker), 区别在于支持异步 callback,避免在 callback 中执行阻塞 IO 时阻塞 worker。

§参数差异
维度rememberremember_async
callback 类型FnOnce() -> T(同步)async fn -> T(异步)
适用场景纯计算 / 已缓存值构造IO 密集型回源(DB / HTTP)
§参数
  • key:缓存键
  • ttl:缓存过期时间
  • callback:未命中时的异步回调函数
§用法
let user: User = cache.remember_async("user_1", Some(Duration::from_secs(60)), || async {
    // 异步回源逻辑(如 DB 查询)
    User::find_async(1).await
}).await?;
Source

pub fn clear(&self) -> Result<(), CacheError>

清空所有缓存(对齐 PHP Cache::clear()

Source

pub fn delete_many(&self, keys: &[&str]) -> Result<(), CacheError>

批量删除多个缓存 key(对齐 PHP Driver::deleteMultiple($keys): bool

PHP think\cache\Driver::deleteMultiple 第 342-351 行:

public function deleteMultiple($keys): bool
{
    foreach ($keys as $key) {
        $result = $this->delete($key);
        if (false === $result) {
            return false;
        }
    }
    return true;
}
§PHP 行为对齐
  • 逐个调用 delete(key),任一失败立即返回 Err
  • 对齐 PHP if (false === $result) return false
  • 注意:PHP File::delete 文件不存在也返回 false,导致 deleteMultiple 在文件不存在时也返回 false(PHP bug)。Rust 端 delete 对不存在的 key 返回 Ok(()),因此 delete_many 对不存在的 key 不会失败(修正 PHP bug)。
§业务场景对齐

对齐业务场景 4(一次写操作失效多类缓存):

// addons/sdp/model/Category.php
Cache::delete('sdp_category_tree');
Cache::delete('sdp_category_select');
Cache::delete('sdp_category_child');
Cache::delete('sdp_category_nav');
Cache::delete('sdp_category_info:'.$data['cat_id']);

Rust 端用 delete_many 一次调用:

cache.delete_many(&["sdp_category_tree", "sdp_category_select", "sdp_category_child"])?;
Source

pub fn invalidate_after_write(&self, keys: &[&str]) -> Result<(), CacheError>

写操作后失效缓存(对齐 PHP 业务场景 1:事务内写后失效)

PHP 业务代码典型模式(app/food/model/cashier/Clerk.php):

public function add($data): bool
{
    $this->startTrans();
    try {
        if($this->save($data)){
            Cache::delete('foodCashierClerkAll_' . $data['cashier_id']);
            $this->commit();
        }
    } catch (\Exception $e) {
        $this->rollback();
    }
}
§设计决策
  • 严禁直接更新缓存:写操作后应 delete(让下次 get 时回源), 而非 set 更新缓存值(cache-aside 模式)
  • 返回 Result<(), CacheError>:调用方可选择忽略错误(对齐 PHP fire and forget)
  • delete_many 的区别:invalidate_after_write 语义明确(写后失效), 便于代码审查和日志追踪
§用法
// 写操作后失效相关缓存
cache.invalidate_after_write(&["foodCashierClerkAll_1", "foodCashierClerkList_1"])?;
// 或 fire and forget(对齐 PHP 业务代码不检查返回值)
let _ = cache.invalidate_after_write(&["foodCashierClerkAll_1"]);
Source

pub fn refresh<T, F>( &self, key: &str, ttl: Option<Duration>, fetcher: F, ) -> Result<T, CacheError>

先删后读强制刷新(对齐 PHP 业务场景 2:delete → get → 回源 set)

PHP 业务代码典型模式(app/common/model/store/Store.php):

public static function info($store_id){
    $cacheKey = 'wmall_store_info_'.$store_id;
    Cache::delete($cacheKey);          // 先删
    $info = Cache::get($cacheKey);     // 再读(必为空,触发回源)
    if(!$info){
        $info = $model->with(['supplier','nav'])->find();
        if($info){
            Cache::set($cacheKey, $info, 86400);
        }
    }
    return $info;
}
§设计决策
  • 优化 PHP 模式:delete → fetcher() → set,避免一次无意义的 get(PHP 模式中 deleteget 必为空,直接调用 fetcher 更高效)
  • 严禁直接更新缓存:通过 delete + fetcher + set 实现“强制刷新“, 而非直接 set 覆盖(确保 fetcher 是唯一数据源)
  • 返回 Result<T, CacheError>:fetcher 失败时传播错误,不写入缓存
§用法
let store_info: StoreInfo = cache.refresh("wmall_store_info_1", Some(Duration::from_secs(86400)), || {
    // 回源逻辑
    Ok(StoreInfo::find(1))
})?;
Source

pub fn fetch_singleflight<T, F>( &self, key: &str, ttl: Option<Duration>, fetcher: F, ) -> Result<T, CacheError>

singleflight 模式回源(Rust 特有扩展,防止缓存击穿)

同一 key 并发请求时,只允许一个线程回源,其他线程等待锁释放后 通过 double-check 从缓存读取结果。

§与 PHP remember 的差异
维度PHP rememberRust fetch_singleflight
加锁方式$this->set($name.'_lock', true) 非原子parking_lot::Mutex::lock() 原子互斥
锁 TTL无(进程崩溃永久锁死)无需 TTL(Mutex guard 释放即解锁,panic 自动释放)
等待方式while + usleep(200ms) 轮询 5s 超时Mutex::lock() 阻塞等待(无超时,但 panic 自动释放)
double-check有(获取锁后再次检查缓存)
§用法
let value: String = cache.fetch_singleflight("hot_key", Some(Duration::from_secs(60)), || {
    // 回源逻辑(数据库查询等)
    Ok("expensive_value".to_string())
})?;
Source

pub fn set_with_jitter<T>( &self, key: &str, value: &T, ttl: Option<Duration>, jitter: Duration, ) -> Result<(), CacheError>
where T: Serialize + ?Sized,

设置带随机抖动的 TTL(Rust 特有扩展,防止缓存雪崩)

在 TTL 上加 [0, jitter] 范围的随机抖动,避免大量 key 同时过期触发雪崩。

§设计决策
  • PHP 无随机过期时间机制(getExpireTime 不做 TTL 抖动)
  • Rust 特有扩展:用 rand crate 生成随机抖动
  • 实际 TTL 在 [ttl, ttl + jitter] 范围内
  • jitter 为 0 时等价于 set(无抖动)
  • ttlNone 时等价于永久缓存(无抖动)
§用法
// 基础 TTL 60s + 随机抖动 0-10s(实际 TTL 60-70s)
cache.set_with_jitter("key", "value", Some(Duration::from_secs(60)), Duration::from_secs(10))?;
Source

pub fn fetch_with_protection<T, F>( &self, key: &str, ttl: Option<Duration>, jitter: Duration, fetcher: F, ) -> Result<T, CacheError>

singleflight + 随机过期时间组合防护(最完整防护)

组合 fetch_singleflight(防击穿)+ set_with_jitter(防雪崩), 提供最完整的缓存防护。

§用法
let value: String = cache.fetch_with_protection(
    "hot_key",
    Some(Duration::from_secs(60)),
    Duration::from_secs(10),  // 随机抖动 0-10s
    || Ok("expensive_value".to_string()),
)?;
Source

pub fn with_store<R, F>(&self, name: &str, f: F) -> Result<R, CacheError>
where F: FnOnce(&dyn CacheDriver) -> Result<R, CacheError>,

获取命名 store 的代理(对齐 PHP $cache->store('redis')

通过回调方式访问命名 store,避免生命周期问题。

§示例
cache.with_store("redis", |driver| {
    driver.set_raw("key", b"value".to_vec(), None)
}).unwrap();
Source

pub fn tag(&self, name: &str) -> TagSet<'_>

缓存标签(对齐 PHP Driver::tag($name)

PHP Driver::tag($name) 第 196-206 行:

public function tag($name): TagSet
{
    $name = (array) $name;
    $key  = implode('-', $name);
    if (!isset($this->tag[$key])) {
        $this->tag[$key] = new TagSet($name, $this);
    }
    return $this->tag[$key];
}
§PHP 单例 vs Rust 实现

PHP 使用 $this->tag[$key] 单例缓存 TagSet 对象,避免重复创建。 Rust 端不实现单例(TagSet 是无状态结构体,每次创建行为一致), 功能上完全等价。

§示例
cache.tag("user").set("user:1", &data, None)?;
cache.tag("user").clear();  // 清除所有 user 标签下的缓存
Source

pub fn tag_many(&self, names: &[&str]) -> TagSet<'_>

多标签缓存(对齐 PHP Cache::tag(['user', 'admin'])

PHP tag($name)$name = (array) $name,支持传入数组。 Rust 端通过 tag_many 方法提供等价功能。

§示例
cache.tag_many(&["user", "admin"]).set("key", &data, None)?;
cache.tag_many(&["user", "admin"]).clear();

Trait Implementations§

Source§

impl Default for Cache

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !Freeze for Cache

§

impl !RefUnwindSafe for Cache

§

impl !UnwindSafe for Cache

§

impl Send for Cache

§

impl Sync for Cache

§

impl Unpin for Cache

§

impl UnsafeUnpin for Cache

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more