在 Spring Boot 中实现"本地缓存 → Redis → 数据库"的多级缓存架构是一种非常有效的性能优化方案。下面我将详细展示完整的实现方案。
🏗️ 整体架构设计
命中 未命中 命中 未命中 客户端请求 Controller层 Service层 查询本地缓存 返回数据 查询Redis缓存 回写本地缓存 查询数据库 回写Redis和本地缓存
📦 第一步:添加依赖
xml
<!-- 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>
⚙️ 第二步:配置类
应用配置
yaml
# 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 配置类
java
@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;
}
}
🎯 第三步:核心服务实现
多级缓存服务
java
@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;
}
}
业务服务实现
java
@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());
}
}
🗃️ 第四步:数据层配置
实体类
java
@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
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByIdIn(List<Long> ids);
}
🎮 第五步:控制器层
java
@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();
}
}
🔧 第六步:高级特性
缓存预热
java
@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());
}
}
监控端点
java
@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;
}
}
💡 核心优势总结
- 性能极致:本地缓存提供纳秒级访问,Redis提供分布式缓存
- 高可用:任何一级缓存故障都不会导致系统完全不可用
- 可扩展:易于添加新的缓存层级或替换缓存实现
- 监控完善:提供详细的缓存命中率统计
- 容错性强:每级缓存都有异常处理,避免级联失败
这种架构特别适合读多写少 、数据变化不频繁 但访问量巨大的业务场景。