CC 咖啡猫的工作空间 Coding Space

开发中的多级缓存

  • L1 本地缓存无网络延迟,适合热点数据(如首页配置、爆款商品详情)
  • L2 分布式缓存解决数据共享问题,避免每个节点重复查 DB
  • L3 兜底保证数据源头可靠

一、缓存基础概念

2.1 缓存的作用

  • 减少数据库压力:避免重复查询相同数据
  • 提高响应速度:内存访问比磁盘访问快几个数量级
  • 降低系统耦合:缓存作为中间层,隔离应用与数据库

2.2 缓存命中率

缓存命中率 = 命中缓存的请求数 / 总请求数 × 100%

理想的缓存命中率应该在95%以上,低于80%需要优化缓存策略。

2.3 缓存常见问题

  • 缓存穿透:查询不存在的数据,导致每次都要查询数据库
  • 缓存击穿:热点数据过期瞬间,大量请求直接打到数据库
  • 缓存雪崩:大量缓存同时过期,导致数据库瞬间压力剧增

二、多级缓存存储逻辑详解

2.1 数据流向与存储层级

2.1.1 读取流程(自上而下)

用户请求 → L1浏览器缓存 → L2 CDN缓存 → L3本地缓存 → L4 Redis缓存 → 数据库
          ↑回填        ↑回填         ↑回填         ↑回填

2.1.2 写入流程(自下而上)

数据更新 → 数据库 → 清除L4 Redis缓存 → 清除L3本地缓存 → 清除L2 CDN缓存 → 清除L1浏览器缓存

2.1.3 各层级存储特点

层级 存储介质 数据持久性 共享范围 更新复杂度
L1 浏览器缓存 用户本地磁盘/内存 单用户 高(需HTTP控制)
L2 CDN缓存 边缘节点内存/磁盘 区域用户 中(需CDN API)
L3 本地缓存 应用服务器内存 单机实例 低(本地操作)
L4 Redis缓存 Redis服务器内存 集群所有实例 中(需网络通信)

2.2 缓存更新策略

2.2.1 Cache-Aside Pattern(旁路缓存)- 推荐方案

// 读操作 - 先读缓存,未命中再读DB
public Product getProduct(Long id) {
    // 1. 查L3本地缓存
    Product product = localCache.getIfPresent(id);
    if (product != null) return product;
    
    // 2. 查L4 Redis缓存
    product = redisTemplate.opsForValue().get("product:" + id);
    if (product != null) {
        localCache.put(id, product); // 回填L3
        return product;
    }
    
    // 3. 查数据库
    product = database.getProduct(id);
    if (product != null) {
        // 4. 逐级回填缓存
        redisTemplate.opsForValue().set("product:" + id, product, 30, TimeUnit.MINUTES);
        localCache.put(id, product);
    }
    return product;
}

// 写操作 - 先更新DB,再删除缓存
public void updateProduct(Product product) {
    // 1. 更新数据库
    database.updateProduct(product);
    
    // 2. 删除L4 Redis缓存
    redisTemplate.delete("product:" + product.getId());
    
    // 3. 删除L3本地缓存
    localCache.invalidate(product.getId());
    
    // 4. 触发CDN和浏览器缓存刷新(异步)
    cacheRefreshService.refreshCDNAndBrowserCache("product:" + product.getId());
}

2.2.2 Write-Through Pattern(写穿透)

  • 特点:写操作同时更新缓存和数据库
  • 适用场景:数据一致性要求极高的场景
  • 缺点:写性能较低,缓存不可用时写操作失败
public void updateProductWriteThrough(Product product) {
    // 同时更新数据库和缓存
    database.updateProduct(product);
    redisTemplate.opsForValue().set("product:" + product.getId(), product, 30, TimeUnit.MINUTES);
    localCache.put(product.getId(), product);
}

2.2.3 Write-Behind Pattern(写回)

  • 特点:只更新缓存,异步批量更新数据库
  • 适用场景:写密集型、对一致性要求不高的场景
  • 风险:缓存宕机可能导致数据丢失

