Java三级缓存架构最佳实践:高性能、高可用的完整解决方案

Mybatis缓存机制(一级缓存、二级缓存、三级缓存) Mybatis 一级缓存、二级缓存、三级缓存 阅读详情

Java三级缓存架构最佳实践:高性能、高可用的完整解决方案

引言

背景介绍

在当今高并发业务场景中,数据库作为持久化存储,其访问延迟往往成为系统性能的瓶颈。根据业界经验,一次数据库查询的耗时通常是缓存查询的 10-100倍。以MySQL为例,一次简单查询耗时约 5-20ms,而Redis只需 0.1-1ms,本地缓存更是低至 纳秒级。

传统的单级缓存方案(仅使用Redis或仅使用本地缓存)存在诸多局限:本地缓存无法跨进程共享,分布式缓存又有网络开销。那么,如何设计一套兼顾性能与一致性的缓存架构?

问题引入

你是否遇到过以下场景?

  • 热点数据频繁访问,数据库压力山大
  • 缓存失效瞬间,大量请求击穿到数据库
  • 分布式缓存故障,整个服务不可用
  • 缓存与数据库数据不一致,导致业务异常

本文目标

阅读本文后,你将掌握:

  1. ✅ 三级缓存架构的设计原理
  2. ✅ 完整的代码实现方案
  3. ✅ 缓存三大问题的解决方案
  4. ✅ 企业级的最佳实践 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

总结

要点回顾

  1. 三级缓存架构:L1本地缓存(纳秒级)+ L2分布式缓存(毫秒级)+ L3数据库,层层递进,平衡性能与一致性

  2. 核心实现:使用Caffeine作为本地缓存,Redis作为分布式缓存,通过统一服务封装提供透明访问

  3. 数据一致性:延迟双删 + 分布式锁 + Redis过期事件订阅

  4. 三大问题:

    • 穿透:布隆过滤器 + 空值缓存
    • 击穿:分布式锁 + 逻辑过期
    • 雪崩:随机过期 + 预热 + 熔断降级

行动号召

  • 欢迎在评论区分享你的缓存设计方案
  • 如果本文对你有帮助,欢迎点赞收藏
  • 关注我,获取更多Java性能优化实战

下期预告

下期我们将介绍 「分布式锁深度实战:从Redis到Redisson的完整方案」,敬请期待!


附录

参考资料

  1. Caffeine官方文档
  2. Redis缓存设计最佳实践
  3. Java三级缓存解决循环依赖 - Spring源码解析
  4. 高并发场景下三级缓存的数据一致性
亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 课程基于真实的每日上亿流量的大型电商网站中的商品详情页系统,作为项目实战。详细讲解如何实现一个复杂的缓存系统架构,去直接支撑电商背景下的高并发与高性能的访问,同时基于缓存架构本身所处的复杂分布式系统架构环境下,如何设计与实现一个高可用的分布式系统架构。 阅读详情

相关推荐

JAVA:Spring Bean 三级缓存机制的技术指南

JAVA:Spring Bean 三级缓存机制的技术指南

木头 767

java 三级缓存

就是把访问量较高的热点数据从传统的关系型数据库中加载到内存中,当用户再次访问热点数据时是从内存中加载,减少了对数据库的访问量,解决了高并发场景下容易造成数据库宕机的问题。

林在闪闪发光的博客 3610

解决uc浏览器劫持video标签问题

解决uc视频悬浮播放 ,video标签被浏览器劫持 ,vide0适配

smile_y_y的博客 3449

三级缓存的理解

三级缓存的理解

beiback的博客 4101

Zotero+WPS文献引用跳转功能缺失?3分钟教你用VBA宏搞定(附完整代码)

本文针对WPS用户使用Zotero时文献引用无法点击跳转的痛点,提供了一套完整的VBA宏解决方案。通过解析Zotero的域代码,自动在文内引用和文末参考文献之间创建超链接,实现一键跳转,极大提升了学术写作与文献查阅的效率。

