Files
Alexander Abakumov 15e744da49 Initial commit: Complete Software Architecture course with 11 modules
- Added comprehensive course structure with 11 modules
- Included theoretical content, practical examples, and tasks
- Multi-language code examples (Java, Rust, Node.js, Python)
- Detailed analysis of architectural approaches and trade-offs
- Complete caching strategies documentation
- Architecture examples from real-world systems (Twitter, Netflix, Uber)
- Structured navigation and learning path
- Ready for educational use
2025-09-09 22:08:07 +03:00

9.8 KiB

Кэширование

Навигация


Стратегии кэширования

1. Cache-Aside Pattern

Технический пример - Advanced Multi-Level Caching System:

// Advanced Cache Management Service
@Service
public class AdvancedCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CacheMetricsService cacheMetricsService;
    
    // Multi-level caching
    private final Map<String, Object> localCache = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    
    public <T> T get(String cacheKey, Class<T> type, Supplier<T> 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 <T> CompletableFuture<T> getAsync(String cacheKey, Class<T> type, Supplier<T> dataLoader) {
        return CompletableFuture.supplyAsync(() -> get(cacheKey, type, dataLoader), scheduler);
    }
    
    public <T> List<T> getBatch(List<String> cacheKeys, Class<T> type, Function<List<String>, List<T>> dataLoader) {
        List<T> results = new ArrayList<>();
        List<String> 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<T> redisValues = getFromRedisCacheBatch(missingKeys, type);
        List<String> 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<T> 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<String> keys = redisTemplate.keys(pattern);
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
        
        cacheMetricsService.recordCacheEvictPattern(pattern);
    }
    
    // Cache warming
    public void warmCache(List<String> keys, Supplier<List<Object>> dataLoader) {
        log.info("Warming cache with {} keys", keys.size());
        
        List<Object> 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> T getFromLocalCache(String key, Class<T> type) {
        Object value = localCache.get(key);
        return type.cast(value);
    }
    
    private <T> T getFromRedisCache(String key, Class<T> 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 <T> List<T> getFromRedisCacheBatch(List<String> keys, Class<T> type) {
        try {
            List<Object> 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

// Write-Through реализация
@Service
public class WriteThroughUserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RedisTemplate<String, User> 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

// Write-Behind реализация
@Service
public class WriteBehindUserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RedisTemplate<String, User> 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)

// L1 кэш (в памяти приложения)
@Service
public class L1CacheUserService {
    private final Map<Long, User> 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)

// L2 кэш (распределенный)
@Service
public class L2CacheUserService {
    @Autowired
    private RedisTemplate<String, User> 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;
    }
}