2.3 多级缓存一致性保证

2.3.1 一致性挑战

  • L3本地缓存:集群环境下各实例数据不一致
  • L4 Redis缓存:分布式环境下可能存在短暂不一致
  • 跨层级一致性:各级缓存之间需要协调更新

2.3.2 一致性解决方案

方案一:主动失效 + 延迟双删
public void updateProductWithDoubleDelete(Product product) {
    // 第一次删除 - 清除可能的脏数据
    invalidateAllCacheLevels(product.getId());
    
    // 更新数据库
    database.updateProduct(product);
    
    // 延迟一段时间后再次删除 - 防止并发读取脏数据
    scheduledExecutor.schedule(() -> {
        invalidateAllCacheLevels(product.getId());
    }, 100, TimeUnit.MILLISECONDS);
}

private void invalidateAllCacheLevels(Long productId) {
    localCache.invalidate(productId);
    redisTemplate.delete("product:" + productId);
    // 异步通知CDN和浏览器缓存失效
    asyncCacheInvalidateService.invalidateCDNAndBrowser(productId);
}
方案二:消息队列最终一致性
// 数据更新后发送消息
public void updateProductWithMQ(Product product) {
    database.updateProduct(product);
    
    // 发送缓存失效消息
    messageQueue.send(new CacheInvalidateMessage("product", product.getId()));
}

// 缓存服务监听消息并处理
@MessageListener
public void handleCacheInvalidate(CacheInvalidateMessage message) {
    // 所有应用实例都会收到消息并清除本地缓存
    localCache.invalidate(message.getEntityId());
    redisTemplate.delete(message.getEntityType() + ":" + message.getEntityId());
}
方案三:版本号控制
// 在缓存value中包含版本号
public class CachedProduct {
    private Product product;
    private long version;      // 数据版本号
    private long timestamp;    // 缓存时间戳
}

// 读取时检查版本号
public Product getProductWithVersion(Long id) {
    CachedProduct cached = localCache.getIfPresent(id);
    if (cached != null) {
        // 检查版本是否过期(比如5秒内)
        if (System.currentTimeMillis() - cached.getTimestamp() < 5000) {
            return cached.getProduct();
        }
    }
    
    // 从Redis或DB获取最新数据
    return loadFromHigherLevel(id);
}

2.4 缓存预热策略

2.4.1 启动预热

@Component
public class CacheWarmupService {
    
    @PostConstruct
    public void warmupHotData() {
        // 预热热点商品数据
        List<Long> hotProductIds = getHotProductIds();
        for (Long productId : hotProductIds) {
            Product product = database.getProduct(productId);
            if (product != null) {
                // 预热到Redis
                redisTemplate.opsForValue().set("product:" + productId, product, 60, TimeUnit.MINUTES);
                // 预热到本地缓存(可选,启动时不预热本地缓存也可以)
            }
        }
    }
}

2.4.2 定时预热

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点预热
public void scheduledWarmup() {
    // 根据业务规律预热第二天的热点数据
    warmupTomorrowHotData();
}

2.4.3 动态预热

// 基于访问模式的动态预热
public Product getProductWithDynamicWarmup(Long id) {
    Product product = getProduct(id);
    
    // 如果是热点商品,预热相关商品
    if (isHotProduct(id)) {
        warmupRelatedProducts(id);
    }
    
    return product;
}

2.5 缓存失效策略

2.5.1 TTL(Time To Live)策略

  • 固定TTL:所有数据设置相同的过期时间
  • 动态TTL:根据数据热度设置不同的过期时间
  • 随机TTL:在基础TTL上增加随机值,防止雪崩
// 动态TTL示例
public long calculateTTL(Long productId) {
    int accessCount = getProductAccessCount(productId);
    if (accessCount > 10000) {
        return 60 * 60; // 热点商品1小时
    } else if (accessCount > 1000) {
        return 30 * 60; // 普通商品30分钟
    } else {
        return 10 * 60; // 冷门商品10分钟
    }
}

