Java多级缓存设计:应对微博明星官宣的高并发场景
一、多级缓存原理与必要性
1.1 为什么需要多级缓存?
单级缓存的问题:
- 性能瓶颈:所有请求都打到同一缓存层,压力集中
- 容错性差:缓存层故障直接影响整体可用性
- 网络开销:分布式缓存频繁网络IO
- 热点数据压力:明星官宣等热点事件导致缓存击穿
1.2 多级缓存的核心思想
客户端 → 本地缓存(L1) → 分布式缓存(L2) → 数据库
↑ ↑ ↑
最快访问 内存级 共享缓存
毫秒级响应 纳秒级访问 微秒级访问
二、多级缓存设计架构
2.1 典型四级缓存架构
java
// 架构示意
┌─────────────────────────────────────────┐
│ 客户端缓存 (L0) │
│ (App/Web端缓存,HTTP缓存) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 本地缓存 (L1) │
│ (Caffeine/Guava Cache,JVM进程内) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 分布式缓存 (L2) │
│ (Redis Cluster/Redis Sentinel) │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ 数据库缓存/持久层 (L3) │
│ (MySQL Query Cache/数据库连接池) │
└─────────────────────────────────────────┘
2.2 核心设计要点
java
public class MultiLevelCacheConfig {
// 1. 本地缓存配置
@Bean
public Cache<String, Object> localCache() {
return Caffeine.newBuilder()
.maximumSize(10_000) // 最大容量
.expireAfterWrite(5, TimeUnit.SECONDS) // 短暂过期时间
.expireAfterAccess(2, TimeUnit.SECONDS)
.recordStats() // 记录统计信息
.build();
}
// 2. 分布式缓存配置
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30)) // 比本地缓存长
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
三、具体实现方案
3.1 缓存加载策略
java
@Component
public class MultiLevelCacheService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DataService dataService;
/**
* 多级缓存读取流程
*/
public Object getWithMultiLevel(String key) {
// 1. 查询本地缓存 (L1)
Object value = localCache.getIfPresent(key);
if (value != null) {
recordCacheHit("local");
return value;
}
// 2. 查询分布式缓存 (L2)
value = redisTemplate.opsForValue().get(key);
if (value != null) {
// 回填本地缓存
localCache.put(key, value);
recordCacheHit("redis");
return value;
}
// 3. 防止缓存击穿:使用分布式锁
String lockKey = "lock:" + key;
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.tryLock(100, 10, TimeUnit.MILLISECONDS)) {
// 双重检查
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value);
return value;
}
// 4. 查询数据库 (L3)
value = dataService.getFromDB(key);
if (value != null) {
// 写入各级缓存
redisTemplate.opsForValue().set(key, value, 60, TimeUnit.SECONDS);
localCache.put(key, value);
} else {
// 缓存空值防止缓存穿透
cacheNullValue(key);
}
return value;
} else {
// 等待其他线程加载
Thread.sleep(50);
return redisTemplate.opsForValue().get(key);
}
} finally {
lock.unlock();
}
}
}
3.2 热点数据特殊处理
java
@Component
public class HotspotCacheManager {
// 热点数据本地缓存(更长时间)
private Cache<String, Object> hotspotCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
// 热点Key探测
private ConcurrentHashMap<String, AtomicInteger> keyAccessCounter =
new ConcurrentHashMap<>();
/**
* 热点探测与特殊缓存
*/
public Object getWithHotspotDetection(String key) {
// 访问计数
keyAccessCounter.computeIfAbsent(key, k -> new AtomicInteger(0))
.incrementAndGet();
// 判断是否为热点(例如:10秒内访问超过100次)
if (isHotspotKey(key)) {
// 从热点专用缓存获取
Object value = hotspotCache.getIfPresent(key);
if (value != null) {
return value;
}
// 热点数据预加载和特殊缓存
value = loadHotspotData(key);
hotspotCache.put(key, value);
// 延长分布式缓存时间
redisTemplate.opsForValue().set(key, value, 300, TimeUnit.SECONDS);
return value;
}
// 普通数据走常规流程
return multiLevelCacheService.getWithMultiLevel(key);
}
private boolean isHotspotKey(String key) {
AtomicInteger counter = keyAccessCounter.get(key);
return counter != null && counter.get() > 100;
}
}
3.3 缓存一致性保证
java
@Component
public class CacheConsistencyManager {
@Autowired
private RedisPubSub redisPubSub;
/**
* 缓存更新时的多级同步
*/
@Transactional
public void updateData(String key, Object newValue) {
// 1. 更新数据库
dataService.updateDB(key, newValue);
// 2. 删除各级缓存(先删后更新策略)
deleteMultiLevelCache(key);
// 3. 异步更新缓存
cacheUpdateExecutor.execute(() -> {
// 延迟双删
try {
Thread.sleep(500);
deleteMultiLevelCache(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
private void deleteMultiLevelCache(String key) {
// 删除本地缓存
localCache.invalidate(key);
// 删除分布式缓存
redisTemplate.delete(key);
// 发布缓存失效消息(其他节点监听)
redisPubSub.publish("cache.invalidate", key);
}
/**
* 监听缓存失效消息
*/
@RedisListener(topic = "cache.invalidate")
public void onCacheInvalidate(String key) {
localCache.invalidate(key);
}
}
四、完整的多级缓存实现示例
4.1 缓存管理器
java
@Slf4j
@Component
public class MultiLevelCacheManager {
// 各级缓存配置
@Data
@ConfigurationProperties(prefix = "cache.multi-level")
public static class CacheConfig {
private LocalCacheConfig local = new LocalCacheConfig();
private RedisCacheConfig redis = new RedisCacheConfig();
@Data
public static class LocalCacheConfig {
private int maximumSize = 10000;
private long expireAfterWrite = 5000; // ms
private long expireAfterAccess = 2000; // ms
}
@Data
public static class RedisCacheConfig {
private long defaultExpire = 30000; // ms
private long hotspotExpire = 300000; // ms
private String keyPrefix = "cache:";
}
}
@Autowired
private CacheConfig config;
// 本地缓存实例
private LoadingCache<String, Object> localCache;
@PostConstruct
public void init() {
localCache = Caffeine.newBuilder()
.maximumSize(config.getLocal().getMaximumSize())
.expireAfterWrite(config.getLocal().getExpireAfterWrite(), TimeUnit.MILLISECONDS)
.expireAfterAccess(config.getLocal().getExpireAfterAccess(), TimeUnit.MILLISECONDS)
.recordStats()
.build(key -> loadFromRedis(key));
}
/**
* 核心获取方法
*/
public Object get(String key) {
try {
// 1. 尝试本地缓存
return localCache.get(key);
} catch (Exception e) {
log.warn("Local cache get failed for key: {}", key, e);
// 2. 降级到Redis
try {
Object value = redisTemplate.opsForValue()
.get(config.getRedis().getKeyPrefix() + key);
if (value != null) {
// 异步回填本地缓存
CompletableFuture.runAsync(() ->
localCache.put(key, value));
}
return value;
} catch (Exception ex) {
log.error("Redis cache get failed for key: {}", key, ex);
// 3. 最后尝试数据库
return dataService.getFromDB(key);
}
}
}
/**
* 带降级的批量获取(适用于微博Feed流)
*/
public Map<String, Object> batchGet(List<String> keys) {
Map<String, Object> result = new HashMap<>();
List<String> missingKeys = new ArrayList<>();
// 1. 批量查询本地缓存
for (String key : keys) {
Object value = localCache.getIfPresent(key);
if (value != null) {
result.put(key, value);
} else {
missingKeys.add(key);
}
}
// 2. 批量查询Redis(使用pipeline优化)
if (!missingKeys.isEmpty()) {
List<Object> redisValues = redisTemplate.executePipelined(
connection -> {
for (String key : missingKeys) {
connection.stringCommands()
.get((config.getRedis().getKeyPrefix() + key).getBytes());
}
return null;
});
// 处理Redis结果并回填本地缓存
for (int i = 0; i < missingKeys.size(); i++) {
String key = missingKeys.get(i);
Object value = redisValues.get(i);
if (value != null) {
result.put(key, value);
localCache.put(key, value);
}
}
}
return result;
}
}
4.2 监控与降级
java
@Component
public class CacheMonitor {
@Autowired
private LoadingCache<String, Object> localCache;
@Autowired
private MeterRegistry meterRegistry;
private Gauge localCacheSize;
private Counter cacheHitCounter;
private Counter cacheMissCounter;
@PostConstruct
public void initMetrics() {
// 监控本地缓存指标
localCacheSize = Gauge.builder("cache.local.size",
localCache, cache -> cache.estimatedSize())
.register(meterRegistry);
cacheHitCounter = Counter.builder("cache.hit.total")
.tag("level", "local")
.register(meterRegistry);
cacheMissCounter = Counter.builder("cache.miss.total")
.tag("level", "local")
.register(meterRegistry);
// 定时采集缓存统计
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(this::recordStats, 1, 1, TimeUnit.MINUTES);
}
private void recordStats() {
CacheStats stats = localCache.stats();
Metrics.counter("cache.hit.rate").increment((long)(stats.hitRate() * 100));
Metrics.counter("cache.miss.rate").increment((long)(stats.missRate() * 100));
// 记录命中率到日志
if (stats.hitRate() < 0.8) {
log.warn("Local cache hit rate is low: {}", stats.hitRate());
}
}
/**
* 动态调整缓存策略
*/
@Scheduled(fixedDelay = 60000)
public void adjustCachePolicy() {
CacheStats stats = localCache.stats();
// 根据命中率动态调整
if (stats.hitRate() > 0.9) {
// 命中率高,可以适当增加缓存时间
// ...
} else if (stats.hitRate() < 0.6) {
// 命中率低,可能需要调整缓存策略
// ...
}
}
}
五、配置与部署建议
5.1 application.yml配置
yaml
# 多级缓存配置
cache:
multi-level:
local:
maximum-size: 10000
expire-after-write: 5000
expire-after-access: 2000
redis:
default-expire: 30000
hotspot-expire: 300000
key-prefix: "weibo:cache:"
cluster:
nodes: redis1:6379,redis2:6379,redis3:6379
# 热点检测配置
hotspot:
detection:
enabled: true
threshold: 100 # 10秒内访问次数
window-seconds: 10
preload: true # 是否预加载
# 降级配置
circuit-breaker:
cache:
enabled: true
failure-threshold: 50
timeout-ms: 100
5.2 部署架构建议
┌─────────────────────────────────────────────────────┐
│ Load Balancer (Nginx) │
└─────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────┐ ┌─────┐ ┌─────┐
│App 1│ │App 2│ │App 3│ (Java应用集群)
│L1 │ │L1 │ │L1 │ (本地缓存)
└─────┘ └─────┘ └─────┘
│ │ │
└───────────────┼───────────────┘
│
┌─────────────────────────────────────────────────────┐
│ Redis Cluster (L2缓存) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Redis│ │Redis│ │Redis│ │Redis│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────┐
│ 数据库集群 (MySQL集群) │
│ 主从复制 + 读写分离 │
└─────────────────────────────────────────────────────┘
六、针对微博场景的特殊优化
6.1 明星官宣场景处理
java
@Component
public class CelebrityAnnouncementHandler {
// 预加载机制
@EventListener
public void handleAnnouncementEvent(CelebrityEvent event) {
String celebrityId = event.getCelebrityId();
// 1. 预热缓存
preloadCelebrityData(celebrityId);
// 2. 动态扩容缓存容量
adjustCacheCapacity(celebrityId);
// 3. 设置特殊缓存策略
setSpecialCachePolicy(celebrityId);
}
private void preloadCelebrityData(String celebrityId) {
// 提前加载相关数据到各级缓存
List<String> cacheKeys = generateCacheKeys(celebrityId);
cacheKeys.parallelStream().forEach(key -> {
// 从数据库加载
Object data = dataService.getCelebrityData(key);
// 写入Redis,设置较长TTL
redisTemplate.opsForValue().set(
key, data, 1, TimeUnit.HOURS);
// 推送到消息队列,让其他节点也预热
kafkaTemplate.send("cache-preload", key);
});
}
}
七、总结
关键设计原则:
- 分层设计:L0→L1→L2→L3,逐层降级
- 容量规划:各级缓存容量呈倒金字塔形
- 过期策略:越靠近用户,过期时间越短
- 一致性保障:通过消息同步或延迟双删
- 降级熔断:任何一级缓存失败不影响整体可用性
- 热点探测:动态识别并特殊处理热点数据
性能预期:
- 本地缓存命中:< 1ms
- Redis缓存命中:1-5ms
- 数据库查询:10-100ms
- 整体命中率:> 99%
这种多级缓存设计能有效应对微博明星官宣等突发高并发场景,通过分级存储、热点探测、预加载等策略,保证系统在高并发下的稳定性和性能。