- 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
9.8 KiB
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;
}
}