Какой опыт работы с кэшированием в Java-приложениях?

«Какой опыт работы с кэшированием в Java-приложениях?» — вопрос из категории Архитектура, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Опыт включает реализацию стратегий кэширования на разных уровнях для повышения производительности и снижения нагрузки на источники данных.

Основные подходы и технологии:

  1. Локальное кэширование в памяти приложения:

    • Caffeine или Ehcache — высокопроизводительные библиотеки.
    • Spring Cache Abstraction — аннотации для декларативного кэширования.

    Пример со Spring Cache и Caffeine:

    @Configuration
    @EnableCaching
    public class CacheConfig {
        @Bean
        public CacheManager cacheManager() {
            CaffeineCacheManager cacheManager = new CaffeineCacheManager("users", "products");
            cacheManager.setCaffeine(Caffeine.newBuilder()
                    .expireAfterWrite(10, TimeUnit.MINUTES)
                    .maximumSize(1000));
            return cacheManager;
        }
    }
    
    @Service
    public class UserService {
        @Cacheable(value = "users", key = "#id", unless = "#result == null")
        public User getUserById(Long id) {
            // Дорогой запрос к БД
            return userRepository.findById(id).orElse(null);
        }
    
        @CacheEvict(value = "users", key = "#user.id")
        public User updateUser(User user) {
            return userRepository.save(user);
        }
    
        @CachePut(value = "users", key = "#user.id")
        public User createUser(User user) {
            return userRepository.save(user);
        }
    }
  2. Распределенное кэширование: Использование Redis или Hazelcast для синхронизации кэша между несколькими экземплярами приложения.

Критические аспекты (нюансы):

  • Стратегии вытеснения: Выбор между LRU (Least Recently Used), LFU (Least Frequently Used) в зависимости от паттерна доступа.
  • Согласованность (Consistency): Инвалидация кэша при обновлении данных (@CacheEvict, @CachePut). Проблемы "cache stampede" решаются через блокировки или TTL с рандомизацией.
  • Сериализация: Для распределенного кэша важно выбрать эффективный формат (например, Jackson для JSON, Protobuf).
  • Мониторинг: Отслеживание hit/miss ratio, размера кэша и времени отклика.

Кэширование применяется для результатов тяжелых вычислений, данных из БД, ответов внешних API и сессий пользователей.