← Назад к вопросам

Сколько живет объект в этом Cache?

2.0 Middle🔥 141 комментариев
#Docker, Kubernetes и DevOps

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Время жизни объекта в 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