Home

场景

java

场景,一般在我们的服务可能有上百个,但是作为读的redis可能只有三四个实例,那么每一次服务查询不到redis都要去数据库里查,怎么解决这种问题?

一、架构层面优化

1. 多级缓存体系

graph TD
    A[客户端] --> B[本地缓存]
    B --> C[分布式Redis]
    C --> D[数据库]
  • 本地缓存:每个服务实例使用Guava/Caffeine/Ehcache

    • 设置合理的过期时间(如30秒)

    • 适合热点数据和变更不频繁的数据

  • 分布式缓存:现有Redis集群

    • 存储全量缓存数据

    • 设置比本地缓存更长的TTL

2. Redis集群扩展方案

  • 读写分离

    • 主实例写,多个只读副本分担查询压力
  • 分片集群

    • 按业务域拆分(如用户数据/商品数据分开)

    • 使用Redis Cluster或代理分片(Twemproxy/Codis)

二、缓存策略优化

1. 缓存预热机制

// 服务启动时加载热点数据
@PostConstruct
public void initHotData() {
    List<String> hotKeys = getHotKeysFromDB();
    hotKeys.forEach(key -> {
        Object value = getFromDB(key);
        redisTemplate.opsForValue().set(key, value);
    });
}

2. 智能缓存穿透防护

  • 布隆过滤器

    • 前置校验key是否存在
    • 减少无效查询
  • 空值缓存

def get_data(key):
    data = redis.get(key)
    if data is None:
        data = db.get(key)
        if data is None:
            redis.set(key, "NULL", ex=300)  # 缓存空值5分钟
        else:
            redis.set(key, data, ex=3600)
    elif data == "NULL":
        return None
    return data

3. 并发控制策略

  • 互斥锁防止缓存击穿:
func GetData(key string) interface{} {
    data := redis.Get(key)
    if data == nil {
        // 获取分布式锁
        if acquireLock(key) {
            defer releaseLock(key)
            data = db.Query(key)
            redis.Set(key, data, TTL)
        } else {
            // 等待短暂时间后重试
            time.Sleep(100 * time.Millisecond)
            return GetData(key)
        }
    }
    return data
}

三、性能优化措施

1. 批量查询优化

  • Pipeline批量操作
# Redis管道示例
echo -e "GET key1\nGET key2\nGET key3" | redis-cli --pipe
  • 多键值合并存储

    • 将关联数据合并为Hash结构存储

2. 缓存更新策略

策略优点缺点适用场景
定时刷新实现简单实时性差变化不频繁的数据
写时更新数据一致性好写压力大强一致性要求
过期剔除系统负载均衡可能短暂不一致最终一致性场景

3. 监控与自动扩缩容

  • 关键监控指标

    • Redis命中率(低于90%需预警)

    • 连接数使用率

    • CPU/内存负载

  • 弹性扩缩容

# 根据监控自动扩展Redis节点示例
if [ $hit_ratio -lt 85 ]; then
    scale_redis_nodes +1
fi

四、实施路线建议

  1. 短期应急方案

    • 为所有服务添加本地缓存

    • 实现空值缓存和基本互斥锁

  2. 中期优化方案

    • 引入多级缓存体系

    • 实施缓存预热

    • 增加Redis只读副本

  3. 长期架构方案

    • 按业务域拆分Redis集群

    • 实现自动化监控扩缩容

    • 构建统一的缓存服务层

五、特别注意事项

  1. 缓存一致性

    • 考虑使用CDC(变更数据捕获)工具如Debezium同步数据库变更
  2. 热点Key处理: java

    复制

    下载

    // 热点Key检测与分散
    String cacheKey = "product_" + productId;
    if(isHotKey(cacheKey)) {
        // 添加随机后缀分散请求
        cacheKey = cacheKey + "_" + ThreadLocalRandom.current().nextInt(10);
    }
  1. 服务降级方案

    • 当Redis不可用时自动降级到本地缓存

    • 实现熔断机制防止雪崩

按钮