2.5.2 LRU/LFU淘汰策略

  • LRU(Least Recently Used):淘汰最近最少使用的数据
  • LFU(Least Frequently Used):淘汰使用频率最低的数据
  • Caffeine默认:Window TinyLFU,在LRU和LFU之间取得平衡

2.5.3 主动失效触发条件

  • 数据更新:业务数据发生变化时主动失效
  • 定时清理:定期清理长时间未使用的缓存
  • 内存压力:内存使用达到阈值时触发淘汰
  • 外部事件:配置变更、系统维护等外部事件

2.6 多级缓存容量规划

2.6.1 容量分配原则

  • L1浏览器缓存:由前端控制,通常几MB到几十MB
  • L2 CDN缓存:由CDN服务商控制,通常GB级别
  • L3本地缓存:单机JVM内存的20-30%,通常几百MB
  • L4 Redis缓存:独立部署,可根据需求扩展到TB级别

2.6.2 容量计算示例

// 估算本地缓存容量
public class CacheCapacityCalculator {
    
    // 单个缓存条目大小估算
    public long estimateEntrySize(Object value) {
        // 使用Java Agent或序列化方式估算对象大小
        return ObjectSizeCalculator.getObjectSize(value);
    }
    
    // 计算最大缓存条目数
    public int calculateMaxSize(long jvmHeapSize, double cacheRatio, long avgEntrySize) {
        long cacheMemory = (long) (jvmHeapSize * cacheRatio);
        return (int) (cacheMemory / avgEntrySize);
    }
    
    // 示例:8GB JVM堆内存,25%用于缓存,平均条目1KB
    // maxSize = (8 * 1024 * 1024 * 1024 * 0.25) / 1024 = 2,097,152 条目
}

2.7 缓存监控与运维

2.7.1 多级缓存监控指标

指标 L1浏览器 L2 CDN L3本地 L4 Redis
命中率
响应时间
内存使用率
请求量
失效率

2.7.2 缓存健康检查

@Component
public class MultiLevelCacheHealthIndicator implements HealthIndicator {
    
    @Override
    public Health health() {
        Map<String, Object> details = new HashMap<>();
        
        // 检查本地缓存
        CacheStats localStats = localCache.stats();
        details.put("localCacheHitRate", localStats.hitRate());
        details.put("localCacheSize", localCache.estimatedSize());
        
        // 检查Redis缓存
        try {
            Boolean redisConnected = redisTemplate.getConnectionFactory().getConnection().isConnected();
            details.put("redisConnected", redisConnected);
        } catch (Exception e) {
            return Health.down().withDetail("redisError", e.getMessage()).build();
        }
        
        // 综合健康状态
        double hitRate = localStats.hitRate();
        if (hitRate < 0.8) {
            return Health.down()
                    .withDetail("reason", "Cache hit rate too low")
                    .withDetails(details)
                    .build();
        }
        
        return Health.up().withDetails(details).build();
    }
}

三、多级缓存架构

3.1 多级缓存定义

多级缓存是指在系统中部署多个层次的缓存,形成缓存链,每一级缓存都有不同的特点和用途。

3.2 典型多级缓存架构

3.2.1 四级缓存架构

L1: 浏览器缓存 (HTTP Cache)
L2: CDN缓存 (Content Delivery Network)
L3: 应用本地缓存 (Local Cache)
L4: 分布式缓存 (Redis/Memcached)

3.2.2 各级缓存特点

缓存层级 存储位置 访问速度 容量 一致性 适用场景
L1 浏览器缓存 用户浏览器 最快 (ms级) 静态资源、API响应
L2 CDN缓存 边缘节点 快 (10-50ms) 静态资源、图片视频
L3 本地缓存 应用服务器内存 很快 (μs级) 热点数据、配置信息
L4 分布式缓存 独立缓存服务器 快 (0.1-1ms) 共享数据、会话信息

