Сколько живет объект в этом Cache?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Время жизни объекта в Cache
Краткий ответ: Время жизни объекта в кэше зависит от типа кэша, его конфигурации и политики вытеснения. Это может быть от миллисекунд до полного жизненного цикла приложения, или объект может быть вытеснен раньше при нехватке памяти.
1. Кэш на уровне приложения (In-memory cache)
Caffeine Cache — самый популярный в Spring Boot:
@Configuration
public class CacheConfiguration {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("users", "orders");
cacheManager.setCaffeine(Caffeine.newBuilder()
// Expiration policies
.expireAfterWrite(10, TimeUnit.MINUTES) // Истекает через 10 минут после записи
.expireAfterAccess(5, TimeUnit.MINUTES) // Истекает через 5 минут после последнего доступа
// Maximum size
.maximumSize(1000) // Максимум 1000 объектов в памяти
// Refreshing (обновление в фоне)
.refreshAfterWrite(2, TimeUnit.MINUTES) // Обновляет значение каждые 2 минуты
);
return cacheManager;
}
}
Время жизни объекта в этом примере:
- Объект сохранится в памяти максимум 10 минут (expireAfterWrite)
- Или 5 минут после последнего обращения (expireAfterAccess) — смотря что наступит раньше
- Если в кэше более 1000 объектов, старые вытесняются
Пример использования:
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// Первый вызов: запросит из БД
return userRepository.findById(id).orElse(null);
}
@CacheEvict(value = "users", key = "#id")
public void updateUser(Long id, User user) {
// При обновлении удаляем из кэша
userRepository.save(user);
}
}
// Использование
User user1 = userService.getUserById(1); // Из БД (записано в кэш)
User user2 = userService.getUserById(1); // Из кэша (быстро)
// Спустя 10 минут или при вытеснении:
User user3 = userService.getUserById(1); // Снова из БД
2. Redis Cache (распределённый кэш)
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)); // Time To Live = 10 минут
return RedisCacheManager.create(factory);
}
}
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
}
Время жизни:
- Объект живёт в Redis 10 минут (TTL)
- После истечения TTL, объект автоматически удаляется Redis'ом
- Данные сохраняются даже при перезагрузке приложения (если Redis персистентный)
3. Manual cache (самодельный кэш)
public class ManualCache<K, V> {
private final Map<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
private final long ttlMillis;
static class CacheEntry<V> {
V value;
long createdAt;
CacheEntry(V value) {
this.value = value;
this.createdAt = System.currentTimeMillis();
}
boolean isExpired(long ttlMillis) {
return System.currentTimeMillis() - createdAt > ttlMillis;
}
}
public ManualCache(long ttlMillis) {
this.ttlMillis = ttlMillis;
}
public void put(K key, V value) {
cache.put(key, new CacheEntry<>(value));
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) {
return null; // Нет в кэше
}
if (entry.isExpired(ttlMillis)) {
cache.remove(key); // Удаляем истёкший объект
return null;
}
return entry.value; // Возвращаем живой объект
}
}
// Использование
ManualCache<String, User> userCache = new ManualCache<>(5 * 60 * 1000); // 5 минут
User user = new User(1, "John");
userCache.put("user:1", user);
User cached = userCache.get("user:1"); // Вернёт user, если не прошло 5 минут
Thread.sleep(6 * 60 * 1000); // Ждём 6 минут
User expired = userCache.get("user:1"); // Вернёт null, объект умер
4. Google Guava Cache
public class GuavaCacheExample {
private final LoadingCache<String, User> userCache;
public GuavaCacheExample(UserRepository userRepository) {
userCache = CacheBuilder.newBuilder()
.maximumSize(10000) // Максимум 10,000 записей
.expireAfterWrite(10, TimeUnit.MINUTES) // Истекает через 10 минут
.recordStats() // Собирает статистику
.build(
new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
return userRepository.findById(Long.parseLong(key))
.orElse(null);
}
}
);
}
public User getUser(String userId) {
try {
return userCache.get(userId);
} catch (ExecutionException e) {
return null;
}
}
public void invalidate(String userId) {
userCache.invalidate(userId);
}
}
Время жизни:
- Объект живёт 10 минут после записи (expireAfterWrite)
- Может быть вытеснен раньше, если кэш содержит более 10,000 объектов
5. WeakReference и SoftReference (специальные случаи)
public class ReferenceCache {
// Слабые ссылки: GC может удалить в любой момент
private Map<String, WeakReference<User>> weakCache = new WeakHashMap<>();
// Мягкие ссылки: GC удаляет только при нехватке памяти
private Map<String, SoftReference<User>> softCache = new HashMap<>();
public void putWeak(String key, User user) {
weakCache.put(key, new WeakReference<>(user));
}
public User getWeak(String key) {
WeakReference<User> ref = weakCache.get(key);
return ref != null ? ref.get() : null; // Может вернуть null если GC удалил
}
public void putSoft(String key, User user) {
softCache.put(key, new SoftReference<>(user));
}
public User getSoft(String key) {
SoftReference<User> ref = softCache.get(key);
return ref != null ? ref.get() : null;
}
}
// Время жизни:
// WeakReference: живёт, пока есть другие ссылки на объект
// SoftReference: живёт до нехватки памяти (gc может удалить)
6. Сравнение политик вытеснения
public class EvictionPolicies {
// LRU (Least Recently Used) — вытесняет давно не используемые
// LFU (Least Frequently Used) — вытесняет редко используемые
// FIFO (First In First Out) — вытесняет первые добавленные
// Random — вытесняет случайно
// Caffeine использует Window TinyLFU
// Это гибрид LRU и LFU
CaffeineCacheManager cache = new CaffeineCacheManager();
// Объекты вытесняются по LFU-подобной политике
// Нечасто используемые объекты удаляются в первую очередь
}
7. Мониторинг времени жизни
@Component
public class CacheMetricsLogger {
@Scheduled(fixedRate = 60000) // Каждую минуту
public void logCacheStats(CacheManager cacheManager) {
cacheManager.getCacheNames().forEach(cacheName -> {
Cache cache = cacheManager.getCache(cacheName);
if (cache instanceof com.github.benmanes.caffeine.cache.Cache) {
com.github.benmanes.caffeine.cache.Cache<Object, Object> caffeineCache =
(com.github.benmanes.caffeine.cache.Cache<Object, Object>) cache.getNativeCache();
System.out.println(String.format(
"Cache %s - Size: %d, Hits: %d, Misses: %d",
cacheName,
caffeineCache.estimatedSize(),
caffeineCache.stats().hitCount(),
caffeineCache.stats().missCount()
));
}
});
}
}
Таблица: Сравнение типов кэшей
| Тип | TTL (Time To Live) | Хранилище | Персистентность | Рекомендация |
|---|---|---|---|---|
| Caffeine | Настраивается (обычно 10-30 мин) | Память | Нет | Single server |
| Redis | Настраивается (obычно TTL) | Память + диск | Да (опционально) | Распределённые системы |
| Guava | Настраивается | Память | Нет | Простые приложения |
| WeakReference | До GC | Память | Нет | Когда нужна max экономия памяти |
| SoftReference | До нехватки памяти | Память | Нет | Кэш больших объектов |
Best Practices для кэша
✓ Установи реалистичный TTL (не слишком короткий, не вечный) ✓ Устанавливай максимальный размер кэша (не бесконечный) ✓ Используй правильную политику вытеснения (LRU/LFU) ✓ Инвалидируй кэш при обновлении данных (@CacheEvict) ✓ Логируй hit/miss ratio'и для оптимизации ✓ Используй Caffeine для single-server, Redis для распределённых систем ✓ Не кэшируй критичные данные с долгим TTL