Вступление
Привет! Вы когда-нибудь задумывались, почему некоторые запросы в микросервисах ощущаются как поездка на «старой электричке»? Казалось бы, есть FeignClient — мощный и удобный инструмент для общения сервисов, но внезапно задержки растут, а коллеги начинают замечать, что ваше API «тормозит».
Я расскажу, как я решил эту проблему, добавив кэширование с помощью Caffeine Cache. После этого мой сервис стал выдавать данные быстрее, чем их запрашивали (шутка, но почти правда).
Готовы? Тогда поехали.
Стек технологий
Для реализации использовались следующие инструменты:
-
Java 21 — на текущий момент это последняя стабильная версия языка;
-
Spring Boot 3.1 — актуальная версия с поддержкой Jakarta EE и обновлённой экосистемой;
-
Spring Cloud OpenFeign — 2023.x.x, одна из последних версий с поддержкой современных фич;
-
Caffeine Cache 3.x — высокопроизводительный инструмент кэширования;
-
Gradle — для сборки проекта.
Кэширование с FeignClient можно внедрить начиная с:
-
Java 8 — базовая версия для использования Caffeine Cache
-
Spring Boot 2.1+ — поддержка интеграции FeignClient через Spring Cloud
-
Caffeine Cache 2.x+ — основные функции кэширования
Однако для максимальной производительности рекомендую использовать Spring Boot 3.x и Java 17 или выше.
Что такое FeignClient и зачем ему кэш?
FeignClient — это такая магия в мире Spring, которая позволяет взаимодействовать с другими сервисами так, будто вы вызываете локальный метод. Никакой головной боли с HTTP-запросами, сериализацией и прочими мелочами.
Но у любого чуда есть обратная сторона: каждое обращение к FeignClient — это сетевой вызов. А там:
-
Латентность сети (время, которое требуется на доставку пакета данных от источника к пункту назначения);
-
Нагрузки на удалённые сервисы;
-
Зависимость от стабильности другого API
Если ваш сервис часто запрашивает одни и те же данные, то вопрос «А нельзя ли это как-то ускорить?» становится очевидным. Здесь на сцену выходит Caffeine Cache.
Как настроить кэширование для FeignClient
Первый шаг: добавляем зависимости.
Если ваш проект ещё не знает, что такое Feign и Caffeine, научите его, для этого необходимо добавить в ваш build.gradle следующие зависимости:
dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache', version: '3.3.5' implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '4.1.3' implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '3.3.5' }
Теперь ваш проект готов к магии.
Второй шаг: создаём FeignClient
Как говорится, в начале было слово… а точнее, интерфейс. Вот так будет выглядеть наш клиент:
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "exampleClient", url = "https://api.example.com") public interface ExampleFeignClient { @GetMapping("/data") ExampleResponse getData(@RequestParam String id); }
Это простой клиент, который обращается к внешнему API. Пока он делает запросы напрямую.
Ответ от клиента: создаём ExampleResponse
Наш FeignClient
возвращает объект ExampleResponse
. Определим его класс:
public class ExampleResponse { private String data; public ExampleResponse(String data) { this.data = data; } public String getData() { return data; } public void setData(String data) { this.data = data; } }
Третий шаг: включаем кэширование и настраиваем Caffeine Cache
Caffeine — это Ferrari в мире кэширования. Лёгкий, быстрый, гибкий. Давайте добавим его в наш проект и включим поддержку кэширования в Spring Boot, добавив аннотацию @EnableCaching
:
import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.time.Duration; @Configuration @EnableCaching public class CacheConfig { @Bean @Primary public CacheManager cacheManager() { var caffeineCacheManager = new CaffeineCacheManager("exampleCache"); caffeineCacheManager.setCaffeine( Caffeine.newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) // Данные устаревают через 10 минут .maximumSize(100) // Максимум 100 записей ); return caffeineCacheManager; } }
Теперь Spring Cache знает, как работать с Caffeine. У нас есть кэш, который будет хранить до 100 записей в течение 10 минут.
Зачем нужна аннотация @Primary?
Аннотация @Primary
позволяет Spring однозначно выбрать, какой бин использовать по умолчанию, когда тип CacheManager
запрашивается в вашем приложении. Если вы добавляете несколько кэш-менеджеров (например, для разных технологий или целей), Spring может не знать, какой из них предпочтителен.
Четвёртый шаг: соединяем FeignClient и кэширование при помощи аннотации @Cacheable
Вот где начинается магия. Вместо того чтобы вызывать FeignClient напрямую, мы добавляем слой кэширования. Используем аннотацию @Cacheable
в сервисе, чтобы автоматически кэшировать результаты вызовов FeignClient:
import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class ExampleService { private final ExampleFeignClient feignClient; public ExampleService(ExampleFeignClient feignClient) { this.feignClient = feignClient; } @Cacheable(value = "exampleCache", cacheManager = "cacheManager", key = "#id") public ExampleResponse getDataWithCaching(String id) { System.out.println("Запрос через FeignClient для ID: " + id); return feignClient.getData(id); } }
Теперь каждый вызов метода getDataWithCaching
сначала проверяет, есть ли данные в кэше. Если они есть — возвращает результат из кэша. Если нет — вызывает FeignClient и кэширует результат.
Проверяем, работает ли
Если вы, как и я, любите убедиться, что код работает идеально, давайте напишем тест:
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; @SpringBootTest class ExampleServiceTest { @Autowired private ExampleService exampleService; @MockBean private ExampleFeignClient feignClient; @Test void testCaching() { String id = "123"; ExampleResponse response = new ExampleResponse("Test Data"); // Имитируем ответ FeignClient Mockito.when(feignClient.getData(id)).thenReturn(response); // Первый вызов - данные будут запрошены через FeignClient ExampleResponse result1 = exampleService.getDataWithCaching(id); Assertions.assertEquals(response, result1); // Второй вызов - данные должны быть взяты из кэша ExampleResponse result2 = exampleService.getDataWithCaching(id); Assertions.assertEquals(response, result2); // Проверяем, что FeignClient вызван только один раз Mockito.verify(feignClient, Mockito.times(1)).getData(id); } }
Тест гарантирует, что кэш действительно работает.
Финальное слово
Добавив кэширование к FeignClient, вы не просто ускоряете свой сервис — вы создаёте более устойчивую и надёжную архитектуру, что особенно важно, когда ваша система работает под высокой нагрузкой.
Кэширование — это не просто про производительность. Это про оптимизацию ресурсов, стабильность и удовольствие от быстродействия.
Использование @Cacheable
значительно упрощает добавление кэширования в проект.
Теперь ваш FeignClient стал быстрее, а пользователи довольны скоростью отклика.
Если вы ещё не используете кэширование в микросервисах — сейчас самое время начать! Если у вас есть вопросы или вы хотите поделиться своим опытом, пишите в комментариях.
Кэширование: далеко за пределами FeignClient
Кэширование в Spring — это универсальный инструмент, который может быть использован в самых разных сценариях. FeignClient — лишь один из примеров, где кэширование помогает ускорить выполнение запросов. Но его потенциал гораздо шире:
-
Кэширование данных из базы данных
Например, если ваш сервис часто обращается к базе за данными, которые редко меняются, кэширование запросов к репозиториям может значительно снизить нагрузку:
@Cacheable(value = "userCache", key = "#userId") public User getUserById(String userId) { return userRepository.findById(userId).orElseThrow(); }
-
Результаты сложных вычислений
Если вы выполняете ресурсоёмкие вычисления (например, обработки больших объёмов данных), кэширование может стать спасением:
@Cacheable(value = "calculationCache", key = "#input") public CalculationResult calculateHeavyTask(InputData input) { // Затратная операция }
-
Взаимодействие с внешними API
Помимо FeignClient, кэширование отлично подходит для любого взаимодействия с внешними сервисами, чтобы минимизировать сетевую нагрузку. -
Часто используемые настройки или метаданные
Если ваш сервис часто запрашивает статические данные (например, конфигурации, справочники или метаданные), кэширование ускоряет доступ:
@Cacheable(value = "configCache") public Config getConfig() { return loadConfigFromFileOrDatabase(); }
-
Работа с асинхронными потоками
Для асинхронных операций кэширование также может быть настроено через асинхронный бин Caffeine.
Вывод
Кэширование — это универсальный инструмент, который работает везде, где требуется оптимизация доступа к данным. Главное — правильно определить, какие данные действительно стоит кэшировать, и выбрать подходящую стратегию.
ссылка на оригинал статьи https://habr.com/ru/articles/860120/
Добавить комментарий