Java远程缓存提速案例如何做

wen java案例 26

本文目录导读:

Java远程缓存提速案例如何做

  1. 业务场景与问题
  2. 缓存架构设计
  3. 完整代码实现
  4. 性能对比数据
  5. 最佳实践建议
  6. 注意事项

我来详细介绍Java远程缓存提速的实战案例,包括设计方案、代码实现和性能对比。

业务场景与问题

场景描述

假设有一个电商系统的商品详情页,每次查询都直接访问数据库,导致:

  • 数据库QPS达到5000+,CPU使用率85%
  • 接口响应时间平均800ms
  • 高峰期经常出现超时、慢查询

缓存架构设计

整体方案:多级缓存架构

用户请求 → CDN → Nginx本地缓存 → Redis集群 → 本地缓存(Caffeine) → 数据库
                                                                    ↓
                                                              (兜底策略)

完整代码实现

Maven依赖配置

<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Caffeine本地缓存 -->
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
        <version>3.1.8</version>
    </dependency>
    <!-- Redisson分布式锁 -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.23.4</version>
    </dependency>
    <!-- Jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

配置类实现

@Configuration
@EnableCaching
public class CacheConfig {
    // Redis连接工厂配置
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName("localhost");
        config.setPort(6379);
        config.setPassword("yourpassword");
        config.setDatabase(0);
        // 配置连接池
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(20);
        poolConfig.setMaxIdle(10);
        poolConfig.setMinIdle(5);
        LettucePoolingClientConfiguration clientConfig = 
            LettucePoolingClientConfiguration.builder()
                .poolConfig(poolConfig)
                .commandTimeout(Duration.ofMillis(200))
                .build();
        return new LettuceConnectionFactory(config, clientConfig);
    }
    // RedisTemplate配置
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // JSON序列化配置
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.WRAPPER_ARRAY);
        serializer.setObjectMapper(mapper);
        // String序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
    // Redis缓存管理器
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认过期时间30分钟
            .serializeKeysWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    new StringRedisSerializer()))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();  // 禁止缓存null值
        // 针对不同业务设置不同过期时间
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("product", config.entryTtl(Duration.ofMinutes(10)));
        configMap.put("hotProduct", config.entryTtl(Duration.ofMinutes(1)));
        configMap.put("user", config.entryTtl(Duration.ofHours(1)));
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withInitialCacheConfigurations(configMap)
            .transactionAware()
            .build();
    }
    // Caffeine本地缓存
    @Bean
    public Cache<String, Product> caffeineCache() {
        return Caffeine.newBuilder()
            .initialCapacity(100)
            .maximumSize(500)
            .expireAfterWrite(Duration.ofSeconds(10))
            .recordStats()
            .build();
    }
}

缓存服务层实现

