文件最后提交记录最后更新时间
3 个月前
3 个月前
23 天前
3 个月前
README.md

LRU Cache 通用缓存模块 API 文档

概述

本模块提供一个通用的 LRU(Least Recently Used,最近最少使用)缓存实现,位于 functionsystem/src/common/lru/。模块包含两个类:

  • LruCache<K, V> — 无锁基础版本,适用于单线程场景(如 LiteBus Actor)
  • ThreadSafeLruCache<K, V> — 互斥锁包装版本,适用于多线程并发访问场景

所有操作的平均时间复杂度为 O(1)

头文件引入

#include "lru/lru_cache.h"              // LruCache<K, V>
#include "lru/thread_safe_lru_cache.h"  // ThreadSafeLruCache<K, V>

CMake 集成

在你的模块 CMakeLists.txt 中添加:

target_link_libraries(your_module PRIVATE lru)

LruCache<K, V>

模板参数

参数 约束 说明
K 需支持 std::hash<K>operator== 缓存键类型
V 需支持移动构造,可以是 move-only 类型 缓存值类型

类型别名

using EvictionCallback = std::function<void(const K&, V&&)>;

淘汰回调函数类型。当缓存容量满时插入新条目,最久未使用的条目会被淘汰, 回调函数接收被淘汰条目的 key(const 引用)和 value(右值引用,所有权转移给回调方)。

构造函数

explicit LruCache(size_t capacity, EvictionCallback onEvict = nullptr);
参数 说明
capacity 缓存最大容量(条目数),必须 > 0,否则触发断言
onEvict 可选的淘汰回调函数,默认为 nullptr(不回调)

拷贝语义:禁用拷贝构造和拷贝赋值。 移动语义:支持移动构造和移动赋值。

API 方法

Put — 插入或更新条目

bool Put(const K& key, V value);

插入新条目或更新已有条目的值,并将该条目提升为最近使用(MRU)。

参数 说明
key 缓存键
value 缓存值(通过移动语义传入)

返回值true 表示插入了新条目,false 表示更新了已有条目。

淘汰行为:当缓存已满且插入新键时,最久未使用的条目(LRU)会被淘汰。 如果设置了淘汰回调,回调会在条目移除后被调用。

LruCache<std::string, int> cache(3);
cache.Put("a", 1);   // 返回 true(新插入)
cache.Put("b", 2);   // 返回 true
cache.Put("c", 3);   // 返回 true
cache.Put("a", 100); // 返回 false(更新已有键)
cache.Put("d", 4);   // 返回 true,淘汰 "b"(最久未使用)

Get — 获取条目(提升为最近使用)

std::optional<std::reference_wrapper<V>> Get(const K& key);

根据 key 查找缓存条目。如果命中,将该条目提升为最近使用(MRU)。

返回值:命中时返回值的引用包装;未命中返回 std::nullopt

auto result = cache.Get("a");
if (result) {
    int& value = result->get();  // 获取引用
    // 使用 value...
}

注意:返回的是引用,不会拷贝值。对于 move-only 类型(如 std::unique_ptr), 可以安全地通过引用访问而不影响所有权。


Contains — 检查键是否存在(不提升)

bool Contains(const K& key) const;

检查指定 key 是否存在于缓存中。不会改变淘汰顺序

if (cache.Contains("a")) {
    // key "a" 存在,但其在淘汰队列中的位置不变
}

Peek — 读取值(不提升)

std::optional<std::reference_wrapper<const V>> Peek(const K& key) const;

读取指定 key 的值但不改变淘汰顺序。适用于诊断、监控等不应影响缓存行为的场景。

返回值:命中时返回 const 引用包装;未命中返回 std::nullopt

auto peeked = cache.Peek("a");
if (peeked) {
    const int& value = peeked->get();
    // 只读访问,不影响淘汰顺序
}

Remove — 显式删除条目

bool Remove(const K& key);

从缓存中删除指定 key 的条目。不会触发淘汰回调

返回值true 表示找到并删除,false 表示 key 不存在。


Clear — 清空所有条目

void Clear();

移除缓存中的所有条目。不会触发淘汰回调


Size — 当前条目数

size_t Size() const;

返回缓存中当前存储的条目数量。


Capacity — 最大容量

size_t Capacity() const;

