Java三级缓存架构最佳实践:高性能、高可用的完整解决方案
引言
背景介绍
在当今高并发业务场景中,数据库作为持久化存储,其访问延迟往往成为系统性能的瓶颈。根据业界经验,一次数据库查询的耗时通常是缓存查询的 10-100倍。以MySQL为例,一次简单查询耗时约 5-20ms,而Redis只需 0.1-1ms,本地缓存更是低至 纳秒级。
传统的单级缓存方案(仅使用Redis或仅使用本地缓存)存在诸多局限:本地缓存无法跨进程共享,分布式缓存又有网络开销。那么,如何设计一套兼顾性能与一致性的缓存架构?
问题引入
你是否遇到过以下场景?
- 热点数据频繁访问,数据库压力山大
- 缓存失效瞬间,大量请求击穿到数据库
- 分布式缓存故障,整个服务不可用
- 缓存与数据库数据不一致,导致业务异常
本文目标
阅读本文后,你将掌握:
- ✅ 三级缓存架构的设计原理
- ✅ 完整的代码实现方案
- ✅ 缓存三大问题的解决方案
- ✅ 企业级的最佳实践 checklist
目录
一、三级缓存架构概述
1.1 为什么需要三级缓存?
| 缓存级别 | 存储介质 | 访问延迟 | 存储容量 | 作用范围 |
|---|---|---|---|---|
| L1 本地缓存 | JVM堆内存 | 纳秒级 | 有限 | 单进程 |
| L2 分布式缓存 | Redis/Memcached | 毫秒级 | 可扩展 | 跨进程 |
| L3 数据库 | 磁盘 | 十毫秒级 | 无限 | 持久化 |
核心思想:将数据按访问频率和时效性分层存储,让热点数据尽可能靠近CPU,减少网络开销。
1.2 三级缓存读写流程
┌─────────────────────────────────────────────────────────────┐
│ 读流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 请求 ──→ L1本地缓存 ──→ L2分布式缓存 ──→ L3数据库 │
│ │ │ │
│ ▼ ▼ │
│ 命中返回 未命中查询 │
│ │ │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ 回填上级缓存 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 写流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 更新 ──→ L3数据库 ──→ 删除L2缓存 ──→ 删除L1缓存 │
│ │ │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ 异步广播/同步通知 │
│ │
└─────────────────────────────────────────────────────────────┘
1.3 技术选型
| 层级 | 推荐方案 | 特点 |
|---|---|---|
| L1 本地缓存 | Caffeine | 性能极高,支持多种淘汰策略 |
| L1 本地缓存 | Guava Cache | 稳定可靠,文档完善 |
| L2 分布式缓存 | Redis | 丰富的数据结构,集群支持 |
| L2 分布式缓存 | Memcached | 简单高效,协议简单 |
推荐组合:Caffeine + Redis,这是目前业界最主流的方案。
二、核心代码实现
2.1 项目依赖
<!-- Maven pom.xml -->
<dependencies>
<!-- Spring Boot Starter -->
<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>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
2.2 配置类
@Configuration
public class CacheConfig {
@Bean
public Cache<String, Object> localCache() {
return Caffeine.newBuilder()
// 初始容量
.initialCapacity(100)
// 最大容量,超过则淘汰
.maximumSize(10000)
// 写入后过期时间
.expireAfterWrite(10, TimeUnit.MINUTES)
// 访问后过期时间
.expireAfterAccess(5, TimeUnit.MINUTES)
// 监听缓存淘汰
.removalListener((key, value, cause) ->
log.info("缓存淘汰 key={}, cause={}", key, cause))
// 统计
.recordStats()
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key使用String序列化
template.setKeySerializer(new StringRedisSerializer());
// Value使用JSON序列化
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.afterPropertiesSet();
return template;
}
}
2.2 三级缓存服务
@Service
@Slf4j
public class ThreeLevelCacheService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
private static final String REDIS_KEY_PREFIX = "user:";
private static final long REDIS_EXPIRE_TIME = 30;
private static final TimeUnit REDIS_TIME_UNIT = TimeUnit.MINUTES;
/**
* 三级缓存读取
*/
public UserVO getUserById(Long userId) {
String cacheKey = REDIS_KEY_PREFIX + userId;
// L1: 读取本地缓存
UserVO cached = (UserVO) localCache.getIfPresent(cacheKey);
if (cached != null) {
log.debug("L1缓存命中 key={}", cacheKey);
return cached;
}
// L2: 读取Redis缓存
cached = (UserVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
log.debug("L2缓存命中 key={}", cacheKey);
// 回填L1本地缓存
localCache.put(cacheKey, cached);
return cached;
}
// L3: 查询数据库
log.debug("缓存未命中,查询数据库 userId={}", userId);
User user = userMapper.selectById(userId);
if (user == null) {
return null;
}
UserVO userVO = convertToVO(user);
// 依次回填L2和L1缓存
redisTemplate.opsForValue().set(cacheKey, userVO, REDIS_EXPIRE_TIME, REDIS_TIME_UNIT);
localCache.put(cacheKey, userVO);
return userVO;
}
/**
* 三级缓存写入(更新)
*/
@Transactional(rollbackFor = Exception.class)
public void updateUser(UserUpdateDTO dto) {
Long userId = dto.getId();
// 1. 更新数据库
User user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
BeanUtils.copyProperties(dto, user);
userMapper.updateById(user);
// 2. 删除缓存(注意顺序:先删缓存,再删本地)
String cacheKey = REDIS_KEY_PREFIX + userId;
// 删除Redis
redisTemplate.delete(cacheKey);
log.info("删除Redis缓存 key={}", cacheKey);
// 删除本地缓存
localCache.invalidate(cacheKey);
log.info("删除本地缓存 key={}", cacheKey);
}
/**
* 三级缓存删除
*/
public void deleteUser(Long userId) {
// 删除数据库
userMapper.deleteById(userId);
// 删除缓存
String cacheKey = REDIS_KEY_PREFIX + userId;
redisTemplate.delete(cacheKey);
localCache.invalidate(cacheKey);
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
2.3 缓存注解实现
为了简化使用,我们可以定义一套缓存注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String key() default "";
int localExpire() default 10;
int redisExpire() default 30;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvict {
String key() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CachePut {
String key() default "";
int localExpire() default 10;
int redisExpire() default 30;
}
@Aspect
@Component
@Slf4j
public class CacheAspect {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(cacheable)")
public Object cacheable(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
String key = resolveKey(pjp, cacheable.key());
// L1
Object result = localCache.getIfPresent(key);
if (result != null) {
return result;
}
// L2
result = redisTemplate.opsForValue().get(key);
if (result != null) {
localCache.put(key, result);
return result;
}
// L3
result = pjp.proceed();
if (result != null) {
localCache.put(key, result);
redisTemplate.opsForValue().set(key, result,
cacheable.redisExpire(), TimeUnit.MINUTES);
}
return result;
}
@Around("@annotation(cacheEvict)")
public Object cacheEvict(ProceedingJoinPoint pjp, CacheEvict cacheEvict) throws Throwable {
String key = resolveKey(pjp, cacheEvict.key());
Object result = pjp.proceed();
// 删除缓存
localCache.invalidate(key);
redisTemplate.delete(key);
return result;
}
private String resolveKey(ProceedingJoinPoint pjp, String keyExpression) {
if (StringUtils.isEmpty(keyExpression)) {
return pjp.getTarget().getClass().getSimpleName() + ":" +
pjp.getSignature().getName();
}
// 解析SpEL表达式
return keyExpression;
}
}
2.4 使用示例
@Service
public class UserService {
@Autowired
private ThreeLevelCacheService cacheService;
@Cacheable(key = "'user:' + #userId")
public UserVO getUserById(Long userId) {
return cacheService.getUserById(userId);
}
@CacheEvict(key = "'user:' + #dto.id")
public void updateUser(UserUpdateDTO dto) {
cacheService.updateUser(dto);
}
@CacheEvict(key = "'user:' + #userId")
public void deleteUser(Long userId) {
cacheService.deleteUser(userId);
}
}
三、数据一致性方案
3.1 缓存一致性分析
在三级缓存架构中,数据一致性是一个核心问题。常见的一致性问题包括:
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 读操作 | 缓存未命中时并发查询数据库 | 分布式锁 + 本地锁 |
| 写操作 | 缓存未及时更新 | 延迟双删 |
| 更新操作 | 缓存与数据库不一致 | 写穿/写回策略 |
3.2 延迟双删策略
延迟双删是业界最常用的方案,适合读多写少的场景:
@Service
@Slf4j
public class CacheWithDelayDeleteService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private Cache<String, Object> localCache;
@Autowired
private UserMapper userMapper;
private static final long DELETE_DELAY_MS = 500;
/**
* 更新用户(延迟双删)
*/
@Transactional(rollbackFor = Exception.class)
public void updateUser(UserUpdateDTO dto) {
Long userId = dto.getId();
String cacheKey = "user:" + userId;
// 1. 先删除缓存
redisTemplate.delete(cacheKey);
localCache.invalidate(cacheKey);
log.info("第一次删除缓存 key={}", cacheKey);
// 2. 更新数据库
User user = userMapper.selectById(userId);
BeanUtils.copyProperties(dto, user);
userMapper.updateById(user);
log.info("更新数据库 userId={}", userId);
// 3. 延迟再删除缓存(异步)
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(DELETE_DELAY_MS);
redisTemplate.delete(cacheKey);
localCache.invalidate(cacheKey);
log.info("延迟删除缓存 key={}", cacheKey);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
3.3 分布式锁防击穿
当缓存失效瞬间,大量并发请求会击穿到数据库,造成雪崩效应。使用分布式锁可以有效解决这个问题:
@Service
@Slf4j
public class CacheWithLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private Cache<String, Object> localCache;
@Autowired
private UserMapper userMapper;
private static final String LOCK_PREFIX = "lock:";
private static final long LOCK_EXPIRE_TIME = 10;
private static final TimeUnit LOCK_TIME_UNIT = TimeUnit.SECONDS;
/**
* 使用分布式锁防止缓存击穿
*/
public UserVO getUserByIdWithLock(Long userId) {
String cacheKey = "user:" + userId;
String lockKey = LOCK_PREFIX + cacheKey;
// L1: 本地缓存
UserVO cached = (UserVO) localCache.getIfPresent(cacheKey);
if (cached != null) {
return cached;
}
// L2: Redis缓存
cached = (UserVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
localCache.put(cacheKey, cached);
return cached;
}
// 获取分布式锁
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", LOCK_EXPIRE_TIME, LOCK_TIME_UNIT);
if (Boolean.TRUE.equals(acquired)) {
try {
// 双重检查
cached = (UserVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
localCache.put(cacheKey, cached);
return cached;
}
// 查询数据库
User user = userMapper.selectById(userId);
if (user != null) {
UserVO userVO = convertToVO(user);
redisTemplate.opsForValue().set(cacheKey, userVO, 30, TimeUnit.MINUTES);
localCache.put(cacheKey, userVO);
return userVO;
}
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 未获取到锁,短暂等待后重试
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 递归重试(注意递归深度)
return getUserByIdWithLock(userId);
}
return null;
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
3.4 订阅Redis过期事件
当分布式缓存过期时,其他实例需要同步清除本地缓存:
@Component
@Slf4j
public class RedisKeyExpirationListener
implements KeyExpirationEventMessageListener {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private ApplicationEventPublisher eventPublisher;
public RedisKeyExpirationListener(RedisConnectionFactory connectionFactory) {
super(new RedisMessageListenerContainer(connectionFactory));
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = new String(message.getBody());
log.info("Redis key过期 key={}", expiredKey);
// 清除本地缓存
if (expiredKey.startsWith("user:")) {
localCache.invalidate(expiredKey);
log.info("清除本地缓存 key={}", expiredKey);
}
// 发布缓存失效事件
eventPublisher.publishEvent(new CacheExpiredEvent(expiredKey));
}
}
四、缓存三大问题解决方案
4.1 缓存穿透
问题:查询一个不存在的数据,由于缓存和数据库都不存在,每次请求都会击穿到数据库。
解决方案:
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 空值缓存 | 将在数据库查不到的结果也缓存起来 | 查询结果为空的情况较多 |
| 布隆过滤器 | 用位数组判断数据是否存在 | 数据量极大,查询不存在占多数 |
| 参数校验 | 提前校验非法参数 | 恶意攻击 |
实现代码:
@Service
@Slf4j
public class CachePenetrationService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private BloomFilter<String> bloomFilter;
/**
* 空值缓存 + 布隆过滤器防穿透
*/
public UserVO getUserById(Long userId) {
if (userId == null || userId <= 0) {
throw new IllegalArgumentException("无效的用户ID");
}
String cacheKey = "user:" + userId;
// 布隆过滤器检查
if (!bloomFilter.mightContain(cacheKey)) {
log.warn("布隆过滤器判定不存在,直接返回null userId={}", userId);
return null;
}
// L1: 本地缓存
UserVO cached = (UserVO) localCache.getIfPresent(cacheKey);
if (cached != null) {
return cached;
}
// L2: Redis
cached = (UserVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
localCache.put(cacheKey, cached);
return cached;
}
// L3: 数据库
User user = userMapper.selectById(userId);
if (user == null) {
// 空值缓存(设置较短过期时间)
log.info("缓存空值 userId={}", userId);
redisTemplate.opsForValue().set(cacheKey,
new UserVO(), 5, TimeUnit.MINUTES);
} else {
UserVO userVO = convertToVO(user);
redisTemplate.opsForValue().set(cacheKey,
userVO, 30, TimeUnit.MINUTES);
localCache.put(cacheKey, userVO);
}
return user != null ? convertToVO(user) : null;
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
4.2 缓存击穿
问题:某个热点key过期瞬间,大量并发请求同时击穿到数据库。
解决方案:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 分布式锁 | 只允许一个请求查数据库 | 一致性好 | 性能有损耗 |
| 永不过期 | 不设置过期时间,用后台任务更新 | 性能高 | 一致性稍差 |
| 逻辑过期 | 返回过期数据,后台异步更新 | 体验好 | 实现复杂 |
推荐实现(分布式锁):
见 3.3 节的 CacheWithLockService.getUserByIdWithLock() 方法。
4.3 缓存雪崩
问题:大量缓存同一时间过期,或者缓存服务故障,导致大量请求击穿到**:
| 数据库。
| **解决方案方案 | 原理 |
|---|---|
| 随机过期时间 | 给缓存过期时间加随机值 |
| 预热 | 系统启动时预加载热点数据 |
| 多级缓存 | 本地缓存作为缓冲 |
| 熔断降级 | 缓存故障时返回默认值 |
实现代码:
@Component
public class CacheWarmingService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
/**
* 缓存预热 - 系统启动时执行
*/
@PostConstruct
public void warmUpCache() {
log.info("开始缓存预热...");
// 预加载热点用户(可以根据业务调整)
List<Long> hotUserIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);
for (Long userId : hotUserIds) {
try {
User user = userMapper.selectById(userId);
if (user != null) {
String cacheKey = "user:" + userId;
UserVO userVO = convertToVO(user);
// 使用随机过期时间(30-60分钟)
int expireMinutes = 30 + new Random().nextInt(31);
redisTemplate.opsForValue().set(cacheKey,
userVO, expireMinutes, TimeUnit.MINUTES);
localCache.put(cacheKey, userVO);
}
} catch (Exception e) {
log.error("缓存预热失败 userId={}", userId, e);
}
}
log.info("缓存预热完成");
}
/**
* 随机过期时间工具
*/
public long getRandomExpireTime(long baseExpireTime) {
// 基础时间 ± 20% 随机波动
double random = Math.random();
long variance = (long) (baseExpireTime * 0.2 * random);
if (random > 0.5) {
return baseExpireTime + variance;
} else {
return baseExpireTime - variance;
}
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
4.4 熔断降级
@Component
@Slf4j
public class CacheFallbackService {
@Autowired
private Cache<String, Object> localCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
private volatile boolean redisAvailable = true;
/**
* 带降级的缓存查询
*/
public UserVO getUserWithFallback(Long userId) {
String cacheKey = "user:" + userId;
try {
// L1: 本地缓存(永远可用)
UserVO cached = (UserVO) localCache.getIfPresent(cacheKey);
if (cached != null) {
return cached;
}
// L2: Redis
if (redisAvailable) {
try {
cached = (UserVO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
localCache.put(cacheKey, cached);
return cached;
}
} catch (Exception e) {
log.error("Redis查询失败,降级到数据库", e);
redisAvailable = false;
}
}
// L3: 数据库(最终保障)
User user = userMapper.selectById(userId);
if (user != null) {
UserVO userVO = convertToVO(user);
// 异步回填缓存
fillCacheAsync(cacheKey, userVO);
return userVO;
}
} catch (Exception e) {
log.error("缓存服务异常,直接查询数据库", e);
}
return null;
}
@Scheduled(fixedRate = 30000)
public void checkRedisHealth() {
try {
redisTemplate.getConnectionFactory().getConnection().ping();
redisAvailable = true;
log.info("Redis连接恢复");
} catch (Exception e) {
redisAvailable = false;
log.warn("Redis不可用,进入降级模式");
}
}
private void fillCacheAsync(String cacheKey, UserVO userVO) {
CompletableFuture.runAsync(() -> {
try {
redisTemplate.opsForValue().set(cacheKey, userVO, 30, TimeUnit.MINUTES);
localCache.put(cacheKey, userVO);
} catch (Exception e) {
log.error("异步回填缓存失败", e);
}
});
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
return vo;
}
}
五、最佳实践 checklist
5.1 架构设计 checklist
- 缓存层次设计:是否明确各层缓存的职责
- 容量规划:本地缓存大小、Redis内存是否合理
- 过期策略:是否根据数据特性设置合理的过期时间
- 监控告警:是否监控缓存命中率、内存使用率
5.2 代码实现 checklist
// ✅ 正确示范
public UserVO getUserById(Long userId) {
String key = "user:" + userId;
// 1. 先查本地缓存
UserVO result = localCache.getIfPresent(key);
if (result != null) {
return result;
}
// 2. 再查Redis
result = (UserVO) redisTemplate.opsForValue().get(key);
if (result != null) {
localCache.put(key, result);
return result;
}
// 3. 最后查数据库
result = userMapper.selectById(userId);
if (result != null) {
redisTemplate.opsForValue().set(key, result, 30, TimeUnit.MINUTES);
localCache.put(key, result);
}
return result;
}
// ❌ 错误示范(常见问题)
public UserVO getUserByIdWrong(Long userId) {
String key = "user:" + userId;
// 直接查Redis,没有本地缓存
UserVO result = (UserVO) redisTemplate.opsForValue().get(key);
if (result == null) {
// 没有加锁,可能击穿
result = userMapper.selectById(userId);
}
return result;
}
5.3 一致性 checklist
- 更新策略:采用延迟双删还是其他方案
- 并发控制:是否使用分布式锁防止击穿
- 本地缓存同步:是否订阅Redis过期事件
5.4 运维 checklist
| 检查项 | 推荐值 | 告警阈值 |
|---|---|---|
| 缓存命中率 | > 90% | < 70% |
| 本地缓存内存 | < 512MB | > 1GB |
| Redis内存 | < 70% | > 85% |
| 慢查询 | < 10ms | > 100ms |
5.5 完整配置模板
# application.yml
spring:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 200
max-idle: 50
min-idle: 10
max-wait: 3000ms
# 缓存配置
cache:
local:
initial-capacity: 100
maximum-size: 10000
expire-after-write: 10
expire-after-access: 5
redis:
expire-after-write: 30
bloom-filter:
expected-insertions: 100000
fpp: 0.001
# 告警配置
alert:
cache-hit-rate-threshold: 0.7
redis-memory-threshold: 0.85
slow-query-threshold: 100
总结
要点回顾
-
三级缓存架构:L1本地缓存(纳秒级)+ L2分布式缓存(毫秒级)+ L3数据库,层层递进,平衡性能与一致性
-
核心实现:使用Caffeine作为本地缓存,Redis作为分布式缓存,通过统一服务封装提供透明访问
-
数据一致性:延迟双删 + 分布式锁 + Redis过期事件订阅
-
三大问题:
- 穿透:布隆过滤器 + 空值缓存
- 击穿:分布式锁 + 逻辑过期
- 雪崩:随机过期 + 预热 + 熔断降级
行动号召
- 欢迎在评论区分享你的缓存设计方案
- 如果本文对你有帮助,欢迎点赞收藏
- 关注我,获取更多Java性能优化实战
下期预告
下期我们将介绍 「分布式锁深度实战:从Redis到Redisson的完整方案」,敬请期待!
767



被折叠的 条评论
为什么被折叠?



