Redis缓存设计与性能优化指南
Redis缓存设计与性能优化指南
一、缓存穿透
问题描述
查询不存在的数据,缓存和DB均未命中,导致每次请求直达数据库。
原因
- 自身业务代码或数据问题
- 恶意攻击/爬虫大量请求不存在的数据
解决方案
✅ 缓存空对象
String get(String key) {String cacheValue = cache.get(key);if (StringUtils.isBlank(cacheValue)) {String storageValue = storage.get(key);cache.set(key, storageValue);// 存储值为空时设置短过期时间(300秒)if (storageValue == null) {cache.expire(key, 300);}return storageValue;}return cacheValue;
}
✅ 布隆过滤器(Bloom Filter)
- 原理:使用位数组+多个无偏哈希函数,判断元素一定不存在或可能存在
- 特点:
- 适用于数据固定、实时性低的场景
- 不支持删除操作(需重新初始化)
- Redisson实现:
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("sample"); bloomFilter.tryInit(100000L, 0.03); // 初始化元素量+误判率 bloomFilter.add("key1"); // 添加数据 bloomFilter.contains("key1"); // 校验存在性
二、缓存击穿(失效)
问题描述
大批量缓存在同一时间失效,导致请求穿透至数据库。
解决方案
// 批量设置缓存时分散过期时间
for (int i = 0; i < items.size(); i++) {int expireTime = 1800 + new Random().nextInt(300); // 1800±300秒cache.set(key, value, expireTime);
}
三、缓存雪崩
问题描述
缓存层崩溃后,流量直接冲击存储层导致级联故障。
解决方案
- 高可用架构:使用Redis Cluster/Sentinel
- 限流降级:
- 非核心数据:返回预定义空值/错误信息
- 核心数据(如库存):允许查缓存或DB
- 容灾演练:提前模拟缓存宕机场景
四、热点Key重建优化
问题:热点Key失效瞬间,大量线程并发重建缓存。
方案:互斥锁控制重建
String rebuildCache(String key) {String value = cache.get(key);if (value == null) {String lockKey = "lock:" + key;if (lock.tryLock()) { // 获取分布式锁value = db.get(key); // 查询数据库cache.set(key, value); // 重建缓存lock.unlock();} else {Thread.sleep(100); // 等待其他线程完成return cache.get(key); // 重试获取}}return value;
}
五、缓存与DB双写不一致
场景分析
- 双写不一致:并发写导致数据覆盖
- 读写不一致:读操作与写操作并发
解决方案
场景 | 策略 |
---|---|
低并发数据 | 设置缓存过期时间 + 定期主动更新 |
允许短暂不一致 | 缓存过期时间兜底 |
强一致性要求 | 分布式读写锁(如Redisson) |
异步监听 | 通过Canal订阅Binlog更新缓存 |
📌 写多读多且强一致性场景,建议直接读写DB或采用缓存为主存储+DB异步备份。
六、键值设计规范
🔑 Key设计
- 规范:
业务名:表名:id
(例:trade:order:1
) - 简洁性:控制长度(例:
u:{uid}:fr:m:{mid}
) - 禁用字符:空格、换行符、引号等
📦 Value设计
⚠️ 避免BigKey
- 定义:
- String类型 > 10KB
- 集合元素 > 5000个
- 危害:
- 阻塞Redis
- 网络拥塞
- 删除操作引发延迟
- 优化方案:
- 拆分:大Hash拆分为小Key(例:
user:1
→user:1:info
,user:1:orders
) - 选择合适结构:实体类型用Hash替代多个String
- 拆分:大Hash拆分为小Key(例:
⭐ 最佳实践
- 设置过期时间:
expire key seconds
- 过期时间打散:避免集中失效
七、命令使用规范
- 避免O(N)命令:
- 使用
HSCAN
/SSCAN
替代HGETALL
/SMEMBERS
- 使用
- 禁用危险命令:
rename-command KEYS "" rename-command FLUSHALL ""
- 高效批量操作:
- 原生批量命令:
MGET
/MSET
- Pipeline非原子操作(一次批量 ≤ 500元素)
- 原生批量命令:
八、客户端优化
🔗 连接池配置
参数 | 含义 | 建议值 |
---|---|---|
maxTotal | 最大连接数 | 按QPS计算(例:QPS=50000 → 50+连接) |
maxIdle | 最大空闲连接 | 与maxTotal 一致 |
minIdle | 最小空闲连接 | 预热连接至该值 |
testOnBorrow | 借出时校验 | 高并发设为false |
预热连接池示例:
List<Jedis> minIdleList = new ArrayList<>(jedisPoolConfig.getMinIdle());
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {Jedis jedis = pool.getResource();minIdleList.add(jedis);jedis.ping(); // 预热连接
}
for (Jedis jedis : minIdleList) {jedis.close(); // 归还连接池
}
九、内存策略优化
♻️ 内存淘汰策略(maxmemory-policy)
策略 | 适用场景 |
---|---|
volatile-lru | 过期Key中淘汰最近最少使用(推荐) |
allkeys-lru | 所有Key中淘汰最近最少使用 |
volatile-lfu | 过期Key中淘汰访问频率最低 |
noeviction | 不淘汰,拒绝写入(默认) |
4.0+版本支持LFU策略,适合热点数据场景。
⚙️ 内核参数调优
# 调整SWAP倾向(避免OOM Kill)
echo vm.swappiness=1 >> /etc/sysctl.conf# 允许内存超分配(保障fork成功)
echo vm.overcommit_memory=1 >> /etc/sysctl.conf# 增加文件句柄数
ulimit -n 65535
十、慢查询监控
# 设置慢查询阈值(单位:微秒)
config set slowlog-log-slower-than 1000 # 保存慢查询日志条数
config set slowlog-max-len 1024 # 查看慢查询日志
slowlog get 5
📌 生产环境建议阈值设为1ms(1000微秒)
总结:缓存设计的核心在于平衡性能与一致性,避免过度设计。优先解决穿透、雪崩、击穿问题,规范Key设计,警惕BigKey,合理配置连接池和淘汰策略,结合监控实现高性能缓存方案。