返回缓存的最大容量(构造时设定,不可变)。


ThreadSafeLruCache<K, V>

互斥锁包装版本,API 与 LruCache 相同,但所有操作通过 std::mutex 保护。

与 LruCache 的区别

特性 LruCache ThreadSafeLruCache
线程安全 是(互斥锁)
Get 返回类型 optional<reference_wrapper<V>> optional<V>(返回拷贝)
Peek 返回类型 optional<reference_wrapper<const V>> optional<V>(返回拷贝)
移动语义 支持 不支持(mutex 不可移动)
适用场景 LiteBus Actor 内部 多线程共享访问

重要ThreadSafeLruCacheGetPeek 返回值的拷贝而非引用, 以避免在持有引用期间其他线程修改或淘汰该条目导致悬空引用。

构造函数

explicit ThreadSafeLruCache(size_t capacity, EvictionCallback onEvict = nullptr);

参数含义同 LruCache

使用示例

ThreadSafeLruCache<std::string, RouteInfo> routeCache(500);

// 所有操作线程安全
routeCache.Put("instance-001", RouteInfo{...});

auto route = routeCache.Get("instance-001");
if (route) {
    // route.value() 是 RouteInfo 的拷贝,可安全使用
}

使用场景示例

1. 快照存储缓存

当快照数据不再被活跃使用时,加入 LRU 队列;容量满时通过淘汰回调异步清理:

LruCache<std::string, std::unique_ptr<SnapshotData>> snapshotCache(
    1000,
    [](const std::string& snapshotId, std::unique_ptr<SnapshotData>&& data) {
        // 异步回调:将快照刷写到持久化存储
        FlushToPersistentStorage(snapshotId, std::move(data));
    }
);

// 访问快照 — 提升为最近使用,不会被淘汰
auto snap = snapshotCache.Get("snap-001");

// 新增快照,若容量满则最久未使用的快照被淘汰,触发回调
snapshotCache.Put("snap-new", std::make_unique<SnapshotData>(...));

2. 实例路由信息缓存

struct RouteInfo {
    std::string nodeAddress;
    uint16_t port;
    uint64_t version;
};

LruCache<std::string, RouteInfo> instanceRouteCache(
    2000,
    [](const std::string& instanceId, RouteInfo&& route) {
        LOG_INFO("路由淘汰: instance={}, node={}", instanceId, route.nodeAddress);
    }
);

// 非提升查询 — 用于诊断,不影响淘汰顺序
if (instanceRouteCache.Contains("instance-xyz")) {
    auto route = instanceRouteCache.Peek("instance-xyz");
    // 只读查看,不改变缓存行为
}

3. 在 LiteBus Actor 中使用

class MyActor : public litebus::ActorBase {
protected:
    void Init() override {
        // Actor 内部使用无锁版本(Actor 消息循环是单线程的)
        member_cache = std::make_unique<LruCache<std::string, DataObj>>(
            500,
            [this](const std::string& key, DataObj&& data) {
                // 注意:在 Actor 回调中应使用 Defer 模式
                // 此处简化展示
                HandleEviction(key, std::move(data));
            }
        );
    }

private:
    std::unique_ptr<LruCache<std::string, DataObj>> member_cache;
};

淘汰回调说明

触发条件

操作 是否触发回调
Put(容量满时插入新键)
Put(更新已有键)
Remove
Clear

异常安全

淘汰回调中抛出的异常会被捕获并忽略。被淘汰的条目已经从缓存内部数据结构中 移除,不会因回调异常导致缓存状态不一致。

值的所有权

回调接收 V&&(右值引用),被淘汰的值的所有权完整转移给回调方。 对于 std::unique_ptr 等 move-only 类型,回调方可以通过 std::move 接管资源。


性能特征

操作 时间复杂度 说明
Put O(1) 均摊 unordered_map 插入 + list 前插
Get O(1) 均摊 unordered_map 查找 + list::splice
Contains O(1) 均摊 unordered_map::count
Peek O(1) 均摊 unordered_map::find
Remove O(1) 均摊 unordered_map 删除 + list::erase
Clear O(n) 清空所有条目
Size O(1) unordered_map::size
Capacity O(1) 返回成员变量

空间复杂度:O(n),其中 n 为缓存容量。每个条目在 std::liststd::unordered_map 中各存储一份引用。