weixin_28419039的博客 671

瑞萨单片机,把CS文件转换为CC

1、打开CC的软件: 点击坐上角的"START" 点击"Create New Project"的"GO": 导入工程: 如下图设置后,点击"Create" 2、进入到CC的开发界面: 可以看到原启动库文件以asm文件的方式直接出现在工程中: 删除新生成的main.c,保留导入的r_main.c(以你自己的代码为准): 注意添加所有的头文件路径: 点击"...

weixin_37611121的博客 1228

CPU 与 内存之间的三级缓存的实现原理

一. CPU 与 Memory 内存之间的三级缓存的实现原理 1.1 cache 存在的原理   引入 Cache 的理论基础是程序局部性原理,包括时间局部性和空间局部性。时间局部性原理即最近被CPU访问的数据,短期内CPU 还要访问(时间);空间局部性即被CPU访问的数据附近的数据,CPU短期内还要访问(空间)。因此如果将刚刚访问过的数据缓存在一个速度比主存快得多的存储中,那下次访问时,可以直接从这个存储中取,其速度可以得到数量级的提高。   CPU缓存是(Cache Memory)位于CPU与内存

首席技术总监的专栏 8907

源码编译部署篇(二)源码编译milvus成功后如何启动standalone并调试成功!

由于Milvus官方文档只提及如何使用docker安装部署Milvus,关于源码安装方式篇幅较少,因此,我整理撰写了如何通过源码编译方式成功安装Milvus,详见此篇。之所以选择这种复杂的安装方式,可以调试源码进而熟悉Milvus如何搭建整个框架支持AI上层应用功能。根据上篇步骤部署虽然可以成功,然而,我却不能成功启动Milvus服务,这个问题困扰了一两天,现在终于得以解决并能成功调试Milvus源码。接下来,分为三个部分介绍如何启动和成功调试。

qq_43893755的博客 3368

三高系统的架构设计方案:高并发、高可用、高性能

高并发、高性能、高可用,它们是互联网系统架构设计永恒的主题。三高并不是孤立的,而是相互支撑,相互影响的,随着并发量的提高,请求延迟肯定会增大,就越考验系统的可用性和性能。三高方案不是简单的一字一句就能说明白的,是需要日积月累,一步步踩坑踩出来的。只不过现在有一些大厂的案例,供我们来参考,实际落地的时候,还需要根据各个公司不同场景进行不同的设计。

秃了也弱了 9116

RT_Thread Studio使用——USB虚拟串口(VCP)

RT_Thread Studio使用——USB虚拟串口(VCP)

weixin_44735205的博客 3374

欧姆龙485通讯示例程序_【精品实验】PLC学习神器与温湿度变送器的通讯

BFH点击蓝字关注我们↑↑↑↑  PLC学习神器提供RS485接口,可支持无协议RS指令、无协议RS2指令、Modbus通讯功能,可通过设置通讯格式改变通讯协议,RS485对应的通讯格式地址是D8400;  本案例以RS485接口与温湿度变送器通讯,通讯格式如下一温湿度变送器的说明书介绍1.1主要技术参数1.2 通讯协议 1、所有通讯回路都应遵照主/从方式。依照主从方式,数据可以在一个主...

weixin_39860201的博客 1万+

高并发、高性能、高可用的架构设计

本文探讨了构建高并发、高性能、高可用系统架构的关键技术。高并发设计包括负载均衡策略(轮询、加权轮询等)、分布式架构(微服务、数据分片)和缓存机制(Redis等);高性能优化涉及瓶颈定位、异步处理(消息队列)和并行计算(线程池);高可用方案则通过主从复制、容灾备份和健康检查实现。文章结合Java代码示例,展示了如何运用这些技术解决大规模互联网应用面临的系统稳定性、响应速度和容错能力等挑战。