3.3 多级缓存工作流程

  1. 请求首先检查浏览器缓存(ETag/Last-Modified)
  2. 未命中则请求CDN,CDN检查边缘节点缓存
  3. CDN未命中则回源到应用服务器
  4. 应用服务器先检查本地缓存(如Caffeine、Guava Cache)
  5. 本地缓存未命中则查询分布式缓存(如Redis)
  6. 分布式缓存未命中则查询数据库,并逐级回填缓存

四、后端开发视角下的缓存范围澄清

4.1 浏览器缓存是否属于后端缓存范畴?

答案:通常不属于

后端开发关注的缓存层级

从后端开发的角度,一般所说的"缓存"主要指:

  • L3 本地缓存:应用服务器内存中的缓存(Caffeine、Guava Cache)
  • L4 分布式缓存:独立的缓存服务(Redis、Memcached)

浏览器缓存的角色定位

  • 前端/全栈范畴:浏览器缓存更多属于前端优化和HTTP协议层面
  • 后端间接控制:后端通过设置HTTP头(Cache-Control、ETag等)来影响浏览器缓存行为
  • 协作关系:后端提供缓存控制策略,前端负责执行

实际开发中的分工

  • 后端工程师:主要关注Redis、本地缓存的设计和实现
  • 前端工程师:主要关注浏览器缓存策略和CDN配置
  • 运维工程师:负责CDN和缓存基础设施的部署

总结:当后端开发讨论"缓存方案"时,通常指的是应用层缓存(本地缓存 + Redis),而不是浏览器缓存。

4.2 本地缓存 vs Redis缓存的本质区别

数据内容差异

本地缓存和Redis缓存的内容通常不完全相同

对比维度 本地缓存 Redis缓存
数据范围 热点数据、高频访问数据 全量业务数据、共享数据
数据粒度 精细化,只缓存最热的数据 相对粗粒度,缓存更多数据
数据一致性 强一致性(单机内) 最终一致性(分布式环境)
典型内容 配置信息、字典数据、用户基本信息 会话信息、业务对象、计算结果

具体示例

// 本地缓存:只缓存最热的1000个商品信息
LoadingCache<Long, Product> hotProductCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .build(id -> loadFromRedis(id));

// Redis缓存:缓存所有商品信息(可能数十万条)
redisTemplate.opsForValue().set("product:" + productId, product, 30, TimeUnit.MINUTES);

数据量差异

  • 本地缓存:受限于单机JVM内存,通常只缓存KB-MB级别的热点数据
  • Redis缓存:可扩展到GB-TB级别,存储大量业务数据

4.3 本地缓存的性能影响与优化

内存占用问题

是的,本地缓存过多确实会导致性能问题

主要风险
  1. JVM内存压力

    • 本地缓存占用堆内存,减少可用内存给业务逻辑
    • 可能触发频繁GC,影响应用性能
    • 极端情况下导致OutOfMemoryError
  2. CPU开销

    • 缓存淘汰算法消耗CPU资源
    • 缓存统计和监控增加额外开销
    • 并发访问时的锁竞争
性能影响量化
  • 内存占用:每个缓存条目通常占用几十到几百字节
  • CPU开销:现代缓存框架(如Caffeine)的CPU开销通常<1%
  • GC影响:合理配置下,对GC的影响可控

优化策略

1. 合理设置缓存容量
// 根据JVM内存和业务需求设置合理的缓存大小
LoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(10_000)        // 限制最大条目数
    .maximumWeight(100 * 1024 * 1024) // 或限制最大字节数
    .weigher((String key, Object value) -> calculateSize(value))
    .build();
2. 智能淘汰策略
// 使用Window TinyLFU算法(Caffeine默认)
// 在频率和新鲜度之间取得平衡
LoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterAccess(10, TimeUnit.MINUTES)  // 访问后过期
    .expireAfterWrite(30, TimeUnit.MINUTES)   // 写入后过期
    .build();
