4. 缓存基础:缓存的作用与类型、常见策略与一致性问题

在构建高并发、低延迟的后端系统时,缓存是提升性能的核心手段。对于 JWT 与 OAuth2.0 场景,缓存不仅承载令牌(Token)、授权码(Authorization Code)、刷新令牌(Refresh Token)等临时凭证,还直接影响认证与授权的响应速度。本章从缓存的基本作用出发,梳理缓存类型、常见淘汰策略,并严格定义缓存一致性问题,为后续的缓存一致性测试奠定理论基础。

4.1 缓存的核心作用

缓存通过在高速存储层(通常是内存)中暂存热点数据,减少对后端数据库或远程服务的访问次数,从而带来三大收益:

  • 降低延迟:内存访问延迟通常在微秒级,而数据库查询或 RPC 调用延迟在毫秒甚至秒级,缓存可将响应时间降低 1~3 个数量级。
  • 提升吞吐量:缓存层能承载更高的 QPS(每秒查询数),避免数据库连接池或下游服务成为瓶颈。
  • 减轻后端压力:对于 OAuth2.0 授权服务器,每次令牌校验都查询数据库将导致严重的性能退化;缓存可大幅减少数据库负载。
JWT 场景提示: 自包含的 JWT 本身不需要缓存令牌信息,但 OAuth2.0 中的授权码、刷新令牌、客户端凭证等非自包含凭证必须依赖缓存或数据库存储。缓存策略的选择直接影响令牌的可用性与安全性。

4.2 缓存类型:本地缓存 vs 分布式缓存

根据缓存数据的存储位置与共享范围,缓存分为本地缓存与分布式缓存两大类。下表从多个维度对比两者的差异:

对比维度 本地缓存(Local Cache) 分布式缓存(Distributed Cache)
存储位置 进程内堆内存(如 HashMap、Caffeine) 独立缓存服务(如 Redis、Memcached)
数据共享 仅限单进程,多实例间不共享 多实例共享同一缓存集群
访问延迟 极低(纳秒~微秒级) 较低(毫秒级,依赖网络 RTT)
容量上限 受限于 JVM 堆大小(通常 GB 级) 可水平扩展至 TB 级甚至更大
一致性保证 单机强一致,但多实例间存在不一致 依赖分布式协议(如 Redis 主从、集群)
典型实现 Caffeine、Guava Cache、Ehcache Redis、Memcached、Hazelcast
适用场景 单机部署、配置缓存、静态字典 多实例共享令牌、会话、授权码
架构决策要点: 在 OAuth2.0 授权码模式中,授权码通常存储在分布式缓存中,确保所有授权服务器实例都能访问同一份数据。而 JWT 的密钥对(JWK)可以同时使用本地缓存(减少网络开销)与分布式缓存(保证密钥轮转一致性),形成两级缓存架构。

4.3 常见缓存策略:LRU 与 TTL

缓存空间有限,必须制定淘汰策略来管理数据的生命周期。最常用的两种策略是 LRU(最近最少使用)和 TTL(生存时间)。

4.3.1 LRU(Least Recently Used)

LRU 策略在缓存满时淘汰最久未被访问的数据。它基于“时间局部性”原理:最近被访问的数据在未来更可能被再次访问。实现方式包括链表 + 哈希表(O(1) 复杂度)或近似 LRU(如 Redis 的近似 LRU)。

4.3.2 TTL(Time To Live)

TTL 为每个缓存条目设置一个过期时间,到期后自动失效。TTL 是令牌缓存最常用的策略,因为令牌本身具有明确的过期时间(如授权码 5 分钟、访问令牌 1 小时)。

策略 触发条件 淘汰依据 适用场景
LRU 缓存容量满 访问时间(最近最少) 热点数据、用户会话、配置缓存
TTL 时间到期 写入时间 + 固定时长 令牌、验证码、临时凭证
LFU 缓存容量满 访问频率(最少使用) 访问模式稳定的场景
FIFO 缓存容量满 写入时间(最早写入) 简单场景,不常用
实践陷阱: 仅依赖 LRU 淘汰令牌可能导致已过期的令牌仍留在缓存中,造成安全漏洞。正确的做法是 LRU + TTL 组合:TTL 保证令牌在过期后自动失效,LRU 在缓存满时淘汰最不活跃的条目。

4.4 缓存一致性问题定义

缓存一致性是指缓存中的数据与数据源(数据库、授权服务器、用户信息源)中的数据保持同步的程度。在分布式系统中,由于缓存与数据源之间存在更新延迟、网络分区、并发写入等因素,不一致是常态而非异常。