一碗黄焖鸡三碗米饭的博客 1149

Python的单元测试之unittest

是一个功能类似 unittest 的库,它集合众家标准测试库之所长,和 unittest 相比,功能更为强大,使用更为简单,OpenStack 广泛的利用它编写单元测试。unittest 提供了自动匹配发现并执行测试用例的功能,随着项目代码结构越发庞大,势必有多个测试文件,自动匹配发现并测试用例的功能在此就显得非常有用,只要满足。unittest 提供了丰富的命令行入口,可以根据需要执行某些特定的用例。的测试用例都会被 unittest 发现并执行,测试用例文件的默认匹配规则为 test*.py。

1、测试博主技术知识分享 2、pos行业知识技术分享 3、java后端技术知识分享 4、嵌入式技术知识分享 5、运维部署相关知识分享 283

Java 架构 19:缓存架构设计(本地缓存 + 分布式缓存)

本文探讨了Java应用中本地缓存与分布式缓存的协同架构设计。通过Caffeine实现高性能本地缓存(微秒级响应),结合Redis构建分布式共享缓存,形成多级缓存防御体系。文章详细介绍了: 缓存价值:降低数据库压力,提升系统吞吐(案例显示性能提升25倍) 架构设计:本地缓存(L1)处理80%请求,Redis(L2)处理19%,数据库仅1% 技术实现: Caffeine集成与智能淘汰策略 Redis部署与Spring Boot整合 完整商品缓存示例代码 监控优化:缓存命中率统计与性能调优建议 适用于中高级开发者

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

32位快速加法器设计

32位快速加法器设计

好好学习,天天向上 3290

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

MyBatis三级缓存实战:高级缓存策略的实现与应用

MyBatis的三级缓存是一种高级缓存策略,可以在整个应用程序中共享缓存数据。本文将深入研究MyBatis的三级缓存,详细解析其配置方式、工作原理以及性能调优技巧。我们将探讨三级缓存的生命周期、作用范围、失效策略等关键特性,以及如何合理配置和优化三级缓存以提高系统性能。通过本文的阅读,读者将能够全面了解MyBatis三级缓存的实现细节和使用技巧,为项目的性能优化提供有力支持。

学而时习之,不亦说乎?温故而知新,可以为师矣! 9721

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

CPU缓存架构详解&高性能内存队列Disruptor实战

Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注。2011年,企业应用软件专家Martin Fowler专门撰写长文介绍。同年它还获得了Oracle官方的Duke大奖。目前,包括Apache Storm、Camel、Log4j2在内的很多知名项目都应用了Disruptor以获取高性能。

输入知识,消化知识,输出知识,分享知识,教人让我学习更好,业精于勤荒于嬉,行成于思毁于随 2256

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java 核心机制深度剖析:AOP 字节码增强、三级缓存、对象池、JMM 内存屏障

在企业级 Java 系统开发中,AOP 字节码增强、三级缓存、对象池、JMM 内存屏障等机制,已成为性能优化和并发安全的核心技术。本文结合源码、流程图和案例,系统性讲解这些机制的原理和应用,帮助你知其然,更知其所以然。AOP 字节码增强Spring Framework 2002 年面世,AOP 成为解耦业务逻辑的利器。JDK 动态代理支持接口,CGLIB 支持类级字节码增强,极大丰富了切面编程的场景。三级缓存机制。

IT技术学习与工作笔记分享 5508

如何构建Tomcat与Redis Cluster高可用缓存架构:完整部署指南

在当今高并发Web应用中,**Tomcat与Redis Cluster的整合**已成为提升应用性能的关键技术方案。作为业界领先的Java Web服务器,Tomcat与Redis分布式缓存集群的结合能够为应用程序提供稳定可靠的高可用缓存服务,有效解决单点故障和数据一致性问题。 ## 🚀 为什么需要Tomcat与Redis Cluster整合? **Redis Cluster**作为分布式缓存解

