# Кэширование ## Навигация - [🏠 Главная страница курса](../../../README.md) - [📚 Модуль 10](../README.md) --- ## Стратегии кэширования ### 1. Cache-Aside Pattern **Технический пример - Advanced Multi-Level Caching System:** ```java // Advanced Cache Management Service @Service public class AdvancedCacheService { @Autowired private RedisTemplate redisTemplate; @Autowired private CacheMetricsService cacheMetricsService; // Multi-level caching private final Map localCache = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); public T get(String cacheKey, Class type, Supplier dataLoader) { // Level 1: Local cache T value = getFromLocalCache(cacheKey, type); if (value != null) { cacheMetricsService.recordCacheHit(cacheKey, CacheLevel.LOCAL); return value; } // Level 2: Redis cache value = getFromRedisCache(cacheKey, type); if (value != null) { putToLocalCache(cacheKey, value, Duration.ofMinutes(5)); cacheMetricsService.recordCacheHit(cacheKey, CacheLevel.REDIS); return value; } // Level 3: Data source cacheMetricsService.recordCacheMiss(cacheKey); value = dataLoader.get(); if (value != null) { putToLocalCache(cacheKey, value, Duration.ofMinutes(5)); putToRedisCache(cacheKey, value, Duration.ofMinutes(30)); } return value; } public CompletableFuture getAsync(String cacheKey, Class type, Supplier dataLoader) { return CompletableFuture.supplyAsync(() -> get(cacheKey, type, dataLoader), scheduler); } public List getBatch(List cacheKeys, Class type, Function, List> dataLoader) { List results = new ArrayList<>(); List missingKeys = new ArrayList<>(); // Check local cache first for (String key : cacheKeys) { T value = getFromLocalCache(key, type); if (value != null) { results.add(value); cacheMetricsService.recordCacheHit(key, CacheLevel.LOCAL); } else { missingKeys.add(key); } } if (missingKeys.isEmpty()) { return results; } // Check Redis cache List redisValues = getFromRedisCacheBatch(missingKeys, type); List stillMissingKeys = new ArrayList<>(); for (int i = 0; i < missingKeys.size(); i++) { String key = missingKeys.get(i); T value = redisValues.get(i); if (value != null) { results.add(value); putToLocalCache(key, value, Duration.ofMinutes(5)); cacheMetricsService.recordCacheHit(key, CacheLevel.REDIS); } else { stillMissingKeys.add(key); } } // Load missing data if (!stillMissingKeys.isEmpty()) { List loadedData = dataLoader.apply(stillMissingKeys); for (int i = 0; i < stillMissingKeys.size(); i++) { String key = stillMissingKeys.get(i); T value = loadedData.get(i); if (value != null) { results.add(value); putToLocalCache(key, value, Duration.ofMinutes(5)); putToRedisCache(key, value, Duration.ofMinutes(30)); } cacheMetricsService.recordCacheMiss(key); } } return results; } public void put(String cacheKey, Object value) { putToLocalCache(cacheKey, value, Duration.ofMinutes(5)); putToRedisCache(cacheKey, value, Duration.ofMinutes(30)); cacheMetricsService.recordCachePut(cacheKey); } public void evict(String cacheKey) { localCache.remove(cacheKey); redisTemplate.delete(cacheKey); cacheMetricsService.recordCacheEvict(cacheKey); } public void evictPattern(String pattern) { localCache.entrySet().removeIf(entry -> entry.getKey().matches(pattern)); Set keys = redisTemplate.keys(pattern); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } cacheMetricsService.recordCacheEvictPattern(pattern); } // Cache warming public void warmCache(List keys, Supplier> dataLoader) { log.info("Warming cache with {} keys", keys.size()); List data = dataLoader.get(); for (int i = 0; i < keys.size() && i < data.size(); i++) { String key = keys.get(i); Object value = data.get(i); if (value != null) { putToLocalCache(key, value, Duration.ofMinutes(5)); putToRedisCache(key, value, Duration.ofMinutes(30)); } } log.info("Cache warming completed"); } private T getFromLocalCache(String key, Class type) { Object value = localCache.get(key); return type.cast(value); } private T getFromRedisCache(String key, Class type) { try { Object value = redisTemplate.opsForValue().get(key); return type.cast(value); } catch (Exception e) { log.warn("Error getting from Redis cache: {}", e.getMessage()); return null; } } private List getFromRedisCacheBatch(List keys, Class type) { try { List values = redisTemplate.opsForValue().multiGet(keys); return values.stream() .map(type::cast) .collect(Collectors.toList()); } catch (Exception e) { log.warn("Error getting batch from Redis cache: {}", e.getMessage()); return keys.stream().map(k -> (T) null).collect(Collectors.toList()); } } private void putToLocalCache(String key, Object value, Duration ttl) { localCache.put(key, value); scheduler.schedule(() -> localCache.remove(key), ttl.toMillis(), TimeUnit.MILLISECONDS); } private void putToRedisCache(String key, Object value, Duration ttl) { try { redisTemplate.opsForValue().set(key, value, ttl); } catch (Exception e) { log.warn("Error putting to Redis cache: {}", e.getMessage()); } } } ``` ### 2. Write-Through Pattern ```java // Write-Through реализация @Service public class WriteThroughUserService { @Autowired private UserRepository userRepository; @Autowired private RedisTemplate redisTemplate; public void createUser(User user) { // Сохраняем в БД user = userRepository.save(user); // Сохраняем в кэш String cacheKey = "user:" + user.getId(); redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30)); } public void updateUser(User user) { // Обновляем БД user = userRepository.save(user); // Обновляем кэш String cacheKey = "user:" + user.getId(); redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30)); } } ``` ### 3. Write-Behind Pattern ```java // Write-Behind реализация @Service public class WriteBehindUserService { @Autowired private UserRepository userRepository; @Autowired private RedisTemplate redisTemplate; @Async public void createUser(User user) { // Сохраняем в кэш сразу String cacheKey = "user:" + user.getId(); redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30)); // Сохраняем в БД асинхронно CompletableFuture.runAsync(() -> { userRepository.save(user); }); } } ``` ## Многоуровневое кэширование ### 1. L1 Cache (Application Level) ```java // L1 кэш (в памяти приложения) @Service public class L1CacheUserService { private final Map localCache = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @Autowired private UserRepository userRepository; public User getUser(Long id) { return localCache.computeIfAbsent(id, key -> { User user = userRepository.findById(key); if (user != null) { // Автоматическое удаление через 5 минут scheduler.schedule(() -> localCache.remove(key), 5, TimeUnit.MINUTES); } return user; }); } } ``` ### 2. L2 Cache (Distributed Cache) ```java // L2 кэш (распределенный) @Service public class L2CacheUserService { @Autowired private RedisTemplate redisTemplate; @Autowired private UserRepository userRepository; public User getUser(Long id) { String cacheKey = "user:" + id; User user = redisTemplate.opsForValue().get(cacheKey); if (user == null) { user = userRepository.findById(id); if (user != null) { redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30)); } } return user; } } ```