为什么要用 Redis 而不用 map/guava 做缓存?
这个问题,本质上是缓存选型问题,我告诉你怎么选,就三条。
- 流量小的,纯数据库够了
- 流量中等的,Redis做中央缓存够了
- C端大流量的查询场景,本地缓存结合中央缓存是标配
就是要看流量等级来选择。

缓存选型策略
按这个框架,分别说每一层该怎么做、要注意什么。
流量小:数据库够用
日活几千、QPS几十几百甚至几千的系统,数据库加上合理的索引就能撑住。这个阶段加缓存是在增加复杂度,多了缓存一致性的问题要处理,多了一个组件要运维,收益很小。
不少团队在项目初期就急着引入Redis,结果花了不少精力处理缓存和数据库之间的数据一致性,得不偿失。能用数据库撑住的阶段,不要急着加缓存。
流量中等:Redis做中央缓存
日活几万到几十万、QPS几万的系统,数据库开始有读压力,这时候引入Redis做中央缓存是最常见的方案。Redis承担读压力,数据库承担写操作和复杂查询,分工明确。
这个阶段用Redis而不是本地缓存,原因有几个。
线上服务部署多个实例,本地缓存是进程内的,每个实例各自维护一份副本。一个实例更新了数据,其他实例的缓存还是旧的。Redis是集中式的,所有实例读写同一份数据,不存在多副本不一致的问题。
Redis的缓存管理能力也完善得多。每个key可以单独设置过期时间,支持多种淘汰策略,自带INFO、SLOWLOG等运维命令,配合Prometheus和Grafana能搭建完整的监控体系。线上出了缓存穿透、击穿、内存不足的问题,有足够的工具去定位。
HashMap和Guava Cache在这些维度上差得远。HashMap没有过期、没有淘汰、没有容量限制,数据只增不减。Guava Cache和Caffeine好一些,有基于容量和时间的淘汰策略,但管理粒度有限,所有key共用一套规则。运维监控也基本是空白,缓存了多少key、内存占了多少、命中率多少,全得自己埋点。
C端大流量:本地缓存 + 中央缓存
C端产品,日活百万级,查询QPS上百万的。这个量级下,每次请求都走网络去Redis取数据,网络往返的延迟和带宽开销会成为瓶颈。
这时候需要在应用进程内加一层本地缓存。请求先查本地,命中直接返回,不走网络;没命中再查Redis;Redis也没有才查数据库。
你需要记住一句话:
在瞬时大流量下,每次查询请求,都尽量不要去数据库读。
那关键问题来了:本地缓存用哪种框架?
本地缓存不能用HashMap
不少人的第一反应是用HashMap或者Guava Cache。能跑,但在生产环境下隐患很大。
HashMap和Guava Cache的数据都存在JVM堆里。业务数据增长、缓存key设计不合理、淘汰策略没配好,堆内存占用可以在短时间内飙升。堆内存一紧张,JVM就会触发Full GC。Full GC期间所有业务线程暂停,接口响应全部超时。C端高并发场景下,一次Full GC就能引发连锁故障:上游调用超时,重试放大流量,系统雪崩。
HashMap还没有任何缓存管理能力。不支持过期、不支持淘汰、不支持容量限制。数据只增不减,迟早把内存撑满。
JVM一重启,堆内缓存全部丢失。服务重启后需要重新加热缓存,这段时间所有请求直接打到数据库。流量大的时候,冷启动可以直接把数据库打挂。
堆内存放业务对象已经够紧张了,别再往里塞大量缓存数据。
当然,你可以使用「堆内的本地缓存」去存一些数据量小,又很少变化的数据。这个没问题的。
堆外缓存方案
我之前做过一个C端项目,要扛住百万级的瞬时流量。当时用了本地缓存,但不是HashMap或Guava,用的是堆外缓存。数据放在JVM堆外的直接内存里,不参与GC,不会因为缓存数据量大而影响业务线程。
注意,需要在本地缓存里存很多数据的,堆外缓存算是最好的选择了。
如果业务场景需要本地缓存,又担心GC影响,堆外缓存是更稳妥的选择。目前主流的几个方案:
OHC(Off-Heap Cache):Apache Cassandra团队开发的堆外缓存库,纯Java实现。提供类似ConcurrentMap的API,支持LRU淘汰和过期策略。Cassandra内部用它来做行缓存。适合需要高吞吐的场景。
Ehcache 3:支持分层存储,可以配置堆内、堆外、磁盘三级缓存。堆外层基于java.nio.ByteBuffer实现。配置灵活,适合对缓存分层有要求的项目。
我当年没有选择Ehchche 3的原因,是开源协议不满足,具体我不太记得了。因此选择了一个当年还比较冷门的OHC。
如果对GC不太敏感、数据量也可控,Caffeine是堆内本地缓存的比较好的选择。
对比速查表
| 维度 | HashMap | Guava/Caffeine | 堆外缓存 | Redis |
|---|---|---|---|---|
| 内存区域 | JVM堆 | JVM堆 | 直接内存 | 独立进程 |
| GC影响 | 大 | 大 | 无 | 无 |
| 容量上限 | 受堆大小限制 | 受堆大小限制 | 受物理内存限制 | 独立扩展 |
| 过期淘汰 | 不支持 | 支持,粒度有限 | 部分支持 | 完善,按key设过期时间 |
| 多实例共享 | 不支持 | 不支持 | 不支持 | 支持 |
| 数据结构 | KV | KV | KV | String/Hash/List/Set/ZSet等 |
| 持久化 | 无 | 无 | 部分支持 | RDB/AOF |
| 运维监控 | 无 | 有限 | 有限 | 完善 |
小结
缓存选型不要上来就想用什么组件,先看流量在哪个量级。大部分系统用Redis做中央缓存就够了。到了需要本地缓存的阶段,说明流量已经大到需要认真考虑GC和内存管理了,这时候堆外缓存比HashMap靠谱得多。
缓存不是越多越好,每多一层就多一层复杂度。用最少的缓存层级满足当前的性能需求,是最合理的策略。
更多推荐




所有评论(0)