3、缓存策略深度优化:Redis集群、本地缓存与多级缓存架构
缓存这东西,说白了就是用空间换时间。但在大规模发证场景下,缓存用不好,数据库照样会被打垮。我见过太多团队,上来就怼一个 Redis 单节点,结果流量一上来,Redis 先扛不住,数据库跟着雪崩。
今天咱们聊聊怎么把缓存玩透。从 Redis 集群到本地缓存,再到多级缓存架构,一层层把数据库的压力卸掉。
3.1 Redis 集群:不只是分片
很多人觉得 Redis 集群就是数据分片,其实没那么简单。我当年接手一个发证项目,日均发证量 500 万,单节点 Redis 内存飙到 40GB,RDB 持久化一次卡死 3 秒。后来切到集群,才算喘过气来。
3.1.1 集群拓扑选型
Redis 集群主流有三种玩法:
| 方案 | 数据分片 | 高可用 | 一致性 | 适合场景 |
|---|---|---|---|---|
| Redis Cluster | 自动分片(16384 slots) | 主从 + 哨兵 | 最终一致性 | 大规模读写分离 |
| Codis | Proxy 分片 | 主从 + ZK | 强一致性(需配置) | 兼容 Redis 协议 |
| Twemproxy | 一致性哈希 | 无 | 弱一致性 | 轻量代理 |
我个人习惯用 Redis Cluster。原因很简单——原生支持,运维成本低。Codis 虽然功能强,但多了一层 Proxy,延迟会增加 1-2ms。在发证这种高并发场景下,每一毫秒都很关键。
3.1.2 Slot 迁移与热点处理
集群部署后,最头疼的是热点问题。我记得有一次,某个证书模板的缓存 key 被大量并发访问,导致单个节点 CPU 冲到 95%。
解决办法有两个:
- 散列 key:在 key 后面加随机后缀,把热点打散到不同 slot
- 本地缓存兜底:热点数据在应用层再缓存一层,减少 Redis 压力
核心原则:Redis 集群不是万能的。当单个 key 的 QPS 超过 10 万时,必须考虑本地缓存介入。
3.2 本地缓存:被低估的利器
很多人觉得本地缓存不够「高级」,其实不然。在发证场景中,证书模板、配置参数这类变化极少的元数据,用本地缓存效果出奇的好。
3.2.1 Caffeine 实战
Java 生态里,Caffeine 是目前性能最好的本地缓存库。我做过压测,Caffeine 的读写吞吐是 Guava Cache 的 3 倍以上。
// 发证场景下的 Caffeine 配置
Cache<String, CertificateTemplate> templateCache = Caffeine.newBuilder()
.maximumSize(10_000) // 最多缓存 1 万个模板
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
.recordStats() // 开启统计,方便监控
.build();
// 使用方式
CertificateTemplate template = templateCache.get(templateId, key -> {
// 缓存未命中时,从数据库加载
return certificateDao.getTemplate(key);
});
这里有个坑:过期时间不能太长。我曾经把模板缓存设为 30 分钟,结果运营改了模板内容,用户拿到的还是旧证书。后来改成 5 分钟 + 主动失效机制,才算解决。
3.2.2 本地缓存 vs 分布式缓存
| 维度 | 本地缓存 | Redis 集群 |
|---|---|---|
| 延迟 | 纳秒级 | 毫秒级(网络开销) |
| 容量 | 受限于 JVM 堆 | 可水平扩展 |
| 一致性 | 节点间不一致 | 集群内一致 |
| 适用数据 | 只读 / 极少变更 | 读写频繁 / 共享数据 |
我的经验:本地缓存适合缓存「每个节点独立且变化慢」的数据。比如证书模板、字典表、配置项。而用户会话、发证流水这类共享数据,必须用 Redis。
3.3 多级缓存架构:层层拦截
单靠一种缓存,很难应对所有场景。真正的生产环境,需要构建多级缓存体系。
3.3.1 架构设计
下面这张图是我在项目中实际落地的多级缓存架构:
你看,请求进来后,先过 CDN,再查本地缓存,然后才到 Redis,最后才是数据库。每一层命中就直接返回,不再往下走。这样数据库几乎只处理缓存未命中的请求。
3.3.2 缓存穿透与击穿
多级缓存最怕什么?穿透和击穿。
- 缓存穿透:查询一个不存在的数据,所有缓存都查不到,直接打到数据库
- 缓存击穿:某个热点 key 过期,大量并发同时去数据库加载
我曾经踩过一个坑:用户查询一个已注销的证书 ID,每次请求都穿透到数据库,导致数据库连接池被打满。后来加了布隆过滤器,才把这个问题堵住。
避坑指南:
- 穿透:用布隆过滤器拦截非法 key,或者缓存空值(设置短过期时间)
- 击穿:用互斥锁(如 Redisson)控制并发加载,或者用「永不过期 + 异步刷新」策略
3.4 缓存一致性:终极难题
多级缓存最头疼的问题,就是一致性。本地缓存和 Redis 数据不一致怎么办?
3.4.1 最终一致性方案
在发证场景中,我推荐使用「Cache Aside Pattern + 消息队列」:
- 更新数据库时,先写数据库,再发一条 MQ 消息
- 消费者收到消息后,删除 Redis 中的对应缓存
- 本地缓存设置较短过期时间(如 5 分钟),到期自动失效
这样做的好处是:数据库和 Redis 最终一致,本地缓存最多有 5 分钟的不一致窗口。对于发证场景来说,这个窗口是可以接受的。
3.4.2 强一致性场景
如果业务要求强一致性(比如证书状态变更),那就不能用多级缓存了。我建议:
- 直接查 Redis,不走本地缓存
- Redis 和数据库之间用分布式事务(如 Seata)保证一致性
- 或者干脆不缓存,直接查数据库(牺牲性能保一致性)
记住:没有银弹。多级缓存适合「读多写少、允许短暂不一致」的场景。发证业务中,证书模板、配置参数、字典表都符合这个特征。但证书状态、用户余额这类数据,请谨慎使用多级缓存。
3.5 监控与调优
缓存系统上线后,不是万事大吉。我习惯在每层缓存都埋点监控:
| 指标 | 本地缓存 | Redis 集群 |
|---|---|---|
| 命中率 | Caffeine.recordStats() | INFO commandstats |
| 延迟 | 微秒级(JVM 内) | 毫秒级(slowlog) |
| 内存使用 | JVM 堆监控 | INFO memory |
| 热点 key | 自定义计数器 | hotkeys 参数 |
我记得有一次,监控发现 Redis 的命中率从 95% 掉到了 70%。排查后发现是本地缓存过期时间设置太短,大量请求穿透到 Redis。调整过期时间后,命中率又回到了 90% 以上。
调优口诀:本地缓存保热点,Redis 集群扛流量,数据库只做兜底。每层命中率低于 80% 就要排查原因。
好了,缓存策略这块就聊到这儿。记住,多级缓存不是堆砌技术,而是根据数据特征做分层。用对了,数据库压力能降 90% 以上。