Каковы цели и компромиссы использования локального кэша в микросервисной архитектуре?

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

Ответ

Локальный (in-memory) кэш хранит данные в оперативной памяти экземпляра сервиса. Его основная цель — резкое снижение задержки (latency) и нагрузки на нижележащие источники данных (базы данных, другие сервисы) для часто запрашиваемой, относительно статичной информации.

Цели и преимущества:

  • Минимальная задержка: Доступ к данным из памяти на порядки быстрее, чем сетевой вызов или запрос к БД.
  • Снижение нагрузки: Уменьшает количество запросов к БД или внешним API, защищая их от перегрузки.
  • Устойчивость к сбоям: При недоступности основного источника данных сервис может какое-то время работать с устаревшими (кэшированными) данными.
  • Простота: Легко реализуется с помощью библиотек (Caffeine, Ehcache) или фреймворков (Spring Cache).

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

@Service
public class CatalogService {
    @Cacheable(value = "products", key = "#id")
    public Product getProduct(Long id) {
        // Дорогой вызов к БД выполняется только при промахе кэша
        return productRepository.findById(id).orElseThrow();
    }
}

// Конфигурация Caffeine для TTL и размера
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(1000));
        return manager;
    }
}

Компромиссы и проблемы:

  • Несогласованность данных (Consistency): Каждый экземпляр сервиса имеет свою копию кэша. Изменение данных в одном экземпляре не обновляет кэш в других. Это проблема для данных, требующих сильной согласованности.
  • Использование памяти: Кэш потребляет RAM, что ограничивает его объем и требует стратегий вытеснения (LRU, LFU).
  • Сложность инвалидации: Актуальность данных зависит от TTL (время жизни) или явной инвалидации при обновлении.

Когда использовать: Для данных, которые часто читаются, редко меняются и допускают eventual consistency (справочники, конфигурация, статичный контент). Для синхронизации между инстансами или часто изменяющихся данных часто используют распределенный кэш (Redis, Hazelcast) поверх или вместо локального.