热点处理
多级缓存架构
系统采用多级缓存架构来保护供应商 API:
多级缓存
本地缓存
应用进程内存缓存Redis 缓存
分布式缓存服务供应商 API
原始数据源(第三方)读写分离上线后,系统运行正常。
但 Redis 集中式缓存的架构很快暴露了瓶颈:
- 单点压力:所有请求最终都会汇聚到同一个 Redis 分片,热点 API 的 QPS 直接转化为 Redis 单节点的 CPU 和网络开销
- 网络延迟:每次缓存查询都需要一次网络 RTT(1~5ms),即使命中也要付出这个代价
- 供应商 API 成本:如果 Redis 未命中,大量并发请求直接打到供应商 API,触发限流甚至产生额外计费
为了解决这些问题,我们引入了 L1 本地缓存作为第一道防线——在应用进程内存中直接返回热点数据,消除网络开销,大幅削减对 Redis 和供应商 API 的冲击。
- 触发问题
- 热点 API 即使命中 Redis,也会把所有请求压到同一条网络链路和同一批 Redis 分片上。
- 候选方案
- 继续扩 Redis 分片、CDN 缓存、本地缓存、热点 key 分散、主动预热。
- 选择理由
- L1 本地缓存可以在应用进程内直接返回热点数据,最快降低 Redis QPS,也不改变外部 API 的调用模型。
- 代价
- 每台实例都有自己的缓存副本,必须接受短时间不一致,并处理冷启动和批量失效问题。
- 暂不解决
- 暂不把所有 API 都放进本地缓存,只针对可短暂过期、读多写少、热点明显的数据。
突发新闻
但某天,一个突发新闻事件发生了:
突发事件
调用流程分析
问题分析
我查了一下监控:
监控分析
多级缓存系统工作正常,但单个热点 Key 仍然导致 Redis 单点压力。
虽然 L1 本地缓存挡掉了一部分请求,但突发流量远超预期,且冷启动的实例 L1 是空的,请求直接打到了 Redis 上。
我的思考
既然一个 Redis 实例扛不住,是否有一个机制,让流量分散到多个 Redis 实例上,而且支持增减实例?就像这样:
多实例动态分流架构
我去阿里云看看有没有这样的方案,果然看到了这样的选项:
原来云厂商早就把这套机制产品化了——集群版就是我们要找的方案。选择集群版后,系统自动创建多个分片实例,流量通过内置代理自动打散,还支持随时增加分片数量。
但问题来了,有了集群版就能高枕无忧了吗?并不。集群版虽然能把不同 Key 分散到不同分片,但同一个热点 Key 仍然只会落在一个分片上。这就是为什么我们还需要下面的”热点 Key 分散”方案。
解决方案
1. 热点 Key 分散
假设某个热点新闻的ID是:88,我将新闻存到Redis时,原来是直接用这个ID作为Key:
news:88 -> { content: "...", timestamp: "..." }假设现在我有了 3 个 Redis 分片,但我的热点 Key 仍然只落在其中一个分片上,导致这个分片压力过大。有没有办法把这个热点 Key 分散到多个分片上呢?
简单,我通过某种算法,将不同的请求映射到不同的 Key 上:
news:0:88 -> { content: "...", timestamp: "..." }
news:1:88 -> { content: "...", timestamp: "..." }
news:2:88 -> { content: "...", timestamp: "..." }
...
news:9:88 -> { content: "...", timestamp: "..." }这 10 个 Key 就可以分布到不同的 Redis 分片上了。
热点 Key 分散
2. 本地缓存预热
另外,我发现,我的新闻供应商提供了一个 API,可以查询最近的热点新闻列表。这个接口的更新频率是每5分钟一次。
这样的话,我完全可以定期获取这些热点新闻,主动放入内存中,预热 L1 本地缓存。这样一来,即使突发事件发生了,绝大多数请求都能直接命中 L1,完全绕过 Redis 和供应商 API。