gitblog_01155的博客 1184

MySQL - +Redis 缓存架构实战:缓存穿透、击穿、雪崩解决方案

本文深入探讨MySQL与Redis缓存架构中的三大核心问题:缓存穿透、击穿和雪崩。针对缓存穿透问题,提出缓存空值和布隆过滤器两种解决方案,通过Java代码示例演示如何防止无效查询冲击数据库。对于缓存击穿问题,重点分析互斥锁和逻辑过期策略,确保热点数据不会因并发访问导致数据库过载。针对缓存雪崩现象,提出随机过期时间、多级缓存和熔断降级等策略,避免大规模缓存失效引发系统崩溃。文章结合实战场景,为开发者提供了一套完整的缓存问题解决方案,帮助构建高可用的分布式系统架构。

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

高性能Java架构之堆外缓存与磁盘缓存解决方案:MapDB

在互联网项目中,一般以堆内缓存的使用居多,无论Guava Cache还是JDK自带的HashMap、ConcurrentHashMap等,都是在堆内缓存中做数据计算操作。这是因为堆内缓存的响应速度最快,但是堆内缓存的价格也最高。有没有既能节约成本,又能提供较好的性能的工具呢?JVM一旦出现GC或者FULL GC的情况,就然删掉堆内存,此时应如何快速读取缓存数据?

2401_83384536的博客 2420

java高可用_Java 三高 ,高并发 ,高可用 。高性能

三高好像离我很远,我好像根本用不到它,整天做着集中式的开发,访问的人数好像最多也没几个人,500?200? 我不知道自己做的项目有超过这么高的峰值过吗?大概没有吧,从来都没有为项目的性能与并发问题发过愁的我,感觉....应该提升提升自己的能力水平了。可能一想到Java 的 高并发 ,高可用 。高性能 ,大家都会想到 分布式、微服务、等等的吧。好像只有分布式、微服务 的电商项目才可以用到这些东西。但...

weixin_34404199的博客 4890

科普文:分布式架构中的三高:高并发、高性能、高可用

所谓的水平伸缩,指的是不去提升单机的处理能力,而是使用更多的服务器,将这些服务器构成一个分布式集群,通过这个集群,对外统一提供服务,以此来提高系统整体的处理能力。此外,还有外部环境引发的不可用,比如促销引来大量用户访问,导致系统并发压力太大而崩溃,以及,黑客攻击、机房火灾、挖掘机挖断光缆,各种情况导致的应用不可用。比如用更快频率的 CPU,用更多核的 CPU,用更大的内存,用更快的网卡,用更多的磁盘组成一台服务器,使单台服务器的处理能力得到提升。增加同样的费用,并不能得到同样的计算能力。

为无为,事无事,味无味。 2454

高并发-高可用-高性能

文章目录高并发,高可用,高性能简介高并发高性能高可用 高并发,高可用,高性能 简介 软件开发通常会提到一个名词 “三高”,即高并发、高性能、高可用。 具体的指标定义,如:高并发方面要求QPS 大于 10万;高性能方面要求请求延迟小于 100 ms;高可用方面要求系统可用性高于 99.99%。 高并发 我们使用 QPS(Queries Per Second,每秒查询率)来衡量系统承载能力 高性能 性能直接影响用户的感官体验,访问一个系统,如果超过5秒没有响应,绝大数用户会选择离开。 高可用 高可用指

远行的博客 4159

三级缓存实现

在Java应用中整合Nginx、Caffeine和Redis构建三级缓存架构,能显著提升高并发系统的性能。下面是一个实用的实现方案。

tqs_123456的博客 527
上一篇: wsl集成docker desktop环境下使用docker-compose一键部署mysql,nacos,redis,mq详细教程(服务器同理)
下一篇: 基于 MyBatis-Plus 实体类自动生成数据库表结构工具
老星*
博客等级 码龄5年 271粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老星*

感谢支持!!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值