- 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
311 lines
9.8 KiB
Markdown
311 lines
9.8 KiB
Markdown
# Кэширование
|
|
|
|
## Навигация
|
|
- [🏠 Главная страница курса](../../../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<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
|
|
```java
|
|
// 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
|
|
```java
|
|
// 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)
|
|
```java
|
|
// 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)
|
|
```java
|
|
// 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;
|
|
}
|
|
}
|
|
```
|