多级缓存解决方案 - 详解

在 Spring Boot 中实现"本地缓存 → Redis → 数据库"的多级缓存架构是一种非常有效的性能优化方案。下面我将详细展示完整的实现方案。

️ 整体架构设计

命中
未命中
命中
未命中
客户端请求
Controller层
Service层
查询本地缓存
返回数据
查询Redis缓存
回写本地缓存
查询数据库
回写Redis和本地缓存

第一步:添加依赖

<!-- 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>
          <version>3.1.8</version>
          </dependency>
          <!-- 数据库相关 -->
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            </dependency>
          </dependencies>

⚙️ 第二步:配置类

应用配置

# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
# 多级缓存配置
multi-level-cache:
local:
# 本地缓存配置
maximum-size: 10000
expire-after-write: 10m  # 10分钟
redis:
# Redis缓存配置  
default-expiration: 30m  # 30分钟

Caffeine 配置类

@Configuration
@EnableCaching
public class CacheConfig {
@Value("${multi-level-cache.local.maximum-size:10000}")
private long maximumSize;
@Value("${multi-level-cache.local.expire-after-write:10m}")
private Duration expireAfterWrite;
/**
* 配置 Caffeine 本地缓存
*/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(maximumSize)
.expireAfterWrite(expireAfterWrite)
.recordStats()  // 开启统计
.removalListener((key, value, cause) ->
log.debug("本地缓存移除: key={}, cause={}", key, cause))
);
return cacheManager;
}
/**
* Redis 模板配置
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    // 使用 Jackson 序列化
    Jackson2JsonRedisSerializer<Object> serializer =
      new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
        mapper.getPolymorphicTypeValidator(),
        ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(mapper);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
        }
        }

第三步:核心服务实现

多级缓存服务

@Service
@Slf4j
public class MultiLevelCacheService {
private final CacheManager cacheManager;
private final RedisTemplate<String, Object> redisTemplate;
  // 缓存名称常量
  private static final String LOCAL_CACHE_NAME = "userCache";
  private static final Duration REDIS_EXPIRE_TIME = Duration.ofMinutes(30);
  public MultiLevelCacheService(CacheManager cacheManager,
  RedisTemplate<String, Object> redisTemplate) {
    this.cacheManager = cacheManager;
    this.redisTemplate = redisTemplate;
    }
    /**
    * 多级缓存查询:本地缓存 → Redis → 数据库
    */
    public <T> T get(String key, Class<T> type, Function<String, T> loader) {
      // 1. 查询本地缓存
      T value = getFromLocalCache(key, type);
      if (value != null) {
      log.debug("本地缓存命中 key: {}", key);
      return value;
      }
      // 2. 查询Redis缓存
      value = getFromRedis(key, type);
      if (value != null) {
      log.debug("Redis缓存命中 key: {}", key);
      // 回写到本地缓存
      putToLocalCache(key, value);
      return value;
      }
      // 3. 查询数据库
      log.debug("缓存未命中,查询数据库 key: {}", key);
      value = loader.apply(key);
      if (value != null) {
      // 同时写入Redis和本地缓存
      putToRedis(key, value);
      putToLocalCache(key, value);
      }
      return value;
      }
      /**
      * 从本地缓存获取数据
      */
      @SuppressWarnings("unchecked")
      private <T> T getFromLocalCache(String key, Class<T> type) {
        try {
        Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
        if (cache != null) {
        Cache.ValueWrapper wrapper = cache.get(buildLocalKey(key));
        if (wrapper != null) {
        return (T) wrapper.get();
        }
        }
        } catch (Exception e) {
        log.warn("本地缓存查询失败 key: {}, error: {}", key, e.getMessage());
        }
        return null;
        }
        /**
        * 从Redis获取数据
        */
        private <T> T getFromRedis(String key, Class<T> type) {
          try {
          String redisKey = buildRedisKey(key);
          Object value = redisTemplate.opsForValue().get(redisKey);
          if (value != null) {
          return type.cast(value);
          }
          } catch (Exception e) {
          log.warn("Redis缓存查询失败 key: {}, error: {}", key, e.getMessage());
          }
          return null;
          }
          /**
          * 写入本地缓存
          */
          private void putToLocalCache(String key, Object value) {
          try {
          Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
          if (cache != null) {
          cache.put(buildLocalKey(key), value);
          }
          } catch (Exception e) {
          log.warn("本地缓存写入失败 key: {}, error: {}", key, e.getMessage());
          }
          }
          /**
          * 写入Redis缓存
          */
          private void putToRedis(String key, Object value) {
          try {
          String redisKey = buildRedisKey(key);
          redisTemplate.opsForValue().set(redisKey, value, REDIS_EXPIRE_TIME);
          } catch (Exception e) {
          log.warn("Redis缓存写入失败 key: {}, error: {}", key, e.getMessage());
          }
          }
          /**
          * 删除缓存(双删策略)
          */
          public void evict(String key) {
          // 先删除本地缓存
          evictLocalCache(key);
          // 再删除Redis缓存
          evictRedisCache(key);
          log.debug("缓存删除完成 key: {}", key);
          }
          private void evictLocalCache(String key) {
          try {
          Cache cache = cacheManager.getCache(LOCAL_CACHE_NAME);
          if (cache != null) {
          cache.evict(buildLocalKey(key));
          }
          } catch (Exception e) {
          log.warn("本地缓存删除失败 key: {}, error: {}", key, e.getMessage());
          }
          }
          private void evictRedisCache(String key) {
          try {
          redisTemplate.delete(buildRedisKey(key));
          } catch (Exception e) {
          log.warn("Redis缓存删除失败 key: {}, error: {}", key, e.getMessage());
          }
          }
          private String buildLocalKey(String key) {
          return "local:" + key;
          }
          private String buildRedisKey(String key) {
          return "redis:" + key;
          }
          /**
          * 获取缓存统计信息(用于监控)
          */
          public CacheStats getStats() {
          com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache =
            (com.github.benmanes.caffeine.cache.Cache<Object, Object>)
              cacheManager.getCache(LOCAL_CACHE_NAME).getNativeCache();
              com.github.benmanes.caffeine.cache.stats.CacheStats stats = nativeCache.stats();
              return new CacheStats(
              stats.hitCount(),
              stats.missCount(),
              stats.loadSuccessCount(),
              stats.loadFailureCount(),
              stats.totalLoadTime()
              );
              }
              @Data
              @AllArgsConstructor
              public static class CacheStats {
              private long hitCount;
              private long missCount;
              private long loadSuccessCount;
              private long loadFailureCount;
              private long totalLoadTime;
              }
              }

