SpringBoot 3 + Caffeine 企业级缓存最佳实践方案(二)

《SpringBoot 3 + Caffeine 企业级缓存最佳实践方案(一)》

《SpringBoot 3 + Caffeine 企业级缓存最佳实践方案(二)》

文章目录

  • 三、监控运维与实战案
    • [1. 缓存统计与监控](#1. 缓存统计与监控)
      • [1.1 缓存指标收集器](#1.1 缓存指标收集器)
      • [1.2 热点Key识别](#1.2 热点Key识别)
      • [1.3 Spring Boot Actuator集成](#1.3 Spring Boot Actuator集成)
      • [1.4 Prometheus监控集成](#1.4 Prometheus监控集成)
        • [1.4.1 Grafana Dashboard JSON](#1.4.1 Grafana Dashboard JSON)
    • [2. 缓存异常告警](#2. 缓存异常告警)
      • [2.1 告警规则定义](#2.1 告警规则定义)
    • [3. 缓存刷新机制](#3. 缓存刷新机制)
      • [3.1 批量刷新调度器](#3.1 批量刷新调度器)
    • [4. 实战案例一:用户信息缓存](#4. 实战案例一:用户信息缓存)
      • [4.1 用户实体](#4.1 用户实体)
      • [4.2 用户Repository](#4.2 用户Repository)
      • [4.3 用户服务(带缓存)](#4.3 用户服务(带缓存))
      • [4.4 用户控制器](#4.4 用户控制器)
    • [5. 实战案例二:接口限流缓存](#5. 实战案例二:接口限流缓存)
      • [5.1 限流注解](#5.1 限流注解)
      • [5.2 限流切面](#5.2 限流切面)
      • [5.3 限流使用示例](#5.3 限流使用示例)
    • [6. 实战案例三:热点商品缓存](#6. 实战案例三:热点商品缓存)
      • [6.1 商品实体](#6.1 商品实体)
      • [6.2 商品服务](#6.2 商品服务)
    • [7. 单元测试示例](#7. 单元测试示例)
      • [7.1 缓存服务测试](#7.1 缓存服务测试)
    • 小结
  • 四、配置文件与性能优化
    • [1. 配置文件详解](#1. 配置文件详解)
      • [1.1 application.yml 主配置](#1.1 application.yml 主配置)
      • [1.2 开发环境配置](#1.2 开发环境配置)
      • [1.3 测试环境配置](#1.3 测试环境配置)
      • [1.4 生产环境配置](#1.4 生产环境配置)
    • [2. 性能优化策略](#2. 性能优化策略)
      • [2.1 缓存预热](#2.1 缓存预热)
        • [2.1.1 预热服务实现](#2.1.1 预热服务实现)
      • [2.2 异步加载优化](#2.2 异步加载优化)
      • [2.3 批量操作优化](#2.3 批量操作优化)
      • [2.4 内存占用控制](#2.4 内存占用控制)
      • [2.5 GC友好配置](#2.5 GC友好配置)
    • [3. 最佳实践总结](#3. 最佳实践总结)
      • [3.1 缓存设计原则](#3.1 缓存设计原则)
      • [3.2 性能优化检查清单](#3.2 性能优化检查清单)
    • [4. 常见问题与解决方案](#4. 常见问题与解决方案)
      • [4.1 问题:缓存命中率低](#4.1 问题:缓存命中率低)
      • [4.2 问题:内存占用过高](#4.2 问题:内存占用过高)
      • [4.3 问题:缓存击穿(热点Key失效)](#4.3 问题:缓存击穿(热点Key失效))
      • [4.4 问题:缓存雪崩(大量缓存同时失效)](#4.4 问题:缓存雪崩(大量缓存同时失效))
    • [5. 性能基准测试](#5. 性能基准测试)
      • [5.1 测试环境](#5.1 测试环境)
      • [5.2 测试结果](#5.2 测试结果)
      • [5.3 性能对比](#5.3 性能对比)
    • 小结

三、监控运维与实战案

1. 缓存统计与监控

1.1 缓存指标收集器

java 复制代码
package com.enterprise.cache.metrics;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 缓存指标收集器
 * 收集并暴露缓存性能指标
 * 
 * 监控指标:
 * 1. 缓存大小
 * 2. 命中率/未命中率
 * 3. 加载时间
 * 4. 驱逐次数
 * 5. 缓存健康度评分
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    private final Map<String, Cache<String, Object>> cacheMap;
    
    /**
     * 缓存命中率历史记录
     */
    private final Map<String, Double> hitRateHistory = new ConcurrentHashMap<>();
    
    /**
     * 缓存大小历史记录
     */
    private final Map<String, Long> cacheSizeHistory = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void init() {
        registerCacheMetrics();
        log.info("Cache metrics collector initialized");
    }
    
    /**
     * 注册缓存指标到Prometheus
     */
    private void registerCacheMetrics() {
        cacheMap.forEach((cacheName, cache) -> {
            String nameTag = "cache_name";
            
            // 缓存大小
            Gauge.builder("cache.size", cache, c -> c.estimatedSize())
                    .tag(nameTag, cacheName)
                    .description("Current cache size")
                    .register(meterRegistry);
            
            // 缓存命中率
            Gauge.builder("cache.hit.rate", cache, c -> {
                CacheStats stats = c.stats();
                double hitRate = stats.hitRate();
                hitRateHistory.put(cacheName, hitRate);
                return hitRate;
            })
                    .tag(nameTag, cacheName)
                    .description("Cache hit rate")
                    .register(meterRegistry);
            
            // 缓存未命中率
            Gauge.builder("cache.miss.rate", cache, c -> c.stats().missRate())
                    .tag(nameTag, cacheName)
                    .description("Cache miss rate")
                    .register(meterRegistry);
            
            // 缓存命中次数
            Gauge.builder("cache.hit.count", cache, c -> c.stats().hitCount())
                    .tag(nameTag, cacheName)
                    .description("Total cache hits")
                    .register(meterRegistry);
            
            // 缓存未命中次数
            Gauge.builder("cache.miss.count", cache, c -> c.stats().missCount())
                    .tag(nameTag, cacheName)
                    .description("Total cache misses")
                    .register(meterRegistry);
            
            // 缓存驱逐次数
            Gauge.builder("cache.eviction.count", cache, c -> c.stats().evictionCount())
                    .tag(nameTag, cacheName)
                    .description("Total cache evictions")
                    .register(meterRegistry);
            
            // 平均加载时间(纳秒转毫秒)
            Gauge.builder("cache.load.average.time", cache, 
                    c -> c.stats().averageLoadPenalty() / 1_000_000.0)
                    .tag(nameTag, cacheName)
                    .description("Average cache load time in milliseconds")
                    .baseUnit("milliseconds")
                    .register(meterRegistry);
            
            log.info("Registered metrics for cache: {}", cacheName);
        });
    }
    
    /**
     * 定期收集缓存统计信息(每分钟)
     */
    @Scheduled(fixedDelay = 60000)
    public void collectCacheStats() {
        cacheMap.forEach((cacheName, cache) -> {
            CacheStats stats = cache.stats();
            long size = cache.estimatedSize();
            
            // 记录缓存大小
            cacheSizeHistory.put(cacheName, size);
            
            // 记录命中率
            double hitRate = stats.hitRate();
            hitRateHistory.put(cacheName, hitRate);
            
            // 检查命中率告警
            if (hitRate < 0.7 && stats.requestCount() > 100) {
                log.warn("⚠️ Low cache hit rate detected: cacheName={}, hitRate={:.2f}%",
                        cacheName, hitRate * 100);
            }
            
            // 检查驱逐率
            long evictionCount = stats.evictionCount();
            long requestCount = stats.requestCount();
            if (requestCount > 0) {
                double evictionRate = (double) evictionCount / requestCount;
                if (evictionRate > 0.3) {
                    log.warn("⚠️ High cache eviction rate: cacheName={}, evictionRate={:.2f}%",
                            cacheName, evictionRate * 100);
                }
            }
        });
    }
    
    /**
     * 获取缓存健康度评分(0-100)
     * 
     * 评分规则:
     * - 命中率权重:50%
     * - 加载成功率权重:30%
     * - 驱逐率权重:20%
     */
    public int getCacheHealthScore(String cacheName) {
        Cache<String, Object> cache = cacheMap.get(cacheName);
        if (cache == null) {
            return 0;
        }
        
        CacheStats stats = cache.stats();
        
        // 命中率得分(0-50)
        double hitRateScore = stats.hitRate() * 50;
        
        // 加载成功率得分(0-30)
        long totalLoads = stats.loadSuccessCount() + stats.loadFailureCount();
        double loadSuccessRate = totalLoads > 0
                ? (double) stats.loadSuccessCount() / totalLoads
                : 1.0;
        double loadSuccessScore = loadSuccessRate * 30;
        
        // 驱逐率得分(0-20,驱逐率越低越好)
        long requestCount = stats.requestCount();
        double evictionRate = requestCount > 0
                ? (double) stats.evictionCount() / requestCount
                : 0.0;
        double evictionScore = (1 - Math.min(evictionRate, 1.0)) * 20;
        
        return (int) (hitRateScore + loadSuccessScore + evictionScore);
    }
    
    /**
     * 生成缓存统计报告
     */
    public String generateStatsReport() {
        StringBuilder report = new StringBuilder();
        report.append("\n").append("=".repeat(80)).append("\n");
        report.append("Cache Statistics Report\n");
        report.append("=".repeat(80)).append("\n");
        
        cacheMap.forEach((cacheName, cache) -> {
            CacheStats stats = cache.stats();
            int healthScore = getCacheHealthScore(cacheName);
            
            report.append("\nCache: ").append(cacheName).append("\n");
            report.append("  Size: ").append(cache.estimatedSize()).append("\n");
            report.append("  Hit Rate: ").append(String.format("%.2f%%", stats.hitRate() * 100)).append("\n");
            report.append("  Miss Rate: ").append(String.format("%.2f%%", stats.missRate() * 100)).append("\n");
            report.append("  Total Requests: ").append(stats.requestCount()).append("\n");
            report.append("  Eviction Count: ").append(stats.evictionCount()).append("\n");
            report.append("  Avg Load Time: ").append(String.format("%.2fms", 
                    stats.averageLoadPenalty() / 1_000_000.0)).append("\n");
            report.append("  Health Score: ").append(healthScore).append("/100\n");
        });
        
        report.append("=".repeat(80)).append("\n");
        
        return report.toString();
    }
}

1.2 热点Key识别

java 复制代码
package com.enterprise.cache.metrics;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * 热点Key识别器
 * 统计访问频率,识别热点数据
 * 
 * 应用场景:
 * 1. 识别热点Key进行针对性优化
 * 2. 预热策略参考
 * 3. 缓存容量规划
 * 4. 异常流量检测
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Component
public class HotKeyDetector {
    
    /**
     * Key访问计数器
     */
    private final Map<String, AtomicLong> accessCountMap = new ConcurrentHashMap<>();
    
    /**
     * 热点Key阈值(每分钟访问次数)
     */
    private static final long HOT_KEY_THRESHOLD = 100;
    
    /**
     * 记录Key访问
     */
    public void recordAccess(String cacheName, String key) {
        String fullKey = cacheName + ":" + key;
        accessCountMap.computeIfAbsent(fullKey, k -> new AtomicLong(0))
                .incrementAndGet();
    }
    
    /**
     * 获取热点Key列表
     * 
     * @param topN 返回前N个热点Key
     * @return 热点Key列表
     */
    public List<HotKeyInfo> getHotKeys(int topN) {
        return accessCountMap.entrySet().stream()
                .filter(entry -> entry.getValue().get() >= HOT_KEY_THRESHOLD)
                .map(entry -> new HotKeyInfo(entry.getKey(), entry.getValue().get()))
                .sorted(Comparator.comparing(HotKeyInfo::getAccessCount).reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }
    
    /**
     * 获取指定缓存的热点Key
     */
    public List<HotKeyInfo> getHotKeysByCache(String cacheName, int topN) {
        String prefix = cacheName + ":";
        
        return accessCountMap.entrySet().stream()
                .filter(entry -> entry.getKey().startsWith(prefix))
                .map(entry -> new HotKeyInfo(
                        entry.getKey().substring(prefix.length()),
                        entry.getValue().get()
                ))
                .sorted(Comparator.comparing(HotKeyInfo::getAccessCount).reversed())
                .limit(topN)
                .collect(Collectors.toList());
    }
    
    /**
     * 定期重置计数器(每小时)
     */
    @Scheduled(cron = "0 0 * * * ?")
    public void resetCounters() {
        // 输出热点Key统计
        List<HotKeyInfo> hotKeys = getHotKeys(20);
        if (!hotKeys.isEmpty()) {
            log.info("🔥 Hot keys in last hour (top 20):");
            hotKeys.forEach(info ->
                log.info("  Key: {}, Access count: {}", info.getKey(), info.getAccessCount())
            );
        }
        
        // 清空计数器
        accessCountMap.clear();
        log.info("Hot key counters reset");
    }
    
    /**
     * 热点Key信息
     */
    @Data
    @AllArgsConstructor
    public static class HotKeyInfo {
        private String key;
        private long accessCount;
    }
}

1.3 Spring Boot Actuator集成

java 复制代码
package com.enterprise.cache.actuator;

import com.enterprise.cache.metrics.CacheMetricsCollector;
import com.enterprise.cache.metrics.HotKeyDetector;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 缓存监控端点
 * 暴露缓存详细信息给Actuator
 * 
 * 访问路径:
 * - GET /actuator/cacheMonitor - 获取所有缓存概览
 * - GET /actuator/cacheMonitor/{cacheName} - 获取指定缓存详情
 * - GET /actuator/cacheMonitor/hotkeys - 获取热点Key排行
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Component
@Endpoint(id = "cacheMonitor")
@RequiredArgsConstructor
public class CacheMonitorEndpoint {
    
    private final Map<String, Cache<String, Object>> cacheMap;
    private final CacheMetricsCollector metricsCollector;
    private final HotKeyDetector hotKeyDetector;
    
    /**
     * 获取所有缓存概览
     * 
     * GET /actuator/cacheMonitor
     */
    @ReadOperation
    public Map<String, Object> getCachesOverview() {
        Map<String, Object> overview = new HashMap<>();
        
        cacheMap.forEach((name, cache) -> {
            Map<String, Object> cacheInfo = new HashMap<>();
            cacheInfo.put("size", cache.estimatedSize());
            cacheInfo.put("stats", cache.stats());
            cacheInfo.put("healthScore", metricsCollector.getCacheHealthScore(name));
            
            overview.put(name, cacheInfo);
        });
        
        return overview;
    }
    
    /**
     * 获取指定缓存详情
     * 
     * GET /actuator/cacheMonitor/{cacheName}
     */
    @ReadOperation
    public Map<String, Object> getCacheDetails(@Selector String cacheName) {
        Cache<String, Object> cache = cacheMap.get(cacheName);
        if (cache == null) {
            Map<String, Object> error = new HashMap<>();
            error.put("error", "Cache not found: " + cacheName);
            return error;
        }
        
        Map<String, Object> details = new HashMap<>();
        details.put("name", cacheName);
        details.put("size", cache.estimatedSize());
        details.put("stats", cache.stats());
        details.put("healthScore", metricsCollector.getCacheHealthScore(cacheName));
        details.put("hotKeys", hotKeyDetector.getHotKeysByCache(cacheName, 10));
        
        return details;
    }
    
    /**
     * 获取热点Key排行
     * 
     * GET /actuator/cacheMonitor/hotkeys
     */
    @ReadOperation
    public Map<String, Object> getHotKeys() {
        Map<String, Object> result = new HashMap<>();
        
        List<HotKeyDetector.HotKeyInfo> hotKeys = hotKeyDetector.getHotKeys(50);
        result.put("totalHotKeys", hotKeys.size());
        result.put("hotKeys", hotKeys);
        
        return result;
    }
}

1.4 Prometheus监控集成

1.4.1 Grafana Dashboard JSON
json 复制代码
{
  "dashboard": {
    "title": "Caffeine Cache Monitoring",
    "tags": ["cache", "performance"],
    "timezone": "browser",
    "panels": [
      {
        "id": 1,
        "title": "Cache Hit Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "cache_hit_rate{job=\"spring-boot-app\"}",
            "legendFormat": "{{cache_name}}"
          }
        ],
        "yaxes": [
          {
            "format": "percentunit",
            "label": "Hit Rate",
            "max": 1,
            "min": 0
          }
        ]
      },
      {
        "id": 2,
        "title": "Cache Size",
        "type": "graph",
        "targets": [
          {
            "expr": "cache_size{job=\"spring-boot-app\"}",
            "legendFormat": "{{cache_name}}"
          }
        ],
        "yaxes": [
          {
            "format": "short",
            "label": "Entries"
          }
        ]
      },
      {
        "id": 3,
        "title": "Cache Eviction Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(cache_eviction_count{job=\"spring-boot-app\"}[5m])",
            "legendFormat": "{{cache_name}}"
          }
        ],
        "yaxes": [
          {
            "format": "ops",
            "label": "Evictions/sec"
          }
        ]
      },
      {
        "id": 4,
        "title": "Average Load Time",
        "type": "graph",
        "targets": [
          {
            "expr": "cache_load_average_time{job=\"spring-boot-app\"}",
            "legendFormat": "{{cache_name}}"
          }
        ],
        "yaxes": [
          {
            "format": "ms",
            "label": "Load Time"
          }
        ]
      },
      {
        "id": 5,
        "title": "Cache Operations",
        "type": "stat",
        "targets": [
          {
            "expr": "sum(rate(cache_hit_count{job=\"spring-boot-app\"}[5m]))",
            "legendFormat": "Hits/sec"
          },
          {
            "expr": "sum(rate(cache_miss_count{job=\"spring-boot-app\"}[5m]))",
            "legendFormat": "Misses/sec"
          }
        ]
      }
    ]
  }
}

2. 缓存异常告警

2.1 告警规则定义

java 复制代码
package com.enterprise.cache.alert;

import com.enterprise.cache.metrics.CacheMetricsCollector;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 缓存告警服务
 * 
 * 告警规则:
 * 1. 命中率低于70%
 * 2. 驱逐率高于30%
 * 3. 平均加载时间超过100ms
 * 4. 健康度评分低于60
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheAlertService {
    
    private final Map<String, Cache<String, Object>> cacheMap;
    private final CacheMetricsCollector metricsCollector;
    
    /**
     * 定期检查缓存健康状态(每5分钟)
     */
    @Scheduled(fixedDelay = 300000)
    public void checkCacheHealth() {
        cacheMap.forEach((cacheName, cache) -> {
            CacheStats stats = cache.stats();
            
            // 检查命中率
            checkHitRate(cacheName, stats);
            
            // 检查驱逐率
            checkEvictionRate(cacheName, stats);
            
            // 检查加载时间
            checkLoadTime(cacheName, stats);
            
            // 检查健康度评分
            checkHealthScore(cacheName);
        });
    }
    
    /**
     * 检查命中率
     */
    private void checkHitRate(String cacheName, CacheStats stats) {
        if (stats.requestCount() < 100) {
            return; // 请求量太少,不检查
        }
        
        double hitRate = stats.hitRate();
        if (hitRate < 0.7) {
            sendAlert(
                "LOW_HIT_RATE",
                String.format("Cache %s has low hit rate: %.2f%%", 
                        cacheName, hitRate * 100),
                "WARN"
            );
        }
    }
    
    /**
     * 检查驱逐率
     */
    private void checkEvictionRate(String cacheName, CacheStats stats) {
        long requestCount = stats.requestCount();
        if (requestCount == 0) {
            return;
        }
        
        double evictionRate = (double) stats.evictionCount() / requestCount;
        if (evictionRate > 0.3) {
            sendAlert(
                "HIGH_EVICTION_RATE",
                String.format("Cache %s has high eviction rate: %.2f%%",
                        cacheName, evictionRate * 100),
                "WARN"
            );
        }
    }
    
    /**
     * 检查加载时间
     */
    private void checkLoadTime(String cacheName, CacheStats stats) {
        double avgLoadTimeMs = stats.averageLoadPenalty() / 1_000_000.0;
        if (avgLoadTimeMs > 100) {
            sendAlert(
                "SLOW_LOAD_TIME",
                String.format("Cache %s has slow load time: %.2fms",
                        cacheName, avgLoadTimeMs),
                "WARN"
            );
        }
    }
    
    /**
     * 检查健康度评分
     */
    private void checkHealthScore(String cacheName) {
        int healthScore = metricsCollector.getCacheHealthScore(cacheName);
        if (healthScore < 60) {
            sendAlert(
                "LOW_HEALTH_SCORE",
                String.format("Cache %s has low health score: %d/100",
                        cacheName, healthScore),
                "ERROR"
            );
        }
    }
    
    /**
     * 发送告警
     * 
     * TODO: 集成实际的告警系统
     * - 邮件告警
     * - 钉钉/企业微信机器人
     * - 短信告警
     * - Prometheus AlertManager
     */
    private void sendAlert(String alertType, String message, String level) {
        log.warn("🚨 CACHE ALERT [{}] {}: {}", level, alertType, message);
        
        // TODO: 实现实际的告警发送逻辑
        // emailService.sendAlert(alertType, message);
        // dingTalkService.sendAlert(message);
    }
}

3. 缓存刷新机制

3.1 批量刷新调度器

java 复制代码
package com.enterprise.cache.scheduler;

import com.enterprise.cache.service.CacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * 缓存批量刷新调度器
 * 定时批量刷新热点缓存
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheBatchRefreshScheduler {
    
    private final CacheService cacheService;
    
    /**
     * 每10分钟批量刷新热点缓存
     */
    @Scheduled(fixedDelay = 600000, initialDelay = 60000)
    public void refreshHotCache() {
        log.info("Starting batch refresh of hot cache...");
        
        try {
            // 获取需要刷新的Key列表
            List<String> hotKeys = getHotCacheKeys();
            
            // 异步批量刷新
            List<CompletableFuture<Void>> futures = hotKeys.stream()
                    .map(key -> CompletableFuture.runAsync(() -> refreshSingleCache(key)))
                    .toList();
            
            // 等待所有刷新完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            
            log.info("Batch refresh completed: count={}", hotKeys.size());
        } catch (Exception e) {
            log.error("Batch refresh failed", e);
        }
    }
    
    /**
     * 获取热点缓存Key列表
     */
    private List<String> getHotCacheKeys() {
        // TODO: 从统计数据中获取热点Key
        return List.of();
    }
    
    /**
     * 刷新单个缓存
     */
    private void refreshSingleCache(String key) {
        try {
            log.debug("Refreshing cache: key={}", key);
            // TODO: 实现具体刷新逻辑
        } catch (Exception e) {
            log.error("Failed to refresh cache: key={}", key, e);
        }
    }
}

4. 实战案例一:用户信息缓存

4.1 用户实体

java 复制代码
package com.enterprise.cache.example.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    /**
     * 用户ID
     */
    private Long id;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 邮箱
     */
    private String email;
    
    /**
     * 昵称
     */
    private String nickname;
    
    /**
     * 头像URL
     */
    private String avatar;
    
    /**
     * 状态:0-禁用,1-正常
     */
    private Integer status;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
    
    /**
     * 最后登录时间
     */
    private LocalDateTime lastLoginTime;
}

4.2 用户Repository

java 复制代码
package com.enterprise.cache.example.repository;

import com.enterprise.cache.example.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用户数据访问层(模拟数据库)
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Repository
public class UserRepository {
    
    /**
     * 模拟数据库
     */
    private static final Map<Long, User> USER_DB = new HashMap<>();
    
    static {
        // 初始化1000条测试数据
        for (long i = 1; i <= 1000; i++) {
            USER_DB.put(i, User.builder()
                    .id(i)
                    .username("user" + i)
                    .email("user" + i + "@example.com")
                    .nickname("User " + i)
                    .avatar("https://avatar.example.com/" + i + ".jpg")
                    .status(1)
                    .createTime(LocalDateTime.now().minusDays(i))
                    .lastLoginTime(LocalDateTime.now().minusHours(i))
                    .build());
        }
    }
    
    /**
     * 根据ID查询用户
     */
    public User findById(Long id) {
        simulateDbDelay(50); // 模拟50ms查询时间
        log.debug("Query user from database: id={}", id);
        return USER_DB.get(id);
    }
    
    /**
     * 根据用户名查询用户
     */
    public User findByUsername(String username) {
        simulateDbDelay(50);
        log.debug("Query user from database: username={}", username);
        return USER_DB.values().stream()
                .filter(user -> user.getUsername().equals(username))
                .findFirst()
                .orElse(null);
    }
    
    /**
     * 批量查询用户
     */
    public Map<Long, User> findByIds(Collection<Long> ids) {
        simulateDbDelay(100); // 批量查询稍慢
        log.debug("Batch query users from database: count={}", ids.size());
        return ids.stream()
                .filter(USER_DB::containsKey)
                .collect(Collectors.toMap(id -> id, USER_DB::get));
    }
    
    /**
     * 更新用户信息
     */
    public void update(User user) {
        simulateDbDelay(30);
        USER_DB.put(user.getId(), user);
        log.debug("Updated user in database: id={}", user.getId());
    }
    
    /**
     * 模拟数据库查询延迟
     */
    private void simulateDbDelay(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

4.3 用户服务(带缓存)

java 复制代码
package com.enterprise.cache.example.service;

import com.enterprise.cache.annotation.EnhancedCacheable;
import com.enterprise.cache.annotation.EnhancedCacheEvict;
import com.enterprise.cache.annotation.EnhancedCachePut;
import com.enterprise.cache.enums.CacheType;
import com.enterprise.cache.example.entity.User;
import com.enterprise.cache.example.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 用户服务
 * 演示用户信息缓存的最佳实践
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    /**
     * 根据ID查询用户(带缓存)
     * 
     * 缓存策略:
     * - 缓存类型:USER_INFO
     * - 防穿透:开启(空值缓存60秒)
     * - 防击穿:开启(分布式锁)
     * - 防雪崩:开启(随机过期时间)
     */
    @EnhancedCacheable(
            cacheType = CacheType.USER_INFO,
            key = "#userId",
            preventPenetration = true,
            preventBreakdown = true,
            randomExpire = true,
            randomExpireRange = 300
    )
    public User getUserById(Long userId) {
        log.info("Loading user from database: userId={}", userId);
        return userRepository.findById(userId);
    }
    
    /**
     * 根据用户名查询用户
     */
    @EnhancedCacheable(
            cacheType = CacheType.USER_INFO,
            key = "'username:' + #username",
            preventPenetration = true,
            preventBreakdown = true
    )
    public User getUserByUsername(String username) {
        log.info("Loading user from database: username={}", username);
        return userRepository.findByUsername(username);
    }
    
    /**
     * 更新用户信息
     * 更新后同步更新缓存
     */
    @EnhancedCachePut(
            cacheType = CacheType.USER_INFO,
            key = "#user.id",
            syncDistributed = true
    )
    public User updateUser(User user) {
        log.info("Updating user: userId={}", user.getId());
        userRepository.update(user);
        return user;
    }
    
    /**
     * 删除用户缓存
     */
    @EnhancedCacheEvict(
            cacheType = CacheType.USER_INFO,
            key = "#userId",
            syncDistributed = true
    )
    public void evictUserCache(Long userId) {
        log.info("Evicting user cache: userId={}", userId);
    }
    
    /**
     * 获取用户(带降级策略)
     */
    @EnhancedCacheable(
            cacheType = CacheType.USER_INFO,
            key = "#userId",
            enableFallback = true,
            fallbackMethod = "getUserFallback"
    )
    public User getUserWithFallback(Long userId) {
        return userRepository.findById(userId);
    }
    
    /**
     * 降级方法:返回默认用户
     */
    public User getUserFallback(Long userId) {
        log.warn("Using fallback for user query: userId={}", userId);
        return User.builder()
                .id(userId)
                .username("guest")
                .nickname("Guest User")
                .build();
    }
}

4.4 用户控制器

java 复制代码
package com.enterprise.cache.example.controller;

import com.enterprise.cache.example.entity.User;
import com.enterprise.cache.example.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * 用户控制器
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    /**
     * 根据ID查询用户
     * 
     * GET /api/users/1
     */
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    /**
     * 根据用户名查询用户
     * 
     * GET /api/users/username/john
     */
    @GetMapping("/username/{username}")
    public User getUserByUsername(@PathVariable String username) {
        return userService.getUserByUsername(username);
    }
    
    /**
     * 更新用户
     * 
     * PUT /api/users
     */
    @PutMapping
    public User updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }
    
    /**
     * 清除用户缓存
     * 
     * DELETE /api/users/cache/1
     */
    @DeleteMapping("/cache/{id}")
    public void evictCache(@PathVariable Long id) {
        userService.evictUserCache(id);
    }
}

5. 实战案例二:接口限流缓存

5.1 限流注解

java 复制代码
package com.enterprise.cache.example.ratelimit;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 接口限流注解
 * 
 * 使用Caffeine缓存实现简单的限流功能
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    
    /**
     * 限流Key(支持SpEL表达式)
     */
    String key() default "";
    
    /**
     * 时间窗口内最大请求数
     */
    int maxRequests() default 100;
    
    /**
     * 时间窗口大小
     */
    long window() default 1;
    
    /**
     * 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MINUTES;
    
    /**
     * 限流类型
     */
    LimitType limitType() default LimitType.IP;
    
    /**
     * 限流消息
     */
    String message() default "请求过于频繁,请稍后再试";
    
    enum LimitType {
        /**
         * 基于IP限流
         */
        IP,
        
        /**
         * 基于用户限流
         */
        USER,
        
        /**
         * 基于自定义Key限流
         */
        CUSTOM
    }
}

5.2 限流切面

java 复制代码
package com.enterprise.cache.example.ratelimit;

import com.enterprise.cache.enums.CacheType;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 限流切面
 * 基于Caffeine实现接口限流
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
    
    private final Map<String, Cache<String, Object>> cacheMap;
    
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        
        // 获取限流Key
        String limitKey = buildLimitKey(joinPoint, rateLimit);
        
        // 获取限流缓存
        Cache<String, Object> cache = cacheMap.get(CacheType.RATE_LIMIT.getCacheName());
        
        // 获取当前计数
        AtomicInteger counter = (AtomicInteger) cache.get(limitKey, 
                k -> new AtomicInteger(0));
        
        // 增加计数
        int currentCount = counter.incrementAndGet();
        
        // 检查是否超过限流阈值
        if (currentCount > rateLimit.maxRequests()) {
            log.warn("⚠️ Rate limit exceeded: key={}, count={}, limit={}",
                    limitKey, currentCount, rateLimit.maxRequests());
            throw new RateLimitException(rateLimit.message());
        }
        
        log.debug("Rate limit check passed: key={}, count={}/{}",
                limitKey, currentCount, rateLimit.maxRequests());
        
        // 执行目标方法
        return joinPoint.proceed();
    }
    
    /**
     * 构建限流Key
     */
    private String buildLimitKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        
        String keyPart;
        switch (rateLimit.limitType()) {
            case IP:
                keyPart = getClientIp();
                break;
            case USER:
                keyPart = getCurrentUserId();
                break;
            case CUSTOM:
                keyPart = rateLimit.key();
                break;
            default:
                keyPart = "default";
        }
        
        return String.format("rateLimit:%s:%s", methodName, keyPart);
    }
    
    /**
     * 获取客户端IP
     */
    private String getClientIp() {
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        
        if (attributes == null) {
            return "unknown";
        }
        
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getHeader("X-Forwarded-For");
        
        if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
            int index = ip.indexOf(',');
            if (index != -1) {
                return ip.substring(0, index);
            }
            return ip;
        }
        
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
            return ip;
        }
        
        return request.getRemoteAddr();
    }
    
    /**
     * 获取当前用户ID
     */
    private String getCurrentUserId() {
        // TODO: 从SecurityContext或Session获取用户ID
        return "user123";
    }
    
    /**
     * 限流异常
     */
    public static class RateLimitException extends RuntimeException {
        public RateLimitException(String message) {
            super(message);
        }
    }
}

5.3 限流使用示例

java 复制代码
package com.enterprise.cache.example.controller;

import com.enterprise.cache.example.ratelimit.RateLimit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

/**
 * API控制器(限流示例)
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@RestController
@RequestMapping("/api")
public class ApiController {
    
    /**
     * 搜索接口
     * 基于IP限流:每分钟最多10次请求
     */
    @GetMapping("/search")
    @RateLimit(
            limitType = RateLimit.LimitType.IP,
            maxRequests = 10,
            window = 1,
            timeUnit = TimeUnit.MINUTES,
            message = "搜索过于频繁,请稍后再试"
    )
    public String search(@RequestParam String keyword) {
        log.info("Searching: keyword={}", keyword);
        return "Search results for: " + keyword;
    }
    
    /**
     * 提交接口
     * 基于用户限流:每小时最多100次请求
     */
    @PostMapping("/submit")
    @RateLimit(
            limitType = RateLimit.LimitType.USER,
            maxRequests = 100,
            window = 1,
            timeUnit = TimeUnit.HOURS,
            message = "提交过于频繁,请稍后再试"
    )
    public String submit(@RequestBody String data) {
        log.info("Submitting data: {}", data);
        return "Data submitted successfully";
    }
    
    /**
     * 导出接口
     * 自定义Key限流:每秒最多5次请求
     */
    @GetMapping("/export")
    @RateLimit(
            limitType = RateLimit.LimitType.CUSTOM,
            key = "'export'",
            maxRequests = 5,
            window = 1,
            timeUnit = TimeUnit.SECONDS,
            message = "导出请求过多,请稍后再试"
    )
    public String export() {
        log.info("Exporting data");
        return "Export started";
    }
}

6. 实战案例三:热点商品缓存

6.1 商品实体

java 复制代码
package com.enterprise.cache.example.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 商品实体
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Integer stock;
    private Integer sales;
    private Integer status;
    private String imageUrl;
}

6.2 商品服务

java 复制代码
package com.enterprise.cache.example.service;

import com.enterprise.cache.annotation.EnhancedCacheable;
import com.enterprise.cache.annotation.EnhancedCacheEvict;
import com.enterprise.cache.enums.CacheType;
import com.enterprise.cache.example.entity.Product;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 商品服务
 * 演示热点商品缓存策略
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ProductService {
    
    // 模拟商品数据库
    private static final Map<Long, Product> PRODUCT_DB = new HashMap<>();
    
    static {
        // 初始化热销商品
        for (long i = 1; i <= 100; i++) {
            PRODUCT_DB.put(i, Product.builder()
                    .id(i)
                    .name("热销商品 " + i)
                    .description("这是一个热销商品")
                    .price(new BigDecimal("99.99"))
                    .stock(1000)
                    .sales((int) (Math.random() * 10000))
                    .status(1)
                    .imageUrl("https://img.example.com/" + i + ".jpg")
                    .build());
        }
    }
    
    /**
     * 查询商品详情
     * 热点商品使用专门的缓存策略
     */
    @EnhancedCacheable(
            cacheType = CacheType.PRODUCT_INFO,
            key = "#productId",
            preventPenetration = true,
            preventBreakdown = true,
            useBloomFilter = true,
            bloomFilterName = "productBloomFilter",
            randomExpire = true,
            expire = 300,
            randomExpireRange = 60
    )
    public Product getProductById(Long productId) {
        log.info("Loading product from database: productId={}", productId);
        simulateDbQuery();
        return PRODUCT_DB.get(productId);
    }
    
    /**
     * 查询热销商品列表
     * 短TTL + 主动刷新
     */
    @EnhancedCacheable(
            cacheName = "hotProducts",
            key = "'top:' + #limit",
            expire = 60,  // 1分钟过期
            randomExpire = false
    )
    public List<Product> getHotProducts(int limit) {
        log.info("Loading hot products from database: limit={}", limit);
        simulateDbQuery();
        
        return PRODUCT_DB.values().stream()
                .sorted(Comparator.comparing(Product::getSales).reversed())
                .limit(limit)
                .collect(Collectors.toList());
    }
    
    /**
     * 更新商品库存
     * 清除缓存确保数据一致性
     */
    @EnhancedCacheEvict(
            cacheType = CacheType.PRODUCT_INFO,
            key = "#productId",
            syncDistributed = true
    )
    public void updateStock(Long productId, Integer newStock) {
        log.info("Updating product stock: productId={}, newStock={}", 
                productId, newStock);
        
        Product product = PRODUCT_DB.get(productId);
        if (product != null) {
            product.setStock(newStock);
        }
    }
    
    /**
     * 商品秒杀场景
     * 使用永不过期策略 + 异步更新
     */
    public boolean seckillProduct(Long productId, Integer quantity) {
        // 1. 从缓存获取商品信息
        Product product = getProductById(productId);
        
        if (product == null || product.getStock() < quantity) {
            return false;
        }
        
        // 2. 扣减库存(实际应使用Redis原子操作)
        synchronized (this) {
            if (product.getStock() >= quantity) {
                product.setStock(product.getStock() - quantity);
                product.setSales(product.getSales() + quantity);
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 模拟数据库查询延迟
     */
    private void simulateDbQuery() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

7. 单元测试示例

7.1 缓存服务测试

java 复制代码
package com.enterprise.cache.test;

import com.enterprise.cache.enums.CacheType;
import com.enterprise.cache.service.CacheService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.*;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 缓存服务单元测试
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@SpringBootTest
class CacheServiceTest {
    
    @Autowired
    private CacheService cacheService;
    
    private static final String CACHE_NAME = CacheType.NORMAL_DATA.getCacheName();
    
    @BeforeEach
    void setUp() {
        cacheService.clear(CACHE_NAME);
    }
    
    @Test
    void testPutAndGet() {
        // Given
        String key = "test:user:1";
        String value = "John Doe";
        
        // When
        cacheService.put(CACHE_NAME, key, value);
        String cachedValue = cacheService.get(CACHE_NAME, key);
        
        // Then
        assertThat(cachedValue).isEqualTo(value);
    }
    
    @Test
    void testEvict() {
        // Given
        String key = "test:user:2";
        String value = "Jane Doe";
        cacheService.put(CACHE_NAME, key, value);
        
        // When
        cacheService.evict(CACHE_NAME, key);
        String cachedValue = cacheService.get(CACHE_NAME, key);
        
        // Then
        assertThat(cachedValue).isNull();
    }
    
    @Test
    void testBatchGet() {
        // Given
        Map<String, String> testData = new HashMap<>();
        testData.put("1", "User1");
        testData.put("2", "User2");
        testData.put("3", "User3");
        cacheService.batchPut(CACHE_NAME, testData);
        
        // When
        Collection<String> keys = Arrays.asList("1", "2", "3");
        Map<String, String> result = cacheService.batchGet(CACHE_NAME, keys);
        
        // Then
        assertThat(result).hasSize(3);
        assertThat(result.get("1")).isEqualTo("User1");
    }
    
    @Test
    void testExists() {
        // Given
        String existingKey = "test:exists:1";
        cacheService.put(CACHE_NAME, existingKey, "value");
        
        // Then
        assertThat(cacheService.exists(CACHE_NAME, existingKey)).isTrue();
        assertThat(cacheService.exists(CACHE_NAME, "nonexistent")).isFalse();
    }
}

小结

本文档(第三篇)完成了以下内容:

✅ 监控与运维

  • 缓存指标收集器(Prometheus集成)
  • 热点Key识别器
  • Spring Boot Actuator端点
  • Grafana Dashboard配置
  • 缓存异常告警机制

✅ 缓存刷新机制

  • 批量刷新调度器
  • 异步刷新策略

✅ 实战案例

  • 用户信息缓存(完整实现)
  • 接口限流缓存(基于Caffeine)
  • 热点商品缓存(秒杀场景)

✅ 单元测试

四、配置文件与性能优化

1. 配置文件详解

1.1 application.yml 主配置

yaml 复制代码
# ============================================================
# Spring Boot 3 + Caffeine 缓存主配置文件
# ============================================================

spring:
  application:
    name: caffeine-cache-practice
  
  # 激活的环境配置文件
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}
  
  # Jackson序列化配置
  jackson:
    default-property-inclusion: non_null
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
    serialization:
      write-dates-as-timestamps: false
      fail-on-empty-beans: false

# ============================================================
# Caffeine 缓存配置
# ============================================================
cache:
  caffeine:
    # 是否启用缓存
    enabled: true
    
    # 默认缓存配置(适用于所有缓存实例)
    default-spec:
      # 初始容量(建议设置为maximumSize的10-20%)
      initial-capacity: 100
      
      # 最大容量(超过此值会根据LRU策略驱逐)
      maximum-size: 1000
      
      # 写入后过期时间(秒)
      expire-after-write: 600
      
      # 访问后过期时间(秒)
      expire-after-access: 300
      
      # 是否开启统计
      record-stats: true
      
      # 弱引用配置
      weak-keys: false
      weak-values: false
      soft-values: false
    
    # 各缓存实例的特定配置
    specs:
      # 用户信息缓存
      userInfo:
        maximum-size: 5000
        expire-after-write: 600    # 10分钟
        expire-after-access: 1800  # 30分钟
        refresh-after-write: 300   # 5分钟异步刷新
        record-stats: true
      
      # 字典数据缓存
      dictionary:
        maximum-size: 2000
        expire-after-write: 3600   # 1小时
        refresh-after-write: 1800  # 30分钟异步刷新
        record-stats: true
      
      # 热点数据缓存
      hotData:
        initial-capacity: 200
        maximum-size: 10000
        expire-after-write: 100    # 100秒
        expire-after-access: 300   # 5分钟
        record-stats: true
      
      # 商品信息缓存
      productInfo:
        maximum-size: 8000
        expire-after-write: 300    # 5分钟
        expire-after-access: 900   # 15分钟
        record-stats: true
      
      # 限流缓存
      rateLimit:
        maximum-size: 10000
        expire-after-write: 1      # 1秒
        record-stats: true
    
    # 统计配置
    stats:
      # 是否启用统计
      enabled: true
      
      # 统计输出间隔(秒)
      output-interval: 60
      
      # 命中率阈值(低于此值告警)
      hit-rate-threshold: 0.7
    
    # 预热配置
    warm-up:
      # 是否启用预热
      enabled: true
      
      # 预热延迟启动时间(秒)
      delay-seconds: 30
      
      # 预热数据批次大小
      batch-size: 100

# ============================================================
# Redis 配置(分布式缓存,可选)
# ============================================================
spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      database: 0
      timeout: 3000ms
      
      # Lettuce 连接池配置
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 2
          max-wait: 3000ms
        shutdown-timeout: 5000ms

# ============================================================
# Actuator 监控配置
# ============================================================
management:
  endpoints:
    web:
      exposure:
        # 暴露所有端点(生产环境建议按需暴露)
        include: '*'
      base-path: /actuator
  
  endpoint:
    health:
      show-details: always
    metrics:
      enabled: true
  
  # Prometheus 监控配置
  metrics:
    export:
      prometheus:
        enabled: true
        step: 1m
    
    # 应用标签
    tags:
      application: ${spring.application.name}
      environment: ${spring.profiles.active}
    
    # 直方图配置
    distribution:
      percentiles-histogram:
        http.server.requests: true
      slo:
        http.server.requests: 10ms,50ms,100ms,200ms,500ms

# ============================================================
# 日志配置
# ============================================================
logging:
  level:
    root: INFO
    com.enterprise.cache: DEBUG
    com.github.benmanes.caffeine: DEBUG
    
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
    file: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'
  
  # 日志文件配置
  file:
    name: logs/application.log
    max-size: 100MB
    max-history: 30

# ============================================================
# 服务器配置
# ============================================================
server:
  port: 8080
  
  # Tomcat 配置
  tomcat:
    threads:
      max: 200
      min-spare: 10
    accept-count: 100
    max-connections: 10000
  
  # 压缩配置
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json

1.2 开发环境配置

yaml 复制代码
# ============================================================
# application-dev.yml - 开发环境配置
# ============================================================

spring:
  # Redis 配置(开发环境)
  data:
    redis:
      host: localhost
      port: 6379
      password:  # 开发环境无密码
      database: 0

# 缓存配置(开发环境)
cache:
  caffeine:
    # 开发环境使用较小的缓存容量
    default-spec:
      maximum-size: 500
      expire-after-write: 300
    
    specs:
      userInfo:
        maximum-size: 1000
        expire-after-write: 300
      
      productInfo:
        maximum-size: 2000
        expire-after-write: 180
    
    # 统计配置(开发环境更频繁的输出)
    stats:
      enabled: true
      output-interval: 30  # 30秒输出一次
      hit-rate-threshold: 0.6
    
    # 预热配置(开发环境不预热)
    warm-up:
      enabled: false

# 日志配置(开发环境)
logging:
  level:
    root: INFO
    com.enterprise.cache: DEBUG
    com.github.benmanes.caffeine: DEBUG
    org.springframework.cache: DEBUG
  
  file:
    name: logs/dev/application.log

# 开发工具配置
spring:
  devtools:
    restart:
      enabled: true
    livereload:
      enabled: true

1.3 测试环境配置

yaml 复制代码
# ============================================================
# application-test.yml - 测试环境配置
# ============================================================

spring:
  data:
    redis:
      host: ${REDIS_HOST:redis-test.internal}
      port: 6379
      password: ${REDIS_PASSWORD}
      database: 1  # 使用不同的数据库

cache:
  caffeine:
    # 测试环境使用中等容量
    default-spec:
      maximum-size: 2000
      expire-after-write: 600
    
    specs:
      userInfo:
        maximum-size: 3000
        expire-after-write: 600
      
      productInfo:
        maximum-size: 5000
        expire-after-write: 300
    
    stats:
      enabled: true
      output-interval: 60
      hit-rate-threshold: 0.7
    
    warm-up:
      enabled: true
      delay-seconds: 30
      batch-size: 50

logging:
  level:
    root: INFO
    com.enterprise.cache: INFO
  
  file:
    name: logs/test/application.log
    max-size: 50MB

server:
  port: 8080

1.4 生产环境配置

yaml 复制代码
# ============================================================
# application-prod.yml - 生产环境配置
# ============================================================

spring:
  # Redis 配置(生产环境)
  data:
    redis:
      # 使用环境变量配置,增强安全性
      host: ${REDIS_HOST}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD}
      database: 0
      
      # 生产环境使用更大的连接池
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 3000ms
        shutdown-timeout: 10000ms

# 缓存配置(生产环境)
cache:
  caffeine:
    # 生产环境使用大容量配置
    default-spec:
      initial-capacity: 200
      maximum-size: 5000
      expire-after-write: 600
      expire-after-access: 300
      record-stats: true
    
    specs:
      # 用户信息缓存
      userInfo:
        initial-capacity: 200
        maximum-size: 10000
        expire-after-write: 1800     # 30分钟
        expire-after-access: 3600    # 1小时
        refresh-after-write: 900     # 15分钟异步刷新
      
      # 字典数据缓存
      dictionary:
        maximum-size: 5000
        expire-after-write: 7200     # 2小时
        refresh-after-write: 3600    # 1小时异步刷新
      
      # 热点数据缓存
      hotData:
        initial-capacity: 500
        maximum-size: 20000
        expire-after-write: 180      # 3分钟
        expire-after-access: 600     # 10分钟
      
      # 商品信息缓存
      productInfo:
        initial-capacity: 300
        maximum-size: 15000
        expire-after-write: 600      # 10分钟
        expire-after-access: 1800    # 30分钟
        refresh-after-write: 300     # 5分钟异步刷新
      
      # 限流缓存
      rateLimit:
        initial-capacity: 2000
        maximum-size: 50000
        expire-after-write: 60       # 1分钟
    
    # 统计配置
    stats:
      enabled: true
      output-interval: 300  # 5分钟输出一次
      hit-rate-threshold: 0.85  # 生产环境要求更高的命中率
    
    # 预热配置
    warm-up:
      enabled: true
      delay-seconds: 60     # 启动后1分钟开始预热
      batch-size: 200       # 更大的批次

# Actuator 配置(生产环境按需暴露)
management:
  endpoints:
    web:
      exposure:
        # 生产环境只暴露必要的端点
        include: health,info,metrics,prometheus,cacheMonitor
  
  metrics:
    tags:
      environment: production

# 日志配置(生产环境)
logging:
  level:
    root: WARN
    com.enterprise.cache: INFO
    com.github.benmanes.caffeine: WARN
  
  file:
    name: /var/log/cache-service/application.log
    max-size: 100MB
    max-history: 30

# 服务器配置(生产环境)
server:
  port: 8080
  
  tomcat:
    threads:
      max: 500
      min-spare: 50
    accept-count: 200
    max-connections: 20000

2. 性能优化策略

2.1 缓存预热

2.1.1 预热服务实现
java 复制代码
package com.enterprise.cache.service;

import com.enterprise.cache.config.properties.CaffeineProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 缓存预热服务
 * 应用启动时预加载热点数据
 * 
 * 预热策略:
 * 1. 延迟启动:避免影响应用启动速度
 * 2. 批量加载:提高预热效率
 * 3. 异步执行:不阻塞主线程
 * 4. 优先级排序:先加载最热的数据
 * 
 * @author Enterprise Team
 * @version 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CacheWarmUpService {
    
    private final CaffeineProperties caffeineProperties;
    private final CacheService cacheService;
    
    /**
     * 应用启动后执行预热
     */
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        if (!caffeineProperties.getWarmUp().isEnabled()) {
            log.info("Cache warm-up is disabled");
            return;
        }
        
        int delaySeconds = caffeineProperties.getWarmUp().getDelaySeconds();
        log.info("Cache warm-up will start in {} seconds", delaySeconds);
        
        // 延迟启动预热
        CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(delaySeconds);
                warmUpAllCaches();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("Cache warm-up interrupted", e);
            }
        });
    }
    
    /**
     * 预热所有缓存
     */
    @Async("cacheWarmUpExecutor")
    public void warmUpAllCaches() {
        log.info("🔥 Starting cache warm-up...");
        
        long startTime = System.currentTimeMillis();
        int totalWarmed = 0;
        
        try {
            // 预热用户信息缓存
            totalWarmed += warmUpUserCache();
            
            // 预热字典数据缓存
            totalWarmed += warmUpDictionaryCache();
            
            // 预热商品信息缓存
            totalWarmed += warmUpProductCache();
            
            long duration = System.currentTimeMillis() - startTime;
            log.info("✅ Cache warm-up completed: {} entries loaded in {}ms",
                    totalWarmed, duration);
            
        } catch (Exception e) {
            log.error("❌ Cache warm-up failed", e);
        }
    }
    
    /**
     * 预热用户信息缓存
     */
    private int warmUpUserCache() {
        log.info("Warming up user cache...");
        
        try {
            int batchSize = caffeineProperties.getWarmUp().getBatchSize();
            
            // TODO: 从数据库加载热点用户数据
            // List<User> hotUsers = userRepository.findHotUsers(batchSize);
            // Map<String, User> userMap = hotUsers.stream()
            //     .collect(Collectors.toMap(u -> String.valueOf(u.getId()), u -> u));
            // cacheService.batchPut("userInfo", userMap);
            
            log.info("User cache warm-up completed");
            return batchSize;
        } catch (Exception e) {
            log.error("User cache warm-up failed", e);
            return 0;
        }
    }
    
    /**
     * 预热字典数据缓存
     */
    private int warmUpDictionaryCache() {
        log.info("Warming up dictionary cache...");
        
        try {
            // TODO: 加载系统字典数据
            // List<Dictionary> dictionaries = dictionaryRepository.findAll();
            // Map<String, Dictionary> dictMap = dictionaries.stream()
            //     .collect(Collectors.toMap(Dictionary::getCode, d -> d));
            // cacheService.batchPut("dictionary", dictMap);
            
            log.info("Dictionary cache warm-up completed");
            return 50;
        } catch (Exception e) {
            log.error("Dictionary cache warm-up failed", e);
            return 0;
        }
    }
    
    /**
     * 预热商品信息缓存
     */
    private int warmUpProductCache() {
        log.info("Warming up product cache...");
        
        try {
            int batchSize = caffeineProperties.getWarmUp().getBatchSize();
            
            // TODO: 加载热销商品数据
            // List<Product> hotProducts = productRepository.findHotProducts(batchSize);
            // Map<String, Product> productMap = hotProducts.stream()
            //     .collect(Collectors.toMap(p -> String.valueOf(p.getId()), p -> p));
            // cacheService.batchPut("productInfo", productMap);
            
            log.info("Product cache warm-up completed");
            return batchSize;
        } catch (Exception e) {
            log.error("Product cache warm-up failed", e);
            return 0;
        }
    }
}

2.2 异步加载优化

java 复制代码
/**
 * 异步缓存加载器
 * 
 * 优化策略:
 * 1. 使用专用线程池
 * 2. 设置合理的超时时间
 * 3. 异常处理和降级
 * 4. 监控加载性能
 */
@Slf4j
@Component
public class OptimizedAsyncCacheLoader implements AsyncCacheLoader<String, Object> {
    
    private final Executor executor;
    
    public OptimizedAsyncCacheLoader() {
        // 创建专用线程池
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        this.executor = new ThreadPoolExecutor(
                corePoolSize,
                corePoolSize * 2,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                new ThreadFactoryBuilder()
                        .setNameFormat("async-cache-loader-%d")
                        .setDaemon(true)
                        .build(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
    
    @Override
    public CompletableFuture<Object> asyncLoad(String key, Executor executor) {
        return CompletableFuture.supplyAsync(() -> {
            long startTime = System.currentTimeMillis();
            
            try {
                log.debug("Async loading cache: key={}", key);
                
                // 实际的数据加载逻辑
                Object data = loadDataFromDatabase(key);
                
                long duration = System.currentTimeMillis() - startTime;
                log.debug("Async load completed: key={}, duration={}ms", key, duration);
                
                return data;
            } catch (Exception e) {
                log.error("Async load failed: key={}", key, e);
                return null;
            }
        }, this.executor);
    }
    
    private Object loadDataFromDatabase(String key) {
        // TODO: 实际实现
        return null;
    }
}

2.3 批量操作优化

java 复制代码
/**
 * 批量缓存操作服务
 * 
 * 优化策略:
 * 1. 减少网络往返次数
 * 2. 批量查询数据库
 * 3. 并行处理
 * 4. 合理的批次大小
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class BatchCacheService {
    
    private final Cache<String, Object> cache;
    
    /**
     * 批量获取缓存数据
     * 
     * @param cacheName 缓存名称
     * @param keys Key列表
     * @param loader 数据加载器
     * @return Key-Value映射
     */
    public <T> Map<String, T> batchGet(String cacheName,
                                       Collection<String> keys,
                                       Function<Collection<String>, Map<String, T>> loader) {
        
        if (keys == null || keys.isEmpty()) {
            return Collections.emptyMap();
        }
        
        Map<String, T> result = new HashMap<>();
        List<String> missedKeys = new ArrayList<>();
        
        // 1. 批量从缓存获取
        for (String key : keys) {
            String fullKey = buildCacheKey(cacheName, key);
            Object cachedValue = cache.getIfPresent(fullKey);
            
            if (cachedValue != null) {
                result.put(key, (T) cachedValue);
            } else {
                missedKeys.add(key);
            }
        }
        
        log.debug("Batch cache query: total={}, hit={}, miss={}",
                keys.size(), result.size(), missedKeys.size());
        
        // 2. 批量加载缓存未命中的数据
        if (!missedKeys.isEmpty()) {
            Map<String, T> loadedData = loader.apply(missedKeys);
            
            // 3. 批量写入缓存
            Map<String, T> cacheMap = loadedData.entrySet().stream()
                    .collect(Collectors.toMap(
                            entry -> buildCacheKey(cacheName, entry.getKey()),
                            Map.Entry::getValue
                    ));
            
            cache.putAll(cacheMap);
            result.putAll(loadedData);
            
            log.debug("Batch loaded and cached: count={}", loadedData.size());
        }
        
        return result;
    }
    
    private String buildCacheKey(String cacheName, String key) {
        return cacheName + ":" + key;
    }
}

2.4 内存占用控制

java 复制代码
/**
 * 内存占用控制策略
 */
public class MemoryControlStrategy {
    
    /**
     * 策略1:使用软引用(内存不足时自动回收)
     */
    public Cache<String, Object> createSoftReferenceCache() {
        return Caffeine.newBuilder()
                .maximumSize(10000)
                .softValues()  // 软引用值
                .build();
    }
    
    /**
     * 策略2:设置合理的最大容量
     * 建议:不超过堆内存的20%
     */
    public Cache<String, Object> createSizedCache() {
        // 假设堆内存为4GB
        // 每个对象平均200字节
        // 4GB * 20% / 200 = 4,194,304
        return Caffeine.newBuilder()
                .maximumSize(4_000_000)  // 约800MB
                .recordStats()
                .build();
    }
    
    /**
     * 策略3:基于权重的容量限制
     */
    public Cache<String, Object> createWeightedCache() {
        return Caffeine.newBuilder()
                .maximumWeight(100_000_000)  // 100MB
                .weigher((String key, Object value) -> {
                    // 估算对象大小
                    return estimateObjectSize(value);
                })
                .build();
    }
    
    private int estimateObjectSize(Object value) {
        // 简单估算,实际可使用更精确的方法
        if (value instanceof String) {
            return ((String) value).length() * 2;
        }
        return 200;  // 默认200字节
    }
}

2.5 GC友好配置

java 复制代码
/**
 * GC友好的Caffeine配置
 * 
 * 优化目标:
 * 1. 减少Full GC频率
 * 2. 降低GC停顿时间
 * 3. 提高内存利用率
 */
public class GCFriendlyConfig {
    
    /**
     * 配置1:使用弱引用键(适合短生命周期的键)
     */
    public Cache<String, Object> createWeakKeysCache() {
        return Caffeine.newBuilder()
                .weakKeys()  // 弱引用键
                .maximumSize(10000)
                .build();
    }
    
    /**
     * 配置2:合理的初始容量(避免频繁扩容)
     */
    public Cache<String, Object> createOptimizedCache() {
        return Caffeine.newBuilder()
                .initialCapacity(1000)   // 设置为预期容量
                .maximumSize(10000)
                .build();
    }
    
    /**
     * 配置3:使用LRU驱逐策略(避免内存泄漏)
     */
    public Cache<String, Object> createLRUCache() {
        return Caffeine.newBuilder()
                .maximumSize(5000)
                .expireAfterAccess(10, TimeUnit.MINUTES)
                .removalListener((key, value, cause) -> {
                    // 及时释放资源
                    if (value instanceof Closeable) {
                        try {
                            ((Closeable) value).close();
                        } catch (IOException e) {
                            log.error("Failed to close resource", e);
                        }
                    }
                })
                .build();
    }
}

3. 最佳实践总结

3.1 缓存设计原则

markdown 复制代码
# Caffeine缓存设计原则

## 1. 容量规划
✅ 合理设置maximumSize,不超过堆内存的20%
✅ initialCapacity设置为maximumSize的10-20%
✅ 根据业务场景选择合适的缓存类型
❌ 避免无限制增长
❌ 避免缓存过大导致频繁GC

## 2. 过期策略
✅ 热点数据:短过期 + 高频刷新(100s-300s)
✅ 常规数据:中等过期(5min-10min)
✅ 冷数据:长过期(30min-1h)
✅ 使用随机过期时间防止雪崩
❌ 避免所有缓存同时过期
❌ 避免过期时间过短导致频繁加载

## 3. Key设计
✅ 使用业务有意义的Key
✅ 包含必要的前缀区分不同业务
✅ 使用复合Key时保持稳定
❌ 避免使用对象toString()
❌ 避免Key过长(建议<200字符)

## 4. 并发控制
✅ 使用分布式锁防止缓存击穿
✅ 使用布隆过滤器防止缓存穿透
✅ 异步刷新减少阻塞
❌ 避免大量并发加载相同Key

## 5. 监控告警
✅ 监控命中率、驱逐率、加载时间
✅ 设置合理的告警阈值
✅ 定期生成统计报告
❌ 不要忽视缓存性能指标

3.2 性能优化检查清单

markdown 复制代码
# 性能优化检查清单

## 启动优化
- [ ] 启用缓存预热
- [ ] 延迟预热启动(30-60秒)
- [ ] 批量加载热点数据
- [ ] 异步执行预热任务

## 运行时优化
- [ ] 使用批量操作减少往返
- [ ] 启用异步刷新机制
- [ ] 设置合理的过期时间
- [ ] 使用随机过期防雪崩

## 内存优化
- [ ] 控制缓存总容量
- [ ] 使用软引用/弱引用(如需要)
- [ ] 设置合理的初始容量
- [ ] 监控内存使用情况

## 并发优化
- [ ] 使用分布式锁防击穿
- [ ] 使用布隆过滤器防穿透
- [ ] 合理设置线程池大小
- [ ] 避免热点Key并发问题

## 监控优化
- [ ] 集成Prometheus监控
- [ ] 配置Grafana仪表板
- [ ] 设置告警规则
- [ ] 定期分析统计报告

4. 常见问题与解决方案

4.1 问题:缓存命中率低

症状:

  • 命中率 < 70%
  • 大量数据库查询
  • 响应时间慢

原因分析:

  1. 过期时间设置过短
  2. 缓存容量不足导致频繁驱逐
  3. Key设计不合理

解决方案:

yaml 复制代码
# 1. 延长过期时间
cache:
  caffeine:
    specs:
      userInfo:
        expire-after-write: 1800  # 从600秒增加到1800秒

# 2. 增加缓存容量
cache:
  caffeine:
    specs:
      userInfo:
        maximum-size: 20000  # 从10000增加到20000

# 3. 优化Key设计
# 不好的设计:
key: "#user.toString()"  # 对象内存地址不稳定

# 好的设计:
key: "'user:' + #user.id"  # 使用稳定的业务ID

4.2 问题:内存占用过高

症状:

  • JVM堆内存使用率 > 80%
  • 频繁Full GC
  • 应用响应变慢

原因分析:

  1. maximumSize设置过大
  2. 缓存值过大
  3. 未及时驱逐过期数据

解决方案:

yaml 复制代码
# 1. 降低maximumSize
cache:
  caffeine:
    specs:
      userInfo:
        maximum-size: 5000  # 从20000降低到5000

# 2. 使用软引用
cache:
  caffeine:
    default-spec:
      soft-values: true  # 内存不足时自动回收

# 3. 缩短过期时间
cache:
  caffeine:
    specs:
      userInfo:
        expire-after-write: 300  # 从1800秒降低到300秒
java 复制代码
// 4. 分片存储大对象
public class LargeObjectCacheStrategy {
    
    public void cacheLargeObject(String key, byte[] data) {
        int chunkSize = 1024 * 1024;  // 1MB per chunk
        int chunks = (data.length + chunkSize - 1) / chunkSize;
        
        for (int i = 0; i < chunks; i++) {
            int start = i * chunkSize;
            int end = Math.min(start + chunkSize, data.length);
            byte[] chunk = Arrays.copyOfRange(data, start, end);
            
            cacheService.put("largeObject", 
                    key + ":chunk:" + i, chunk);
        }
    }
}

4.3 问题:缓存击穿(热点Key失效)

症状:

  • 某个Key失效瞬间大量请求打到数据库
  • 数据库CPU飙升
  • 响应时间突增

解决方案:

java 复制代码
// 方案1:分布式锁
@EnhancedCacheable(
    cacheType = CacheType.HOT_DATA,
    key = "#productId",
    preventBreakdown = true,  // 启用分布式锁
    lockTimeout = 3000
)
public Product getHotProduct(Long productId) {
    return productRepository.findById(productId);
}

// 方案2:永不过期策略
public Product getHotProductNeverExpire(Long productId) {
    return neverExpireStrategy.getWithNeverExpire(
        "hotData",
        String.valueOf(productId),
        () -> productRepository.findById(productId),
        300  // 逻辑过期时间
    );
}

4.4 问题:缓存雪崩(大量缓存同时失效)

症状:

  • 大量缓存同时失效
  • 数据库压力骤增
  • 系统整体不可用

解决方案:

java 复制代码
// 方案1:随机过期时间
@EnhancedCacheable(
    cacheType = CacheType.NORMAL_DATA,
    key = "#userId",
    randomExpire = true,
    randomExpireRange = 300  // 增加0-300秒的随机偏移
)
public User getUser(Long userId) {
    return userRepository.findById(userId);
}

// 方案2:熔断降级
@EnhancedCacheable(
    cacheType = CacheType.USER_INFO,
    key = "#userId",
    enableFallback = true,
    fallbackMethod = "getUserFallback"
)
public User getUserWithFallback(Long userId) {
    return userRepository.findById(userId);
}

public User getUserFallback(Long userId) {
    return User.builder()
            .id(userId)
            .username("guest")
            .build();
}

5. 性能基准测试

5.1 测试环境

复制代码
硬件配置:
- CPU: Intel Xeon 8核
- 内存: 16GB
- 存储: SSD

软件配置:
- JDK: 17
- Spring Boot: 3.2.1
- Caffeine: 3.1.8

5.2 测试结果

测试场景 QPS P50延迟 P95延迟 P99延迟 命中率
纯缓存命中 1,200,000 0.05ms 0.1ms 0.2ms 100%
缓存未命中(同步加载) 15,000 50ms 80ms 120ms 0%
缓存未命中(异步加载) 50,000 10ms 20ms 35ms 0%
混合场景(80%命中率) 850,000 1ms 15ms 30ms 80%
高并发访问(1000并发) 600,000 2ms 8ms 15ms 85%

5.3 性能对比

复制代码
缓存 vs 数据库直接查询:

场景1:单次查询
- 缓存命中:0.05ms
- 数据库查询:50ms
- 性能提升:1000倍

场景2:1000次批量查询
- 批量缓存查询:50ms
- 批量数据库查询:5000ms
- 性能提升:100倍

场景3:高并发查询(1000 QPS)
- 缓存:CPU 10%, 内存 500MB
- 数据库:CPU 80%, 连接池耗尽

小结

本文档(第四篇)完成了以下内容:

✅ 配置文件详解

  • 主配置文件(application.yml)
  • 开发环境配置
  • 测试环境配置
  • 生产环境配置
  • 所有配置项详细说明

✅ 性能优化策略

  • 缓存预热实现
  • 异步加载优化
  • 批量操作优化
  • 内存占用控制
  • GC友好配置

✅ 最佳实践

  • 缓存设计原则
  • 性能优化检查清单
  • 常见问题与解决方案
  • 性能基准测试数据

相关推荐
程序员老赵1 小时前
Docker 部署 Dolibarr:轻松搭建开源 ERP/CRM 平台
运维·前端·后端
量化分析码农2 小时前
【Python量化系统工程实战 #06】从本地脚本到云端部署:量化系统的最小可运行架构
后端
136096757232 小时前
页面打不开不是 Nginx 的错
前端·后端
ALONE阿龙太原微码2 小时前
RBAC 以及主流权限模型
后端
斑鸠喳喳2 小时前
线程本地存储 ThreadLocal
java·后端
你顶住我先撤2 小时前
RocketMQ 消息类型
后端
青茶3602 小时前
开发一个网站插件,什么时候用到缓存存储功能呢?
缓存·网站
海绵宝宝转agent3 小时前
Pico 学习笔记 Harness 设计、历史压缩、三类目录与缓存复用
笔记·学习·缓存
ShineWinsu3 小时前
对于Redis:AOF持久化的解析
linux·数据库·redis·缓存·面试·持久化·aof
仍然.3 小时前
Redis---集群
数据库·redis·缓存