别再只懂原理了!动手用C++实现一个Redis风格的LRU缓存(支持TTL过期)

张开发
2026/4/20 23:15:57 15 分钟阅读

分享文章

别再只懂原理了!动手用C++实现一个Redis风格的LRU缓存(支持TTL过期)
从零构建工业级LRU缓存C实现与TTL过期策略深度解析在分布式系统和高性能服务架构中缓存组件扮演着至关重要的角色。当我们需要自己动手实现一个类似Redis的内存缓存时如何设计高效的LRU最近最少使用算法并整合TTL生存时间过期机制成为每个中高级C开发者必须掌握的技能。本文将带您从工程实践角度逐步构建一个支持TTL的LRU缓存系统。1. LRU缓存核心架构设计1.1 数据结构选型与时间复杂度分析工业级LRU缓存需要保证get和put操作都在O(1)时间复杂度内完成。这要求我们精心选择数据结构组合// 核心数据结构定义 struct CacheNode { int key; int value; time_t expire_time; // 过期时间戳 CacheNode* prev; CacheNode* next; CacheNode(int k, int v, time_t exp) : key(k), value(v), expire_time(exp), prev(nullptr), next(nullptr) {} }; class LRUCache { private: unordered_mapint, CacheNode* cache_map; // 哈希表用于快速查找 CacheNode* head; // 虚拟头节点 CacheNode* tail; // 虚拟尾节点 int capacity; };这种双向链表哈希表的组合具有以下优势哈希表提供O(1)的键值查找能力双向链表维护访问顺序支持快速移动节点到链表头部虚拟头尾节点简化边界条件处理1.2 内存管理与RAII原则在C实现中我们需要特别注意内存管理// 在析构函数中释放所有节点内存 ~LRUCache() { CacheNode* curr head-next; while (curr ! tail) { CacheNode* temp curr; curr curr-next; delete temp; } delete head; delete tail; }提示使用智能指针可以进一步简化内存管理但在高性能场景下原始指针可能提供更好的性能。2. TTL过期机制实现策略2.1 时间精度与系统调用选择不同的时间获取方式会影响性能和精度时间获取方式精度系统调用开销适用场景time()秒级低一般过期检查gettimeofday()微秒级中需要更高精度std::chrono纳秒级低C11及以上环境clock_gettime()纳秒级高极端精度要求推荐实现#include chrono auto now std::chrono::system_clock::now(); auto now_sec std::chrono::time_point_caststd::chrono::seconds(now); time_t expire_time now_sec.time_since_epoch().count() ttl;2.2 过期检查策略对比Redis采用了两种互补的过期策略惰性删除在访问key时检查是否过期定期删除周期性扫描过期key我们的实现可以结合这两种策略// 惰性删除实现示例 int get(int key) { if (cache_map.find(key) cache_map.end()) { return -1; } CacheNode* node cache_map[key]; if (isExpired(node)) { // 检查是否过期 removeNode(node); return -1; } moveToHead(node); // 移动到链表头部表示最近使用 return node-value; }3. 性能优化关键技巧3.1 批量过期处理当缓存满载时可以批量清理过期键值对void purgeExpiredNodes() { vectorint expired_keys; time_t current_time time(nullptr); CacheNode* curr head-next; while (curr ! tail) { if (curr-expire_time current_time) { expired_keys.push_back(curr-key); } curr curr-next; } for (int key : expired_keys) { removeNode(cache_map[key]); } }3.2 写时复制优化对于高并发场景可以考虑COWCopy-On-Write技术维护双缓冲区当前缓存和待更新缓存写操作作用于待更新缓存定期或达到阈值时原子切换缓冲区4. 完整实现与测试案例4.1 完整类实现class LRUCacheWithTTL { public: LRUCacheWithTTL(int cap) : capacity(cap) { head new CacheNode(-1, -1, 0); tail new CacheNode(-1, -1, 0); head-next tail; tail-prev head; } int get(int key) { if (cache_map.find(key) cache_map.end()) { return -1; } CacheNode* node cache_map[key]; if (node-expire_time time(nullptr)) { removeNode(node); return -1; } moveToHead(node); return node-value; } void put(int key, int value, int ttl) { time_t expire_time time(nullptr) ttl; if (cache_map.find(key) ! cache_map.end()) { CacheNode* node cache_map[key]; node-value value; node-expire_time expire_time; moveToHead(node); return; } if (cache_map.size() capacity) { purgeExpiredNodes(); if (cache_map.size() capacity) { removeNode(tail-prev); } } CacheNode* newNode new CacheNode(key, value, expire_time); addToHead(newNode); cache_map[key] newNode; } private: // 私有方法实现... };4.2 测试案例设计void testLRUCache() { LRUCacheWithTTL cache(2); // 测试基本LRU功能 cache.put(1, 1, 10); // 存活10秒 cache.put(2, 2, 10); assert(cache.get(1) 1); // 测试容量淘汰 cache.put(3, 3, 10); assert(cache.get(2) -1); // 测试TTL过期 this_thread::sleep_for(chrono::seconds(11)); assert(cache.get(1) -1); }在实际项目中这种缓存实现可以轻松处理百万级QPS的请求内存占用和性能表现都能达到工业级标准。关键在于合理设置过期时间、定期清理策略以及适当的内存预分配。

更多文章