业务服务实现

@Service
@Slf4j
public class UserService {
private final UserRepository userRepository;
private final MultiLevelCacheService cacheService;
private static final String USER_CACHE_PREFIX = "user:";
public UserService(UserRepository userRepository, MultiLevelCacheService cacheService) {
this.userRepository = userRepository;
this.cacheService = cacheService;
}
/**
* 根据ID查询用户 - 使用多级缓存
*/
public User getUserById(Long id) {
String cacheKey = USER_CACHE_PREFIX + id;
return cacheService.get(cacheKey, User.class, key -> {
// 数据库查询函数
log.info("查询数据库用户信息,ID: {}", id);
return userRepository.findById(id).orElse(null);
});
}
/**
* 更新用户信息 - 同时更新缓存
*/
@Transactional
public User updateUser(User user) {
// 1. 更新数据库
User updatedUser = userRepository.save(user);
// 2. 删除缓存(采用双删策略)
String cacheKey = USER_CACHE_PREFIX + user.getId();
cacheService.evict(cacheKey);
log.info("用户信息更新完成,ID: {}", user.getId());
return updatedUser;
}
/**
* 批量获取用户(演示批量操作)
*/
public List<User> getUsersBatch(List<Long> ids) {
  return ids.stream()
  .map(this::getUserById)
  .filter(Objects::nonNull)
  .collect(Collectors.toList());
  }
  }

