缓存穿透问题
大量请求查询不存在的数据,绕过缓存直接访问数据库,导致数据库压力过大。
解决方案:
使用布隆过滤器(Bloom Filter)预先过滤无效请求,避免查询不存在的数据。
对空结果进行短时间缓存,减少重复无效查询。
Java实现布隆过滤器
使用Guava库的BloomFilter类可以高效过滤无效请求,以下是完整代码示例。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class RequestFilter {
private static final int EXPECTED_INSERTIONS = 1000;
private static final double FALSE_POSITIVE_PROB = 0.01;
private BloomFilter<String> filter = BloomFilter.create(
Funnels.unencodedCharsFunnel(),
EXPECTED_INSERTIONS,
FALSE_POSITIVE_PROB
);
public void addValidKey(String key) {
filter.put(key);
}
public boolean isInvalidRequest(String key) {
return !filter.mightContain(key);
}
}
使用示例
初始化过滤器并添加合法请求标识。
RequestFilter filter = new RequestFilter();
filter.addValidKey("valid_token_123");
filter.addValidKey("valid_session_456");
拦截无效请求逻辑。
public ResponseEntity handleRequest(String requestToken) {
if (filter.isInvalidRequest(requestToken)) {
return ResponseEntity.status(403).build();
}
// 正常业务逻辑
}
参数调优建议
初始容量应设置为预估请求量的1-1.5倍,误判率根据业务需求调整。
// 高精度场景(金融业务)
BloomFilter.create(Funnels.stringFunnel(), 100000, 0.001);
// 普通场景(内容缓存)
BloomFilter.create(Funnels.stringFunnel(), 500000, 0.03);
性能注意事项
布隆过滤器内存占用计算公式为:
$$m = -\frac{n \ln p}{(\ln 2)^2}$$
其中n为元素数量,p为误判率。实际生产环境建议配合Redis实现分布式过滤。
空值缓存策略
对查询结果为null的键设置短时间缓存,避免重复查询数据库。需注意缓存过期时间不宜过长。
public Object getData(String key) {
Object value = cache.get(key);
if (value != null) {
return "NULL".equals(value) ? null : value; // 处理空值标记
}
value = database.query(key);
if (value == null) {
cache.set(key, "NULL", 60); // 空值缓存60秒
} else {
cache.set(key, value, 3600); // 正常数据缓存1小时
}
return value;
}
缓存击穿问题
热点数据失效时,大量并发请求直接冲击数据库。
解决方案:
使用互斥锁(Mutex Lock)或分布式锁(如Redis SETNX)防止多个请求同时重建缓存。
设置逻辑过期时间,异步更新缓存而非直接失效。
互斥锁
使用本地锁(如ReentrantLock)或分布式锁(如Redis SETNX)确保只有一个线程能重建缓存。其他线程等待锁释放后直接读取新缓存。
// 伪代码示例:Redis分布式锁实现
public String getData(String key) {
String data = redis.get(key);
if (data == null) {
if (redis.setnx(key + "_lock", "1", 10)) { // 获取锁
try {
data = db.query(key); // 查数据库
redis.set(key, data, 30); // 写缓存
} finally {
redis.delete(key + "_lock"); // 释放锁
}
} else {
Thread.sleep(100); // 重试等待
return getData(key); // 递归调用
}
}
return data;
}
public Object getDataWithLock(String key) {
Object value = cache.get(key);
if (value != null) {
return "NULL".equals(value) ? null : value;
}
// 获取分布式锁
String lockKey = "lock:" + key;
try {
if (lock.tryLock(lockKey, 3, TimeUnit.SECONDS)) {
try {
value = database.query(key);
if (value == null) {
cache.set(key, "NULL", 60);
} else {
cache.set(key, value, 3600);
}
} finally {
lock.unlock(lockKey);
}
} else {
Thread.sleep(100); // 未获取到锁时短暂等待
return getDataWithLock(key); // 重试
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return value;
}
逻辑过期时间
缓存数据永不过期,但存储额外字段(如expireTime)。异步线程定期检查并更新临近过期的数据。
// 逻辑过期结构示例
class CacheData {
Object value;
long expireTime; // 逻辑过期时间戳
}
// 异步更新流程
if (System.currentTimeMillis() > cacheData.expireTime) {
executor.submit(() -> {
updateCache(key); // 后台更新
});
}
缓存雪崩问题
大量缓存同时失效,导致请求全部转向数据库。
解决方案:
分散缓存过期时间,避免同时失效(如基础时间+随机偏移)。
采用多级缓存架构(如本地缓存+Redis)。
降级策略:热点数据永不过期,后台异步更新。
分散缓存过期时间
为缓存键设置基础过期时间并添加随机偏移值,避免同时失效。例如采用基础时间(如24小时)加上0-2小时的随机偏移:
// 设置缓存过期时间为24小时 + 随机0~2小时
int baseExpire = 24 * 3600;
int randomExpire = (int)(Math.random() * 7200);
redisTemplate.opsForValue().set(key, value, baseExpire + randomExpire, TimeUnit.SECONDS);
多级缓存架构
构建本地缓存(如Caffeine)与分布式缓存(如Redis)的多级屏障:
// 多级缓存示例:Caffeine + Redis
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager localCache = new CaffeineCacheManager();
localCache.setCaffeine(Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES));
RedisCacheManager redisCache = RedisCacheManager.create(redisConnectionFactory);
return new MultiLevelCacheManager(localCache, redisCache);
}
热点数据永不过期
对高频访问数据设置逻辑过期时间,后台线程定期更新:
// 逻辑过期实现
public Object getHotData(String key) {
ValueWrapper wrapper = cache.get(key);
if (wrapper == null) {
return loadFromDBAndSetCache(key);
}
CacheItem item = (CacheItem)wrapper.get();
if (item.isExpired()) {
executorService.submit(() -> refreshCache(key));
}
return item.getValue();
}
熔断降级机制
引入Hystrix或Sentinel实现系统保护:
// Sentinel规则配置
@PostConstruct
public void initRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule("cacheQuery")
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(1000); // 阈值QPS
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
缓存预热与更新策略
系统启动时加载高频数据,采用发布订阅模式同步更新:
// Redis消息订阅
@Bean
RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.addMessageListener(listenerAdapter, new PatternTopic("cache.update"));
return container;
}
数据一致性挑战
更新数据库后缓存未同步或延迟。
解决方案:
双删策略:先删缓存再更新数据库,延迟后再删一次缓存。
订阅数据库binlog(如Canal)触发缓存更新。
详细见:缓存一致性解决方案
内存管理与淘汰策略
分析Redis内存不足时的表现和常见策略(LRU、LFU、TTL等)。
原因:当Redis内存达到maxmemory限制时,会根据配置的策略淘汰数据
淘汰策略
- noeviction(默认):拒绝所有写入操作(返回错误),读操作正常。适用于不允许数据丢失的场景,但需谨慎内存监控。
- allkeys-lru:从所有key中使用近似LRU算法淘汰最近最少使用的key。适合缓存场景,尤其是热点数据分布不均的情况。
- volatile-lru:仅从设置了过期时间的key中淘汰最近最少(时间)使用的key。需确保未设置过期时间的key允许常驻内存。
- allkeys-random:随机淘汰所有key。适用于数据访问模式无规律的情况。
- volatile-random:随机淘汰设置了过期时间的key。
- volatile-ttl:优先淘汰剩余存活时间(TTL)最短的key。适合希望快速清理短期数据的场景。
- allkeys-lfu(Redis 4.0+):从所有key中使用近似LFU算法淘汰(访问频率)最低的key。适合长期热点数据。
- volatile-lfu(Redis 4.0+):仅淘汰设置了过期时间且访问频率最低的key。
优化建议:
根据业务场景选择合适的淘汰策略(如volatile-lru)。
监控内存碎片率,定期执行MEMORY PURGE。
选取方案
- 数据特性
-
- 数据短期有效:【volatile-ttl】
-
- 数据重要性
- 允许丢失部分数据:【all-keys-*】
- 仅允许淘汰带过期时间的数据:【volatile-*】
- 访问模式
- 突发流量且无规律:【allkeys-random】
- 长期高频访问:【allkeys-lfu】
分布式缓存问题
讨论集群环境下缓存同步、分片及高可用设计。
解决方案:
使用Redis Cluster或Codis实现分片存储。
通过哨兵(Sentinel)或Raft协议保障高可用。
集群环境下的缓存同步
分布式缓存同步通常采用最终一致性模型。Redis Cluster使用Gossip协议进行节点间状态同步,Codis通过Proxy层协调数据同步。同步延迟可通过以下手段优化:
- 异步复制结合冲突解决策略
- 写操作日志(WAL)持久化后进行传播
- 批量同步代替实时同步降低网络开销
// Jedis集群模式下的写操作示例
JedisCluster jedis = new JedisCluster(nodes);
jedis.set("global_key", "value"); // 自动路由到正确分片
数据分片策略
一致性哈希是分布式缓存常见分片方案,Java生态常用实现方式:
Redis Cluster方案:
- 16384个虚拟槽位(slot)均匀分布
- 客户端直接参与路由计算
- CRC16算法保证键分布均匀
Codis方案:
- Proxy层维护slot映射表
- 支持动态迁移无需客户端感知
- ZooKeeper/Etcd存储元数据
// Redisson分片配置示例
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://127.0.0.1:7001")
.setScanInterval(2000); // 集群状态扫描间隔
高可用保障机制
哨兵模式部署要点:
- 奇数个Sentinel节点组成监控网络
- 故障转移时执行客观下线判定
- 新主节点选举考虑复制偏移量
Raft协议实现要点:
- 选举超时时间随机化避免冲突
- 日志复制要求多数节点确认
- 成员变更采用联合共识算法
// Lettuce连接哨兵集群示例
RedisURI uri = RedisURI.Builder.sentinel("sentinel-host", 26379, "master-name")
.withSentinel("sentinel2-host", 26379)
.build();
RedisClient client = RedisClient.create(uri);
StatefulRedisConnection<String, String> connection = client.connect();
性能优化实践
- 本地缓存结合分布式缓存的多级缓存架构
- 热点数据预加载与动态分片调整
- 批量管道操作减少网络往返
- 客户端连接池合理配置
// 使用Redisson实现多级缓存
Map<String, RLocalCachedMap<String, Object>> caches = new HashMap<>();
RLocalCachedMap<String, Object> localMap = redisson.getLocalCachedMap(
"distributed_cache",
LocalCachedMapOptions.defaults()
.cacheProvider(CacheProvider.CAFFEINE)
.evictionPolicy(EvictionPolicy.LRU)
);
监控与运维关键点
- 实时监控缓存命中率/网络延迟等核心指标
- 慢查询日志分析与热点键处理
- 容量规划时考虑数据倾斜问题
- 定期执行集群重新平衡操作
分布式系统的CAP理论在实际应用中需要根据业务场景权衡,金融类系统优先保证一致性,互联网高并发场景往往选择可用性优先策略。
监控与运维实践
关键指标:缓存命中率、延迟、内存使用率。
工具推荐:Redis-stat、Prometheus+Grafana监控。
缓存命中率
缓存命中率是衡量缓存系统效能的核心指标,计算公式为 (缓存命中次数 / 总请求次数) * 100
。高命中率(如90%以上)表明缓存策略有效,低命中率可能需调整缓存策略(如LRU优化或预热机制)。
// 示例:使用Caffeine库统计命中率
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.recordStats()
.build();
cache.get("key", k -> "value");
CacheStats stats = cache.stats();
double hitRate = stats.hitRate(); // 获取命中率
延迟监控
延迟包括平均响应时间(Avg)和百分位延迟(P99)。Redis可通过redis-cli --latency
测试基准延迟,Prometheus抓取应用埋点数据。
// 使用Micrometer监控Redis延迟
@Timed(value = "redis.command.latency", histogram = true)
public String executeRedisCommand() {
// Redis操作代码
}
内存使用率
Redis内存需关注used_memory
和maxmemory
,建议设置报警阈值(如80%)。可通过INFO memory
命令获取数据。
# PromQL内存使用率告警规则
ALERT HighRedisMemoryUsage
IF redis_memory_used_bytes / redis_memory_max_bytes > 0.8
FOR 5m
工具集成方案
-
Redis-stat
实时命令行监控:redis-stat --server
启动Web界面,展示命中率、内存等关键指标。 -
Prometheus+Grafana
- 配置
redis_exporter
采集Redis数据 - Grafana仪表盘导入模板ID
763
(官方Redis监控看板)
- 配置
# Prometheus配置示例
scrape_configs:
- job_name: 'redis'
static_configs:
- targets: ['redis_exporter:9121']
感兴趣的小伙伴们请关注我,还会带来更多知识经验分享~~~