4. 缓存基础:缓存的作用与类型、常见策略与一致性问题
在构建高并发、低延迟的后端系统时,缓存是提升性能的核心手段。对于 JWT 与 OAuth2.0 场景,缓存不仅承载令牌(Token)、授权码(Authorization Code)、刷新令牌(Refresh Token)等临时凭证,还直接影响认证与授权的响应速度。本章从缓存的基本作用出发,梳理缓存类型、常见淘汰策略,并严格定义缓存一致性问题,为后续的缓存一致性测试奠定理论基础。
4.1 缓存的核心作用
缓存通过在高速存储层(通常是内存)中暂存热点数据,减少对后端数据库或远程服务的访问次数,从而带来三大收益:
- 降低延迟:内存访问延迟通常在微秒级,而数据库查询或 RPC 调用延迟在毫秒甚至秒级,缓存可将响应时间降低 1~3 个数量级。
- 提升吞吐量:缓存层能承载更高的 QPS(每秒查询数),避免数据库连接池或下游服务成为瓶颈。
- 减轻后端压力:对于 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 |
| 适用场景 | 单机部署、配置缓存、静态字典 | 多实例共享令牌、会话、授权码 |
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 | 缓存容量满 | 写入时间(最早写入) | 简单场景,不常用 |
4.4 缓存一致性问题定义
缓存一致性是指缓存中的数据与数据源(数据库、授权服务器、用户信息源)中的数据保持同步的程度。在分布式系统中,由于缓存与数据源之间存在更新延迟、网络分区、并发写入等因素,不一致是常态而非异常。
4.4.1 一致性的三个维度
- 强一致性(Strong Consistency):缓存更新后,所有后续读取都能立即看到最新值。实现代价高,通常需要分布式事务或共识协议(如 Paxos、Raft)。
- 最终一致性(Eventual Consistency):允许短暂的不一致窗口,但保证在没有新更新的情况下,所有副本最终会达到一致状态。这是大多数缓存系统的默认模型。
- 弱一致性(Weak Consistency):不保证何时能读到最新值,甚至可能永远读不到。适用于日志、监控等非关键数据。
4.4.2 缓存不一致的典型场景
- 写后读不一致:用户修改密码后,缓存中仍保留旧密码的哈希,导致旧密码仍可登录。
- 多实例缓存不一致:两个应用实例各自持有本地缓存,实例 A 更新了数据,实例 B 仍使用旧缓存。
- 缓存与数据库双写冲突:同时更新缓存和数据库时,由于并发或失败,导致两者数据不一致。
- TTL 过期延迟:令牌已从授权服务器撤销,但缓存中的 TTL 尚未到期,导致已撤销的令牌仍被接受。
- 授权码:必须强一致,因为授权码是一次性的,重复使用会导致授权码重放攻击。
- 访问令牌:可接受最终一致性,但撤销操作必须尽快传播(秒级)。
- 刷新令牌:需要强一致或接近强一致,防止同一刷新令牌被多次使用。
- 客户端密钥:强一致,密钥变更后应立即生效。
4.5 缓存架构示意图
下图展示了一个典型的后端缓存分层架构,包含本地缓存、分布式缓存与数据源之间的交互关系:
4.6 缓存一致性问题分类矩阵
根据不一致的成因与影响范围,可将缓存一致性问题分为以下四类。该矩阵将指导后续章节的测试用例设计:
| 问题类型 | 成因 | 影响范围 | JWT/OAuth2.0 示例 | 严重程度 |
|---|---|---|---|---|
| 写后读不一致 | 更新数据源后未及时更新缓存 | 单用户 | 用户撤销令牌后仍可访问 | 高 |
| 多实例不一致 | 本地缓存未同步 | 部分用户 | 实例 A 缓存了旧公钥,实例 B 使用新公钥 | 中 |
| 双写冲突 | 并发更新缓存与数据源 | 单用户 | 同时刷新令牌导致缓存写入旧值 | 高 |
| TTL 过期延迟 | TTL 设置过长或未设置 | 全局 | 已撤销的授权码仍在 TTL 内有效 | 严重 |
4.7 缓存策略选择决策树
在 JWT 与 OAuth2.0 系统中,不同数据类型的缓存策略选择可参考以下决策流程:
4.8 缓存一致性测试切入点
基于上述理论基础,在后续章节中我们将围绕以下测试维度展开:
- TTL 边界测试:验证令牌在 TTL 到期后是否准确失效,以及 TTL 设置过短或过长的影响。
- LRU 淘汰测试:在缓存容量满时,验证最不活跃的令牌是否被正确淘汰,以及淘汰后是否触发回源加载。
- 多实例一致性测试:模拟多个应用实例同时读写缓存,验证最终一致性窗口是否符合预期。
- 撤销传播测试:测试令牌撤销操作从数据源传播到所有缓存节点的延迟与准确性。
- 并发冲突测试:模拟高并发下的缓存与数据源双写场景,验证是否存在脏读或数据覆盖。