当前位置: 首页 > news >正文

Redis分层缓存

我们来深入浅出地详细讲解 Redis 分层缓存的知识。这是一个非常重要且实用的高性能架构设计模式。

一、核心思想:用“空间”换“时间”,用“成本”换“性能”

想象一下图书馆的藏书架构:

  1. 你的书桌(本地缓存):放着你正在看马上要看的几本书。取用速度极快(纳秒级),但空间极小。

  2. 图书馆的开架阅览室(Redis):放着大家最常借的热门书籍。取用速度很快(毫秒级),空间较大。

  3. 图书馆的地下书库(数据库MySQL):放着所有的书籍。取用速度很慢(磁盘I/O,毫秒到秒级),空间极大。

你要找一本书时,会按照 书桌 -> 阅览室 -> 地下书库 的顺序去找。这样,绝大部分时间你都能在前两步就找到书,效率最高。只有极少数的冷门书籍才需要去书库翻找。

这就是分层缓存的核心思想:将数据存储在不同性能和成本的“层”中,让尽可能多的请求被最快、最便宜的层所响应。


二、经典的三层缓存架构

你听说的“三层”,通常指的是下图所示的经典架构:本地缓存 -> 分布式缓存(Redis) -> 数据库。这是一种从快到慢、从贵到廉的排列。

图表

代码

下面我们来详细拆解每一层。

第1层:本地缓存 (Local Cache)
  • 是什么? 存储在应用程序进程的内存中。每个服务器实例都有自己的本地缓存,彼此不共享。

  • 技术选型C++/Golang 可以用 std::map/sync.MapJava 可以用 CaffeineGuava CacheJavaScript 可以用 lru-cache 等。

  • 特点

    • 速度极快:直接读内存,无网络开销,纳秒/微秒级响应。

    • 容量极小:受单机内存限制,只能存储极少量的热点中的热点数据。

    • 非分布式:缓存数据不在多实例间同步,可能造成短暂的数据不一致

  • 适用场景

    • 极端热点的数据(如某个爆款商品的详情)。

    • 几乎不变的数据(如系统配置、字典项)。

    • 可容忍一定时间不一致的数据。

第2层:分布式缓存 (Distributed Cache - Redis)
  • 是什么? 独立的、网络式的缓存服务,所有应用程序实例都访问同一个Redis集群。

  • 技术选型Redis、Memcached。

  • 特点

    • 速度快:内存存储,但有网络开销,亚毫秒/毫秒级响应。

    • 容量大:独立集群,容量可扩展(可达TB级),能存储大量的热点数据

    • 分布式:数据集中存储,保证所有应用实例看到的缓存数据是一致的。

  • 适用场景

    • 绝大部分热点数据(如用户信息、会话、商品列表、排行榜)。

    • 需要共享的缓存数据。

    • 需要利用Redis丰富数据结构(如Set、ZSet、Hash)的场景。

第3层:数据库 (Database)
  • 是什么? 数据的最终落地存储,数据的唯一真相来源

  • 技术选型MySQL、PostgreSQL、MongoDB等。

  • 特点

    • 速度慢:数据存储在磁盘,毫秒/秒级响应(即使有数据库自身缓存,也慢于Redis)。

    • 容量极大:磁盘存储,容量可轻松扩展(可达PB级),存储全量数据

    • 持久化与一致性:通过事务、日志等机制保证数据的强一致性持久化

  • 适用场景

    • 数据的持久化存储。

    • 复杂的查询和事务操作。

    • 缓存未命中时的数据兜底。


三、数据访问流程(回种流程)

结合上面的流程图,我们来看一次完整的缓存读取流程,这被称为 “回种”

  1. 请求到达:用户请求获取用户ID=1的信息。

  2. 检查本地缓存:应用先检查自己的本地内存中是否有 user:1 的数据。

    • 如果有(缓存命中):直接返回数据,流程结束。(最快路径)

  3. 检查Redis:如果本地缓存没有,就发起一个网络请求,问Redis:“你有 user:1 吗?”

    • 如果有(缓存命中)

      • 将数据返回给应用。

      • 应用同时把这份数据存一份到自己的本地缓存中(并设置一个较短的过期时间,比如5分钟),以便后续请求能走更快的第一层。然后返回数据。

  4. 查询数据库:如果Redis也没有(这被称为缓存穿透),应用只能去查询数据库。

    • 从数据库的user表中查询ID=1的用户信息。

  5. 回种缓存

    • 回种Redis:应用从数据库拿到数据后,首先将其写入Redis(并设置一个合理的过期时间,比如30分钟)。这样下一个请求来就能在第二层命中。

    • 回种本地缓存同时,也会把数据放入本地缓存(设置更短的过期时间,比如5分钟)。

  6. 返回数据:最终,数据返回给用户。

这个流程确保了:热数据会逐渐被“提升”到更快的缓存层中。


四、分层缓存的核心优势

  1. 极致性能:99% 的请求可能由本地缓存和Redis处理,响应速度极快,数据库压力极小。

  2. 高可用性与伸缩性

    • 即使Redis短暂宕机,应用还能依靠本地缓存支撑一部分流量,不至于数据库立刻被击垮。

    • 可以通过增加应用实例和Redis节点来水平扩展系统的整体吞吐量。

  3. 成本效益:用昂贵的内存存储最热的数据,用廉价的磁盘存储全量数据,实现了成本和性能的最佳平衡。