️ 第四步:数据层配置

实体类

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true)
private String email;
private Integer age;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}

Repository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
  Optional<User> findByEmail(String email);
    List<User> findByIdIn(List<Long> ids);
      }

第五步:控制器层

@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
private final UserService userService;
private final MultiLevelCacheService cacheService;
public UserController(UserService userService, MultiLevelCacheService cacheService) {
this.userService = userService;
this.cacheService = cacheService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
  User user = userService.getUserById(id);
  return user != null ?
  ResponseEntity.ok(user) :
  ResponseEntity.notFound().build();
  }
  @PutMapping("/{id}")
  public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
    user.setId(id);
    User updatedUser = userService.updateUser(user);
    return ResponseEntity.ok(updatedUser);
    }
    @GetMapping("/cache/stats")
    public ResponseEntity<MultiLevelCacheService.CacheStats> getCacheStats() {
      return ResponseEntity.ok(cacheService.getStats());
      }
      @DeleteMapping("/{id}/cache")
      public ResponseEntity<Void> clearUserCache(@PathVariable Long id) {
        userService.clearCache(id);
        return ResponseEntity.ok().build();
        }
        }

第六步:高级特性

缓存预热

@Component
@Slf4j
public class CacheWarmUpRunner implements ApplicationRunner {
private final UserRepository userRepository;
private final MultiLevelCacheService cacheService;
public CacheWarmUpRunner(UserRepository userRepository, MultiLevelCacheService cacheService) {
this.userRepository = userRepository;
this.cacheService = cacheService;
}
@Override
public void run(ApplicationArguments args) {
log.info("开始缓存预热...");
// 预热热点数据
List<User> hotUsers = userRepository.findAll(PageRequest.of(0, 100)).getContent();
  hotUsers.forEach(user -> {
  String cacheKey = "user:" + user.getId();
  cacheService.putToRedis(cacheKey, user);
  });
  log.info("缓存预热完成,共预热 {} 条用户数据", hotUsers.size());
  }
  }

监控端点

@RestControllerEndpoint(id = "multicache")
@Slf4j
public class MultiCacheEndpoint {
private final MultiLevelCacheService cacheService;
public MultiCacheEndpoint(MultiLevelCacheService cacheService) {
this.cacheService = cacheService;
}
@GetMapping("/stats")
public Map<String, Object> getCacheStats() {
  MultiLevelCacheService.CacheStats stats = cacheService.getStats();
  Map<String, Object> result = new HashMap<>();
    result.put("hitCount", stats.getHitCount());
    result.put("missCount", stats.getMissCount());
    result.put("loadSuccessCount", stats.getLoadSuccessCount());
    result.put("hitRate", calculateHitRate(stats));
    result.put("timestamp", LocalDateTime.now());
    return result;
    }
    private double calculateHitRate(MultiLevelCacheService.CacheStats stats) {
    long totalRequests = stats.getHitCount() + stats.getMissCount();
    return totalRequests > 0 ?
    (double) stats.getHitCount() / totalRequests : 0.0;
    }
    }

核心优势总结

  1. 性能极致:本地缓存提供纳秒级访问,Redis提供分布式缓存
  2. 高可用:任何一级缓存故障都不会导致系统完全不可用
  3. 可扩展:易于添加新的缓存层级或替换缓存实现
  4. 监控完善:提供详细的缓存命中率统计
  5. 容错性强:每级缓存都有异常处理,避免级联失败

这种架构特别适合读多写少数据变化不频繁访问量巨大的业务场景。

posted @ 2026-02-06 18:06  clnchanpin  阅读(31)  评论(0)    收藏  举报