4.4.1 一致性的三个维度

  • 强一致性(Strong Consistency):缓存更新后,所有后续读取都能立即看到最新值。实现代价高,通常需要分布式事务或共识协议(如 Paxos、Raft)。
  • 最终一致性(Eventual Consistency):允许短暂的不一致窗口,但保证在没有新更新的情况下,所有副本最终会达到一致状态。这是大多数缓存系统的默认模型。
  • 弱一致性(Weak Consistency):不保证何时能读到最新值,甚至可能永远读不到。适用于日志、监控等非关键数据。

4.4.2 缓存不一致的典型场景

  1. 写后读不一致:用户修改密码后,缓存中仍保留旧密码的哈希,导致旧密码仍可登录。
  2. 多实例缓存不一致:两个应用实例各自持有本地缓存,实例 A 更新了数据,实例 B 仍使用旧缓存。
  3. 缓存与数据库双写冲突:同时更新缓存和数据库时,由于并发或失败,导致两者数据不一致。
  4. TTL 过期延迟:令牌已从授权服务器撤销,但缓存中的 TTL 尚未到期,导致已撤销的令牌仍被接受。
JWT/OAuth2.0 场景下的特殊一致性要求:
- 授权码:必须强一致,因为授权码是一次性的,重复使用会导致授权码重放攻击。
- 访问令牌:可接受最终一致性,但撤销操作必须尽快传播(秒级)。
- 刷新令牌:需要强一致或接近强一致,防止同一刷新令牌被多次使用。
- 客户端密钥:强一致,密钥变更后应立即生效。

4.5 缓存架构示意图

下图展示了一个典型的后端缓存分层架构,包含本地缓存、分布式缓存与数据源之间的交互关系:

缓存分层架构图 缓存分层架构与一致性边界 应用层(Application Instances) 实例 A(本地缓存) | 实例 B(本地缓存) | 实例 C(本地缓存) 分布式缓存层(Redis Cluster) 主节点 | 从节点 1 | 从节点 2 数据源层(Database / Auth Server) 用户表 | 令牌表 | 授权码表 一致性边界说明 ① 本地缓存 → 分布式缓存:最终一致性,需通过失效通知或 TTL 同步 ② 分布式缓存 → 数据源:强一致性(授权码)/ 最终一致性(访问令牌)

4.6 缓存一致性问题分类矩阵

根据不一致的成因与影响范围,可将缓存一致性问题分为以下四类。该矩阵将指导后续章节的测试用例设计:

问题类型 成因 影响范围 JWT/OAuth2.0 示例 严重程度
写后读不一致 更新数据源后未及时更新缓存 单用户 用户撤销令牌后仍可访问 高
多实例不一致 本地缓存未同步 部分用户 实例 A 缓存了旧公钥,实例 B 使用新公钥 中
双写冲突 并发更新缓存与数据源 单用户 同时刷新令牌导致缓存写入旧值 高
TTL 过期延迟 TTL 设置过长或未设置 全局 已撤销的授权码仍在 TTL 内有效 严重

4.7 缓存策略选择决策树

在 JWT 与 OAuth2.0 系统中,不同数据类型的缓存策略选择可参考以下决策流程:

缓存策略选择决策树 缓存策略选择决策树(JWT/OAuth2.0 场景) 数据类型 授权码 / 刷新令牌 访问令牌(JWT) 客户端密钥 / JWK 策略:TTL + 强一致 缓存:分布式缓存 策略:TTL + LRU 缓存:本地 + 分布式 策略:TTL + 强一致 缓存:分布式缓存 决策依据 • 授权码/刷新令牌:一次性使用,必须强一致,推荐 Redis 事务或 Lua 脚本保证原子性 • 访问令牌(JWT):自包含验证,可接受最终一致性,本地缓存减少验证延迟 • 客户端密钥/JWK:密钥轮转需立即生效,必须强一致,推荐分布式缓存 + 发布订阅通知

4.8 缓存一致性测试切入点

基于上述理论基础,在后续章节中我们将围绕以下测试维度展开:

  • TTL 边界测试:验证令牌在 TTL 到期后是否准确失效,以及 TTL 设置过短或过长的影响。
  • LRU 淘汰测试:在缓存容量满时,验证最不活跃的令牌是否被正确淘汰,以及淘汰后是否触发回源加载。
  • 多实例一致性测试:模拟多个应用实例同时读写缓存,验证最终一致性窗口是否符合预期。
  • 撤销传播测试:测试令牌撤销操作从数据源传播到所有缓存节点的延迟与准确性。
  • 并发冲突测试:模拟高并发下的缓存与数据源双写场景,验证是否存在脏读或数据覆盖。
本章小结: 缓存是后端性能优化的基石,但引入缓存的同时也带来了一致性问题。理解本地缓存与分布式缓存的差异、掌握 LRU 与 TTL 的组合策略、明确定义一致性边界,是设计可靠缓存系统的前提。在 JWT 与 OAuth2.0 场景中,不同类型的数据对一致性要求不同,需要针对性地选择缓存策略。下一章将深入探讨 JWT 与 OAuth2.0 的缓存架构设计。