缓存设计规范
鹿Sir上线,见字如面。
在高并发系统中,缓存是提升性能的“王牌武器”——它能将数据库的毫秒级查询压缩到微秒级,轻松扛住流量洪峰。但不少团队在使用缓存时,常会踩中雪崩、穿透、击穿的“深坑”,甚至因缓存滥用导致系统OOM。
今天我们就从实战角度,梳理缓存设计的核心逻辑、使用规范及避坑技巧。
0x1
缓存该用在什么场景?
不是所有数据都适合放缓存,盲目使用反而会增加系统复杂度。以下5类场景是缓存的“最佳阵地”,覆盖90%的业务需求:
静态数据:字典表、配置项等“一成不变”的数据这类数据几乎不发生变更,缓存命中率能达到100%。比如系统中的字典表(性别、学历枚举)、地区行政区划数据、固定的业务配置(如折扣比例阈值)。建议直接用本地缓存+分布式缓存的二级缓存架构,最大化提升性能。
准静态数据:变化频率极低的数据比静态数据多了“偶尔变更”的特性,比如部门组织架构、用户角色权限(通常按天更新)。这类数据可设置较长的过期时间,配合“变更时主动更新缓存”的策略,既保证性能又兼顾一致性。
中间状态数据:可复用的计算结果、临时副本复杂业务逻辑的中间计算结果,若多次复用可缓存减少重复计算。比如大数据报表的中间统计结果、配置中心的本地副本(减少远程拉取次数)。这类数据需关注有效期,避免因源数据变更导致计算结果失效。
热数据:短时间内被高频访问的数据高并发场景的核心诉求,比如大促时的爆款商品信息、热门活动页面数据。建议用“本地缓存拦截高频请求+分布式缓存兜底”的模式,本地缓存设置秒级过期时间,防止缓存不一致。
读写比极高的数据:读频率远大于写频率缓存的核心优势是提升读性能,若写操作过于频繁,缓存维护成本会超过收益。典型场景如商品详情页(读:写≈1000:1)、用户个人资料(读:写≈100:1);反例如订单表(读写比接近1:1),不适合缓存。
禁忌场景提醒:1. 变动频率高的数据(如实时交易金额):缓存与数据库一致性难维护,易引发业务错误; 2. 一致性要求高的数据(如支付状态):必须依赖数据库事务,缓存无法保证强一致。
0x2
缓存有效性的5个核心指标
缓存上线后不是“一劳永逸”,需通过监控及时发现问题。以下5个指标是缓存健康度的“体温计”:
命中率:缓存有效性的核心指标计算公式:
命中率=缓存命中次数/(缓存命中次数+缓存未命中次数)。命中率越高,缓存价值越大——静态字典表需达到100%,热数据建议≥95%。若命中率过低,需排查是否场景选错(如写频过高)或key设计不合理(如key粒度太细)。读写比:判断缓存性价比的关键计算公式:
读写比=读请求次数/写请求次数。建议读写比≥10:1再使用缓存,否则写操作带来的缓存更新成本(如删除/刷新)会抵消读性能收益。过期时间:控制缓存规模的“阀门”除静态数据外,绝大部分缓存需设置过期时间,防止缓存无限增长导致OOM。长期有效的数据(如字典表),建议用定时任务定时刷新或事件触发更新,避免永久缓存堆积。
占用空间:避免资源耗尽的“红线”上线前需预估缓存空间,运维据此分配资源;上线后关注单key平均大小和总占用。重点警惕Redis集合对象(map、list等)和本地缓存的key数量——本地缓存若用集合对象存value,即使限制了key数,也可能因value过大导致OOM。
读写耗时:警惕缓存成为新瓶颈正常缓存读写耗时应在几毫秒级,若超过10毫秒需紧急排查:Redis可能存在bigkey阻塞、网络延迟过高,或本地缓存锁竞争严重。
0x3
缓存三大核心问题解决方案
缓存雪崩、击穿、穿透是高并发场景的“老三样”问题,一旦发生可能导致系统雪崩。以下是经过实战验证的解决方案:
场景:大促零点,大量商品缓存同时过期,所有请求直达数据库,导致数据库连接耗尽。
解决方案:
过期时间打散:设置过期时间时加随机值,比如基础过期时间30分钟,再加0-5分钟随机值,避免大量key同时过期。代码示例:
expireTime = 30*60 + Random.nextInt(300)。加锁防重复回源:缓存未命中时,用分布式锁(如Redis的setNx)控制只有一个请求去数据库回源,其他请求等待缓存更新后再读取。注意加锁后先检查缓存是否已存在,避免拿到锁后重复回源。
热点数据永不过期:对核心热点数据(如大促爆款)设置永不过期,通过后台定时任务刷新或数据变更时主动更新,避免过期触发回源。
场景:某爆款商品缓存过期瞬间,数万请求同时查询该商品,直达数据库导致单表查询压力骤增。
解决方案:
热点数据特殊处理:设置热点数据过期时间为业务低峰期(如凌晨3-5点),或直接设置永不过期,通过主动更新维持一致性。
互斥锁+短暂兜底:缓存未命中时,加锁回源并设置临时缓存(如10秒过期),其他请求先返回临时缓存,避免同时冲击数据库。
场景:黑客用不存在的用户ID批量请求,缓存未命中后直达数据库,导致数据库空查风暴。
解决方案:
前置校验+风控:接口层增加参数校验(如用户ID格式校验),结合风控系统拦截恶意IP,从源头减少非法请求。
缓存空值/默认值:数据库查询为空时,缓存空值或默认值(如空对象),并设置较短过期时间(如5分钟),避免缓存空值堆积。数据插入时需主动删除或更新缓存。
布隆过滤器拦截:将数据库所有存在的key(如用户ID、商品ID)预载入布隆过滤器,请求先经过过滤器判断,不存在则直接返回,无需查询缓存和数据库。布隆过滤器误判率可控制在0.1%以下,且占用空间极小。
0x4
缓存落地细则
不同缓存类型的适用场景和规范不同,以下是本地缓存(如Caffeine)和Redis缓存的强制规范,覆盖90%的业务场景:
本地缓存优势是毫秒级响应,缺点是容量小、多节点不一致,需严格控制使用场景和规模。
核心规范
【强制】空间上限控制:必须设置最大元素数量,防止OOM。value禁止使用集合对象或大对象,建议用简单数据类型(如String)。
【推荐】组件统一:全量本地缓存封装通用组件(预加载、定时更新);普通本地缓存用Caffeine,优先用注解实现,禁止重复造轮子。
【推荐】使用场景:小容量不常更新数据(如字典表)、热点数据短时间缓冲(如5秒过期的爆款商品信息),或proxy层减少RPC请求(如组合数据的短缓存)。
Redis是分布式缓存的核心,但滥用会导致性能问题甚至数据丢失,以下是必须遵守的强制规范:
一、基础原则
冷热数据分离,禁止将所有数据放入Redis;Redis不做持久化,不能当数据库用,必须有回源数据库的逻辑。
不同业务用key前缀区分(格式:{业务系统}:{模块}:{标识}),禁止跨业务直接调用缓存,需通过RPC接口中转。正例:
system:common:custom-config-list;反例:直接用123当key。key长度≤100字符,用统一常量文件管理所有key,避免多处书写导致修改遗漏。
除特殊场景(如热点数据永不过期),所有key必须设置过期时间,无过期时间需组长审核。
二、bigkey治理
bigkey是Redis性能杀手,会导致阻塞、带宽占用过高,以下是判定标准和解决方案:
bigkey判定:String类型≥100KB、List/ZSET/HASH成员数≥5000,或HASH成员总大小≥100MB。
解决方案:拆分bigkey——将一个大key拆分为多个小key,用MGET批量获取(注意key的slot分布和MGET数量≤200)。
粉丝列表优化:大V粉丝列表不直接用List/ZSET,拆分为粉丝ID分片(如按用户ID取模),每个分片存部分粉丝ID,粉丝信息单独存储。查询时先分页取ID,再批量查信息。
热点数据分级:仅预加载热点分页数据的前几页,后续数据按需加载,减少缓存体量。
三、命令与组件规范
禁用高危命令:线上禁止使用KEYS、FLUSHALL、FLUSHDB等阻塞或清数据的命令,扫描key用SCAN渐进式处理。
组件统一:二级缓存(本地+Redis)用mycache组件;避免循环中操作Redis,改用批量命令(如MGET、HMSET),批量操作key≤200或数据量≤2000KB。
回源耗时控制:回源逻辑复杂(如复杂SQL)时,延长缓存TTL至30分钟以上,或用“本地缓存+Redis”二级缓存;大促期间禁止回源,通过异步刷新维持缓存。
数据库数据变更时(增删改),必须无条件删除或更新对应缓存,避免缓存脏数据。
二级缓存(本地+Redis)中,本地缓存TTL设为5-10秒,即使分布式缓存更新消息丢失,也能通过本地缓存过期保证最终一致。
热key更新用“更新缓存”策略+写入锁;非热key用“删除缓存”策略,查询时回源重建。
数据库主从分离场景,强一致需求需从主库回源写入缓存,避免从库延迟导致缓存脏数据。
0x5
缓存设计的核心逻辑
缓存设计不是“有缓存就好”,而是“合适的场景用合适的缓存”:
场景优先:先判断数据是否符合“读多写少、热点、非强一致”三大特征,再决定是否用缓存;
监控兜底:命中率、读写比、空间占用三大指标必须监控,提前发现问题;
规范落地:本地缓存控容量、Redis控bigkey和key规范、一致性靠“变更必更缓存”,避免踩坑;
分层防御:雪崩用过期打散+加锁、击穿用热点永不过期、穿透用布隆过滤器,三层防御保障系统稳定。
缓存是高性能系统的“加速器”,但只有遵循规范、精准落地,才能真正发挥其价值,而不是成为系统的“不定时炸弹”。
EOF
关于鹿Sir「微信:Jensvn」
分享架构技术/IT资讯/牛马日常
电商/SaaS架构师/DDD极客,COLA-DDD/DDD4j框架作者
→关注公众号,撩小码鹿「已接入AI」
→加我备注“进群”,进技术大佬群学习