3. 监控和告警
// 监控缓存使用情况
CacheStats stats = cache.stats();
double hitRate = stats.hitRate();  // 命中率
long estimatedSize = cache.estimatedSize(); // 当前大小

// 设置告警阈值
if (estimatedSize > MAX_CACHE_SIZE * 0.8) {
    // 发送告警
}
4. 分层缓存策略
// L1: 本地缓存(超热点数据,1000条)
// L2: Redis缓存(热点数据,10万条)
// L3: 数据库(全量数据)

public Product getProduct(Long id) {
    // 先查本地缓存
    Product product = localCache.getIfPresent(id);
    if (product != null) {
        return product;
    }
    
    // 再查Redis
    product = redisCache.get("product:" + id);
    if (product != null) {
        localCache.put(id, product); // 回填本地缓存
        return product;
    }
    
    // 最后查数据库
    product = database.getProduct(id);
    if (product != null) {
        redisCache.set("product:" + id, product, 30, TimeUnit.MINUTES);
        localCache.put(id, product);
    }
    return product;
}
5. 内存优化技巧
  • 使用弱引用/软引用:对于非关键数据
  • 对象复用:避免创建不必要的对象
  • 序列化优化:选择内存友好的序列化方式
  • 定期清理:主动清理长时间未使用的缓存

容量规划建议

JVM内存分配原则
  • 堆内存总量:根据服务器物理内存确定
  • 缓存内存占比:建议不超过堆内存的20-30%
  • 预留空间:为业务逻辑和临时对象预留足够内存
示例配置
# 服务器:16GB内存
# JVM堆内存:8GB (-Xmx8g)
# 本地缓存最大占用:2GB (25% of heap)
# Redis缓存:独立部署,不受JVM限制

五、各级缓存实现详解

5.1 浏览器缓存 (L1)

5.1.1 强缓存

  • Expires: HTTP/1.0标准,绝对时间戳
  • Cache-Control: HTTP/1.1标准,相对时间(max-age)
Cache-Control: max-age=3600, public

5.1.2 协商缓存

  • Last-Modified/If-Modified-Since: 基于时间戳
  • ETag/If-None-Match: 基于内容哈希值(更精确)

5.2 CDN缓存 (L2)

5.2.1 CDN工作原理

  • 将内容分发到全球边缘节点
  • 用户就近访问最近的节点
  • 支持动态内容加速和静态内容缓存

5.2.2 CDN缓存策略

  • TTL设置: 根据内容更新频率设置合适的过期时间
  • 缓存刷新: 主动刷新缓存内容
  • 预热: 提前将热门内容加载到CDN节点

5.3 本地缓存 (L3)

5.3.1 常用本地缓存框架

  • Guava Cache: Google开源,功能丰富
  • Caffeine: Guava Cache的高性能替代品
  • Ehcache: 支持内存和磁盘两级存储

5.3.2 Caffeine缓存示例

// 创建缓存
LoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(10_000)           // 最大条目数
    .expireAfterWrite(10, TimeUnit.MINUTES)  // 写入后10分钟过期
    .refreshAfterWrite(5, TimeUnit.MINUTES)  // 写入5分钟后自动刷新
    .recordStats()                 // 开启统计
    .build(key -> loadFromRedisOrDB(key));   // 加载函数

// 使用缓存
Object value = cache.get("key");

5.3.3 本地缓存优势与劣势

  • 优势: 访问速度极快,无网络开销
  • 劣势: 内存有限,集群环境下数据不一致

5.4 分布式缓存 (L4)

5.4.1 Redis特性

  • 数据结构丰富: String、Hash、List、Set、Sorted Set等
  • 持久化支持: RDB快照和AOF日志
  • 高可用: 主从复制、哨兵模式、集群模式
  • 性能优异: 单机可支持10万+ QPS

5.4.2 Redis缓存策略

  • 缓存粒度: 对象缓存 vs 字段缓存
  • 序列化方式: JSON、Protobuf、Kryo等
  • 连接池配置: 合理设置最大连接数、超时时间

