命中率降低
Redis 缓存命中率降低,可能是由于多个因素导致的,比如缓存未命中、缓存污染、缓存淘汰过快等。针对不同情况,可以采取以下优化措施:
1. 分析缓存命中率下降的原因
在优化之前,先使用 Redis 监控工具 分析命中率下降的具体原因:
redis-cli info stats | grep "keyspace_hits\|keyspace_misses"
keyspace_hits
表示缓存命中次数keyspace_misses
表示缓存未命中次数- 计算缓存命中率:
2. 减少缓存穿透
问题:请求的 Key 在缓存和数据库中都不存在,导致每次请求都要查询数据库,增加 keyspace_misses
。
解决方案:
- 布隆过滤器(Bloom Filter)
在 Redis 之前使用 布隆过滤器 存储可能存在的 Key,避免无效查询:import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 100000); bloomFilter.put(12345); // 预存 Key if (!bloomFilter.mightContain(54321)) { return "数据不存在"; // 直接返回,避免数据库查询 }
- 缓存空值
例如,对于查询不到的 Key 存入一个短 TTL(如 60s)的空值:
风险:要避免缓存大量空值,占用 Redis 内存。SET key "null" EX 60
3. 减少缓存击穿
问题:某个热点 Key 失效后,短时间内大量请求击中数据库,导致 Redis 负载降低,数据库压力激增。
解决方案:
- 设置热点 Key 的自动续期(使用 逻辑过期+异步重建)
String value = redis.get("hot_key"); if (value == null) { if (redis.setNx("lock_hot_key", "1", 10)) { // 尝试获取锁 value = dbQuery(); // 查询数据库 redis.setEx("hot_key", value, 60); // 重新缓存 redis.del("lock_hot_key"); // 释放锁 } else { Thread.sleep(100); // 等待其他线程加载完成 value = redis.get("hot_key"); } }
- 预加载数据:
- 提前加载热点数据到 Redis,避免突然失效带来的冲击。
- 使用 Lua 脚本 实现 原子性 续期:
local ttl = redis.call("TTL", KEYS[1]) if ttl > 0 then redis.call("EXPIRE", KEYS[1], ttl + 30) -- 续期 30s end
4. 减少缓存雪崩
问题:大量 Key 同时过期,导致 Redis 负载骤降,数据库压力激增。
解决方案:
- 设置随机过期时间,避免同一时刻大量 Key 失效:
redis.setEx("key", value, 60 + new Random().nextInt(30)); // 60-90s 随机过期
- 分批加载缓存
- 在高并发场景下,采用 预热缓存 的方式,在系统启动或数据更新时提前加载缓存。
5. 优化缓存淘汰策略
问题:Redis 可能因为 内存不足 频繁淘汰数据,导致缓存命中率下降。
解决方案:
- 调整淘汰策略:
volatile-lru
(淘汰最近最少使用的 key)allkeys-lru
(淘汰所有 key 中最近最少使用的)volatile-ttl
(优先淘汰快过期的 key)allkeys-random
(随机淘汰)- 设置适合的 maxmemory-policy:
CONFIG SET maxmemory-policy allkeys-lru
- 使用大 Key 拆分
- 如果某个 Key 过大(如存储了整个用户历史数据),可拆分成多个小 Key,减少单 Key 失效影响。
6. 提升 Redis 读取性能
问题:如果 Redis 本身读取性能下降,也可能导致命中率降低。
解决方案:
- 优化数据结构:
- 使用 Hash 代替 String:
redis.hset("user:1001", "name", "Alice"); redis.hset("user:1001", "age", "25");
- 减少大 Key 读取,如 List, Set, Zset,避免一次性加载大量数据。
- 使用 Hash 代替 String:
- 开启 Redis 读写分离:
- 部署 Redis 主从架构,让从节点分担读取压力:
redis-cli -h master-ip replicaof slave-ip 6379
- 在 Spring Boot 代码中配置多个 Redis 连接:
spring: redis: cluster: nodes: 192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379
- 部署 Redis 主从架构,让从节点分担读取压力:
总结
问题 | 优化方案 |
---|---|
缓存穿透 | 布隆过滤器、缓存空值 |
缓存击穿 | 热点数据续期、逻辑过期、预加载 |
缓存雪崩 | 随机过期时间、分批加载 |
缓存淘汰 | 调整淘汰策略、拆分大 Key |
Redis 读写 | 读写分离、优化数据结构 |
Redis 命中率下降的根本原因在于 缓存未命中次数增加 或 缓存数据被频繁淘汰,针对不同的情况,可以结合 预防 + 限流 + 监控 + 读写分离 等手段优化。
如果你的 Redis 缓存命中率持续降低,可以具体分析 请求 Key 的访问分布、数据更新频率、查询模式,再针对性优化。🚀
穿透、击穿、雪崩
缓存穿透:请求一个不存在的数据,导致请求直接到达数据库。
解决方案:对空值缓存,使用布隆过滤器,拦截不存在的请求,避免数据库查询。
缓存击穿:在高并发下,某个数据的缓存失效,导致大量请求同时访问数据库。
解决方案:对热点数据加锁(分布式锁),或使用互斥锁(分布式锁),确保在缓存更新时只允许一个请求访问数据库。
缓存雪崩:大量缓存同时过期,导致瞬间大量请求涌向数据库。
解决方案:设置不同的过期时间,随机化缓存的过期时间,避免集中失效。