@Service
@Slf4j
public class ProductCacheService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private Cache<String, Product> caffeineCache;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private RedissonClient redissonClient;
    // 缓存前缀
    private static final String CACHE_KEY_PREFIX = "product:detail:";
    private static final String LOCK_KEY_PREFIX = "lock:product:";
    /**
     * 多级缓存查询(读模式)
     */
    public Product getProductById(Long productId) {
        String cacheKey = CACHE_KEY_PREFIX + productId;
        // 1. 查本地缓存(Caffeine)
        Product localProduct = caffeineCache.getIfPresent(cacheKey);
        if (localProduct != null) {
            log.info("从本地缓存命中: {}", cacheKey);
            return localProduct;
        }
        // 2. 查Redis缓存
        Product redisProduct = (Product) redisTemplate.opsForValue().get(cacheKey);
        if (redisProduct != null) {
            log.info("从Redis缓存命中: {}", cacheKey);
            // 回填本地缓存
            caffeineCache.put(cacheKey, redisProduct);
            return redisProduct;
        }
        // 3. 查数据库(带分布式锁,防止缓存击穿)
        String lockKey = LOCK_KEY_PREFIX + productId;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试加锁,最多等待2秒,锁自动释放时间10秒
            boolean isLocked = lock.tryLock(2, 10, TimeUnit.SECONDS);
            if (isLocked) {
                // 双重检查,防止在等待锁期间其他线程已经加载了数据
                Product doubleCheck = (Product) redisTemplate.opsForValue().get(cacheKey);
                if (doubleCheck != null) {
                    log.info("双重检查命中: {}", cacheKey);
                    caffeineCache.put(cacheKey, doubleCheck);
                    return doubleCheck;
                }
                // 查询数据库
                Product product = productMapper.selectById(productId);
                if (product != null) {
                    // 写入Redis缓存(随机过期时间,防止缓存雪崩)
                    int expireTime = 300 + new Random().nextInt(60);
                    redisTemplate.opsForValue().set(cacheKey, product, 
                        Duration.ofSeconds(expireTime));
                    // 写入本地缓存
                    caffeineCache.put(cacheKey, product);
                    log.info("从数据库加载并写入缓存: {}", cacheKey);
                    return product;
                } else {
                    // 防止缓存穿透:缓存空值,过期时间较短
                    redisTemplate.opsForValue().set(cacheKey, new Product(), 
                        Duration.ofSeconds(30));
                    return null;
                }
            } else {
                // 获取锁失败,降级处理
                log.warn("获取分布式锁超时,返回降级数据");
                return getFallbackProduct(productId);
            }
        } catch (InterruptedException e) {
            log.error("获取分布式锁被中断", e);
            Thread.currentThread().interrupt();
            return getFallbackProduct(productId);
        } finally {
            // 释放锁(确保释放)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    /**
     * 更新缓存(写模式)
     */
    public void updateProduct(Product product) {
        String cacheKey = CACHE_KEY_PREFIX + product.getId();
        // 1. 更新数据库
        productMapper.updateById(product);
        // 2. 删除缓存(延迟双删策略)
        redisTemplate.delete(cacheKey);
        caffeineCache.invalidate(cacheKey);
        // 3. 延迟删除(解决并发读写问题)
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(() -> {
            redisTemplate.delete(cacheKey);
            caffeineCache.invalidate(cacheKey);
            log.info("延迟删除缓存: {}", cacheKey);
        }, 500, TimeUnit.MILLISECONDS);
        executor.shutdown();
    }
    /**
     * 批量预热热点商品
     */
    public void preloadHotProducts(List<Long> productIds) {
        List<Product> products = productMapper.selectBatchIds(productIds);
        products.parallelStream().forEach(product -> {
            String cacheKey = CACHE_KEY_PREFIX + product.getId();
            // 设置较长的过期时间
            redisTemplate.opsForValue().set(cacheKey, product, 
                Duration.ofHours(2));
            // 写入本地缓存
            caffeineCache.put(cacheKey, product);
            log.info("预热缓存: {}", cacheKey);
        });
    }
    /**
     * 降级数据
     */
    private Product getFallbackProduct(Long productId) {
        // 返回基本的商品信息或默认数据
        Product fallback = new Product();
        fallback.setId(productId);
        fallback.setName("商品信息加载中...");
        fallback.setPrice(new BigDecimal("0.00"));
        return fallback;
    }
}

缓存监控与统计

@Component
@Slf4j
public class CacheMonitor {
    @Autowired
    private Cache<String, Product> caffeineCache;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // 缓存统计指标
    private final AtomicLong cacheHits = new AtomicLong(0);
    private final AtomicLong cacheMisses = new AtomicLong(0);
    private final AtomicLong dbQueries = new AtomicLong(0);
    /**
     * 记录缓存命中
     */
    public void recordHit() {
        cacheHits.incrementAndGet();
    }
    /**
     * 记录缓存未命中
     */
    public void recordMiss() {
        cacheMisses.incrementAndGet();
    }
    /**
     * 记录数据库查询
     */
    public void recordDbQuery() {
        dbQueries.incrementAndGet();
    }
    /**
     * 获取缓存命中率
     */
    public double getHitRate() {
        long hits = cacheHits.get();
        long total = hits + cacheMisses.get();
        return total == 0 ? 0 : (double) hits / total;
    }
    /**
     * 定时打印缓存统计
     */
    @Scheduled(fixedRate = 60000)
    public void printCacheStats() {
        // Caffeine统计
        CacheStats stats = caffeineCache.stats();
        log.info("===== 缓存统计报告 =====");
        log.info("本地缓存命中率: {}%", 
            String.format("%.2f", stats.hitRate() * 100));
        log.info("本地缓存请求数: {}", stats.requestCount());
        log.info("本地缓存大小: {}", caffeineCache.estimatedSize());
        log.info("Redis缓存命中率: {}%", 
            String.format("%.2f", getHitRate() * 100));
        log.info("数据库查询次数: {}", dbQueries.get());
        log.info("当前缓存命中率: {}%", 
            String.format("%.2f", getHitRate() * 100));
        // 重置统计
        cacheHits.set(0);
        cacheMisses.set(0);
        dbQueries.set(0);
    }
}

缓存性能测试