5.4.3 Redis Java客户端示例

// Spring Boot RedisTemplate使用
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 设置缓存
redisTemplate.opsForValue().set("user:1001", user, 30, TimeUnit.MINUTES);

// 获取缓存
User user = (User) redisTemplate.opsForValue().get("user:1001");

六、缓存问题解决方案

6.1 缓存穿透解决方案

6.1.1 布隆过滤器 (Bloom Filter)

  • 在缓存层前增加布隆过滤器
  • 快速判断数据是否存在
  • 存在误判可能,但不会漏判
// Guava BloomFilter示例
BloomFilter<String> bloomFilter = BloomFilter.create(
    Funnels.stringFunnel(Charset.defaultCharset()), 
    10000, // 预期插入元素数量
    0.01   // 误判率
);
bloomFilter.put("existing_key");
boolean mightContain = bloomFilter.mightContain("query_key");

6.1.2 空值缓存

  • 对查询结果为空的数据也进行缓存
  • 设置较短的过期时间(如1-5分钟)
  • 避免恶意攻击导致数据库压力

6.2 缓存击穿解决方案

6.2.1 互斥锁 (Mutex Lock)

  • 当缓存失效时,只有一个线程去加载数据
  • 其他线程等待或返回旧数据
public User getUser(Long id) {
    String key = "user:" + id;
    User user = redisTemplate.opsForValue().get(key);
    if (user == null) {
        // 获取分布式锁
        String lockKey = "lock:" + key;
        if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS)) {
            try {
                // 双重检查
                user = redisTemplate.opsForValue().get(key);
                if (user == null) {
                    user = loadFromDB(id);
                    redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
                }
            } finally {
                redisTemplate.delete(lockKey);
            }
        } else {
            // 等待一小段时间后重试
            Thread.sleep(50);
            return getUser(id);
        }
    }
    return user;
}

6.2.2 逻辑过期

  • 不设置物理过期时间
  • 在value中包含逻辑过期时间
  • 后台异步更新过期数据

6.3 缓存雪崩解决方案

6.3.1 过期时间随机化

  • 在基础过期时间上增加随机值
  • 避免大量缓存同时失效
// 设置30分钟基础过期时间,加上0-5分钟随机值
long expireTime = 30 * 60 + new Random().nextInt(5 * 60);
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);

6.3.2 永不过期 + 异步更新

  • 缓存永不过期
  • 后台定时任务或事件驱动更新缓存
  • 适用于数据变化不频繁的场景

6.3.3 限流降级

  • 在缓存失效期间对数据库访问进行限流
  • 提供降级方案(如返回默认值、简化数据)

七、缓存一致性保证

7.1 Cache-Aside Pattern (旁路缓存模式)

  • 读操作: 先读缓存,缓存未命中再读数据库,然后回填缓存
  • 写操作: 先更新数据库,再删除缓存
// 读操作
public User getUser(Long id) {
    User user = cache.get(id);
    if (user == null) {
        user = db.getUser(id);
        if (user != null) {
            cache.set(id, user);
        }
    }
    return user;
}

// 写操作
public void updateUser(User user) {
    db.updateUser(user);
    cache.delete(user.getId()); // 删除而不是更新缓存
}

7.2 Write-Through Pattern (写穿透模式)

  • 写操作同时更新缓存和数据库
  • 保证数据强一致性,但写性能较低

7.3 Write-Behind Pattern (写回模式)

  • 只更新缓存,异步批量更新数据库
  • 提高写性能,但存在数据丢失风险

7.4 双删策略

针对Cache-Aside模式可能出现的脏数据问题:

public void updateUser(User user) {
    // 第一次删除缓存
    cache.delete(user.getId());
    // 更新数据库
    db.updateUser(user);
    // 延迟一段时间后再次删除缓存
    Thread.sleep(100);
    cache.delete(user.getId());
}

