Redis缓存设计与性能优化指南

发布于:2025-07-14 ⋅ 阅读:(39) ⋅ 点赞:(0)

Redis缓存设计与性能优化指南


一、缓存穿透

问题描述
查询不存在的数据,缓存和DB均未命中,导致每次请求直达数据库。

原因

  1. 自身业务代码或数据问题
  2. 恶意攻击/爬虫大量请求不存在的数据

解决方案

✅ 缓存空对象
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);
}

三、缓存雪崩

问题描述
缓存层崩溃后,流量直接冲击存储层导致级联故障。

解决方案

  1. 高可用架构:使用Redis Cluster/Sentinel
  2. 限流降级
    • 非核心数据:返回预定义空值/错误信息
    • 核心数据(如库存):允许查缓存或DB
  3. 容灾演练:提前模拟缓存宕机场景

四、热点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
    • 网络拥塞
    • 删除操作引发延迟
  • 优化方案
    1. 拆分:大Hash拆分为小Key(例:user:1user:1:info, user:1:orders
    2. 选择合适结构:实体类型用Hash替代多个String
⭐ 最佳实践
  • 设置过期时间:expire key seconds
  • 过期时间打散:避免集中失效

七、命令使用规范

  1. 避免O(N)命令
    • 使用HSCAN/SSCAN替代HGETALL/SMEMBERS
  2. 禁用危险命令
    rename-command KEYS ""
    rename-command FLUSHALL ""
    
  3. 高效批量操作
    • 原生批量命令: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,合理配置连接池和淘汰策略,结合监控实现高性能缓存方案。


网站公告

今日签到

点亮在社区的每一天
去签到