本文目录导读:

我来详细介绍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远程缓存提速方案,适用于高并发、大数据量的业务场景。