八、高并发优化策略

8.1 读写分离

  • 主库负责写操作,从库负责读操作
  • 缓解主库压力,提高读取性能
  • 注意主从延迟问题

8.2 分库分表

  • 垂直分库: 按业务模块拆分
  • 水平分表: 按数据范围或哈希值拆分
  • 结合ShardingSphere等中间件实现

8.3 异步处理

  • 使用消息队列解耦耗时操作
  • 提高系统响应速度
  • 常用MQ: Kafka、RocketMQ、RabbitMQ

8.4 限流熔断

  • 限流: 控制请求速率,保护系统
  • 熔断: 故障时快速失败,避免雪崩
  • 降级: 返回简化结果或默认值

8.5 连接池优化

  • 合理配置数据库连接池参数
  • 监控连接池使用情况
  • 避免连接泄漏

九、监控与调优

9.1 关键监控指标

  • 缓存命中率: 监控各级缓存命中情况
  • 缓存响应时间: 缓存访问延迟
  • 内存使用率: 避免内存溢出
  • QPS/TPS: 系统处理能力
  • 错误率: 异常请求比例

9.2 Redis监控命令

# 查看基本信息
INFO

# 查看内存使用情况
INFO memory

# 查看键空间统计
INFO keyspace

# 查看慢查询
SLOWLOG GET 10

# 查看客户端连接
CLIENT LIST

9.3 JVM调优要点

  • 堆内存设置: -Xms和-Xmx设置为相同值
  • 新生代比例: -XX:NewRatio合理设置
  • GC算法选择: G1适合大堆内存场景
  • 监控工具: jstat、jmap、VisualVM

十、面试常见问题

10.1 基础概念类

  1. 什么是缓存穿透、击穿、雪崩?如何解决?
  2. 多级缓存的优势是什么?
  3. Redis为什么这么快?
  4. Redis的数据类型有哪些?各自适用场景?

10.2 实践应用类

  1. 如何保证缓存与数据库的一致性?
  2. 缓存过期策略有哪些?如何选择?
  3. Redis集群模式的工作原理?
  4. 如何设计一个高并发的商品详情页?

10.3 深度技术类

  1. Redis的持久化机制RDB和AOF的区别?
  2. Redis的内存淘汰策略有哪些?
  3. 如何实现分布式锁?Redisson的实现原理?
  4. Redis的Pipeline和事务有什么区别?

十一、最佳实践总结

11.1 缓存设计原则

  • 热点数据优先: 识别并缓存访问频率高的数据
  • 合理设置过期时间: 平衡内存使用和数据新鲜度
  • 分层设计: 多级缓存各司其职
  • 监控告警: 及时发现和解决问题

11.2 性能优化要点

  • 批量操作: 减少网络往返次数
  • 压缩存储: 对大对象进行压缩
  • 序列化优化: 选择高效的序列化方式
  • 连接复用: 合理使用连接池

11.3 容灾备份策略

  • 多副本: 保证缓存高可用
  • 持久化: 防止数据丢失
  • 降级方案: 缓存失效时的备用方案
  • 容量规划: 预留足够的内存和带宽

11.4 本地缓存使用建议

适用场景

  • 高频访问的热点数据
  • 相对静态的配置信息
  • 计算成本高的结果缓存
  • 用户会话相关的临时数据

避免场景

  • 大数据量缓存(超过JVM内存20%)
  • 频繁变更的数据
  • 需要强一致性的共享数据
  • 集群环境下需要全局一致的数据

配置建议

  • 大小限制maximumSize(1000-10000) 根据业务调整
  • 过期策略expireAfterAccess(5-10分钟) 避免内存泄漏
  • 监控开启recordStats() 便于性能分析
  • 加载函数:实现合理的加载和异常处理

参考资料:

  • Redis官方文档
  • 《Redis设计与实现》
  • 《高性能MySQL》
  • 《大型网站技术架构》
  • Alibaba Sentinel文档
  • Caffeine官方文档