五、在聊天服务器项目中的具体应用

在你的聊天服务器中,可以这样设计:

数据类型本地缓存 (L1)Redis (L2)数据库 (L3)说明
用户信息√ (短时间)√ (较长时间)√ (永久)用户登录后,其信息可缓存在本地和Redis
好友列表√ (短时间)√ (较长时间)√ (永久)关系变化不频繁,适合缓存
群组成员√ (短时间)√ (较长时间)√ (永久)同上
群聊消息×√ (短时间)√ (永久)消息量大,通常只放Redis做短暂缓存
离线消息×√ (临时)√ (永久)发出后存Redis,被收取后删除,并持久化到DB
会话状态×用户连接在哪台服务器上,这类状态信息非常适合用Redis共享

举例:获取用户信息

cpp

// 伪代码
User getUserInfo(int userId) {// 1. 检查本地缓存User user = localCache.get(userId);if (user != null) {return user;}// 2. 检查Redisuser = redis.get("user:" + std::to_string(userId));if (user != null) {// 回种到本地缓存localCache.set(userId, user, LOCAL_TTL);return user;}// 3. 查询数据库user = db.query("SELECT * FROM users WHERE id = ?", userId);if (user != null) {// 回种到Redis和本地缓存redis.setex("user:" + std::to_string(userId), REDIS_TTL, user);localCache.set(userId, user, LOCAL_TTL);}return user;
}

六、必须注意的挑战与解决方案

分层缓存很强大,但也引入了复杂性:

  1. 缓存穿透:请求一个数据库中根本不存在的数据,导致请求每次都穿透缓存打到数据库。

    • 解决方案:对于不存在的数据,也在缓存中设置一个空值(如 NULL)并设置一个很短的过期时间。

  2. 缓存击穿:某个极端热点数据过期时,大量请求同时涌来,瞬间击穿缓存到数据库。

    • 解决方案:使用互斥锁。第一个请求发现缓存过期时,加锁去数据库加载数据,其他请求等待锁释放后直接从缓存读取。

  3. 缓存雪崩:同一时刻大量缓存键同时过期,导致所有请求都打到数据库。

    • 解决方案:给缓存过期时间加上一个随机值(如基础30分钟 + 随机0-5分钟),避免同时过期。

  4. 数据不一致:数据库数据更新后,缓存中的数据还是旧的。

    • 解决方案

      • 先更新数据库,再删除缓存。这是最常用的策略。

      • 通过消息队列进行异步更新,确保最终一致性。

      • 为缓存设置较短的过期时间,到期后自动重新加载。

总结

Redis分层缓存(尤其是L1/L2/L3架构)是构建高性能、高可用应用的基石技术。它通过将数据分级存储,巧妙地平衡了速度、容量和成本。

其核心精髓在于:

  • L1 本地缓存:解决速度问题,应对极致热点。

  • L2 Redis:解决容量和共享问题,应对主要热点。

  • L3 数据库:解决持久化和全量数据问题,是数据的最终保障。

理解和运用好这套架构,你的聊天服务器性能将得到质的飞跃。

http://www.dtcms.com/a/362830.html

相关文章:

  • kukekey在线搭建k8sV1.30.4版本
  • VMWare ubuntu24.04安装(安装ubuntu安装)
  • InnoDB存储引擎-逻辑存储结构
  • Qwen3-30B-A3B 模型解析
  • 【LeetCode牛客数据结构】单链表的应用
  • C语言(长期更新)第12讲:指针二详解
  • 【嵌入式电机控制#进阶6】三段启动法
  • 怎么为服务器设置或重置服务器密码?
  • 【Vue2 ✨】Vue2 入门之旅(九):Vue Router 入门
  • JetBrains 2025 全家桶 11合1 Windows直装(含 IDEA PyCharm、WebStorm、DataSpell、DataGrip等
  • [密码学实战]智能密码钥匙SKF库软实现(四十六)
  • LabVIEW应急柴油发电机组诊断装置
  • LabVIEW振动信号积分处理
  • 【设计模式】通俗讲解设计模式的七大原则
  • 【设计模式】从游戏角度开始了解设计模式 --- 创建型模式(一)
  • Python OpenCV图像处理与深度学习:Python OpenCV性能优化与高效图像处理
  • VGG改进(7):基于Spatial Attention的性能优化
  • 从“叠加”到“重叠”:Overlay 与 Overlap 双引擎驱动技术性能优化
  • Spring MVC + JSP 项目的配置流程,适合传统 Java Web 项目开发
  • 【MySQL】初识数据库基础
  • RAG-检索进阶
  • 【一张图看懂Kafka消息队列架构】
  • 【C++】编写通用模板代码的重要技巧:T()
  • 与后端对话:在React中优雅地请求API数据 (Fetch/Axios)
  • 基于STM32的智能语音浴缸设计
  • 工业视觉光源选色指南:白光通用、蓝光显瑕疵、红光能穿透,看完直接用
  • 推荐一个论文阅读工具ivySCI
  • C++内存管理,模板初阶(泛型编程)
  • 项目组文档标准不一致的原因有哪些
  • 设计模式:命令模式(Command Pattern)