@RestController
@RequestMapping("/api/cache")
public class CacheTestController {
    @Autowired
    private ProductCacheService cacheService;
    @Autowired
    private ProductMapper productMapper;
    /**
     * 测试缓存性能对比
     */
    @GetMapping("/benchmark")
    public Map<String, Object> benchmark() {
        Map<String, Object> result = new HashMap<>();
        List<Long> elapsedTimes = new ArrayList<>();
        // 预热数据
        Long productId = 1L;
        cacheService.getProductById(productId);
        // 测试1000次请求
        for (int i = 0; i < 1000; i++) {
            long start = System.currentTimeMillis();
            Product product = cacheService.getProductById(productId);
            long elapsed = System.currentTimeMillis() - start;
            elapsedTimes.add(elapsed);
        }
        // 计算统计
        DoubleSummaryStatistics stats = elapsedTimes.stream()
            .mapToLong(Long::longValue)
            .summaryStatistics();
        result.put("total_requests", 1000);
        result.put("avg_response_time", stats.getAverage());
        result.put("min_response_time", stats.getMin());
        result.put("max_response_time", stats.getMax());
        result.put("throughput", 1000.0 / stats.getSum() * 1000);
        // 对比:直接查数据库
        List<Long> dbTimes = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            long start = System.currentTimeMillis();
            productMapper.selectById(productId);
            long elapsed = System.currentTimeMillis() - start;
            dbTimes.add(elapsed);
        }
        DoubleSummaryStatistics dbStats = dbTimes.stream()
            .mapToLong(Long::longValue)
            .summaryStatistics();
        result.put("db_avg_response_time", dbStats.getAverage());
        result.put("performance_improvement", 
            String.format("%.2fx", dbStats.getAverage() / stats.getAverage()));
        return result;
    }
    /**
     * 模拟缓存穿透测试
     */
    @GetMapping("/penetration-test")
    public String penetrationTest() {
        // 查询不存在的商品ID
        Long nonExistId = -1L;
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            Product product = cacheService.getProductById(nonExistId);
        }
        long elapsed = System.currentTimeMillis() - start;
        return String.format("缓存穿透测试完成,1000次请求耗时: %dms", elapsed);
    }
}

性能对比数据

测试结果

场景 平均耗时(ms) QPS 数据库压力
无缓存 800 1,250 100%
仅Redis缓存 50 20,000 10%
本地+Redis缓存 5 200,000 2%

性能提升效果

优化前:

  • 响应时间:平均800ms
  • 数据库QPS:5000
  • 服务器CPU:85%

优化后:

  • 响应时间:平均5ms(提升160倍)
  • 数据库QPS:100(降低98%)
  • 服务器CPU:25%

最佳实践建议

缓存策略选择

// 根据业务特点选择缓存策略
public enum CacheStrategy {
    // 强一致性要求:先更新数据库,再删除缓存
    STRONG_CONSISTENCY,
    // 最终一致性:异步更新缓存
    EVENTUAL_CONSISTENCY,
    // 热点数据:长期缓存,定时刷新
    HOT_DATA,
    // 冷数据:短时间缓存或不缓存
    COLD_DATA
}

缓存过期策略

// 根据数据特点设置不同过期时间
@Component
public class ExpireTimeStrategy {
    // 热点数据:1分钟
    private static final int HOT_DATA_EXPIRE = 60;
    // 普通数据:30分钟
    private static final int NORMAL_DATA_EXPIRE = 1800;
    // 冷数据:不缓存
    private static final int COLD_DATA_EXPIRE = 0;
    // 添加随机偏移,防止缓存雪崩
    public int getExpireTime(Product product) {
        int baseExpire;
        if (product.isHot()) {
            baseExpire = HOT_DATA_EXPIRE;
        } else if (product.isNormal()) {
            baseExpire = NORMAL_DATA_EXPIRE;
        } else {
            baseExpire = COLD_DATA_EXPIRE;
        }
        // 添加随机偏移(正负10%)
        int offset = (int) (baseExpire * 0.1 * Math.random());
        if (Math.random() > 0.5) {
            baseExpire += offset;
        } else {
            baseExpire -= offset;
        }
        return Math.max(baseExpire, 10); // 至少10秒
    }
}

监控告警配置

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,cache,metrics
  metrics:
    cache:
      enabled: true
    export:
      prometheus:
        enabled: true
---
# 自定义告警规则
spring:
  redis:
    client-type: lettuce
    timeout: 200ms
    connect-timeout: 100ms
    # 连接池健康检查
    health-check:
      enabled: true
      interval: 30s

注意事项

缓存穿透防护

// 缓存空值
if (product == null) {
    redisTemplate.opsForValue().set(cacheKey, new Product(), Duration.ofSeconds(30));
    return null;
}
// 布隆过滤器
public boolean isProductExists(Long productId) {
    // 使用布隆过滤器判断
    return bloomFilter.mightContain(productId.toString());
}

缓存一致性保障

// 使用消息队列保证最终一致性
@Component
public class CacheConsistencyService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void sendCacheUpdateMessage(Long productId) {
        // 发送消息到延迟队列
        rabbitTemplate.convertAndSend(
            "cache.exchange",
            "cache.update",
            productId.toString(),
            message -> {
                message.getMessageProperties().setDelay(500);
                return message;
            });
    }
    @RabbitListener(queues = "cache.delay.queue")
    public void handleCacheUpdate(String productId) {
        // 延迟删除缓存
        String cacheKey = CACHE_KEY_PREFIX + productId;
        redisTemplate.delete(cacheKey);
        log.info("消息队列触发缓存删除: {}", cacheKey);
    }
}

这个案例实现了完整的Java远程缓存提速方案,适用于高并发、大数据量的业务场景。

抱歉,评论功能暂时关闭!