Кэширование с FeignClient: как ускорить микросервисы и стать героем проекта

от автора

Вступление

Привет! Вы когда-нибудь задумывались, почему некоторые запросы в микросервисах ощущаются как поездка на «старой электричке»? Казалось бы, есть 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 — лишь один из примеров, где кэширование помогает ускорить выполнение запросов. Но его потенциал гораздо шире:

  1. Кэширование данных из базы данных
    Например, если ваш сервис часто обращается к базе за данными, которые редко меняются, кэширование запросов к репозиториям может значительно снизить нагрузку:

@Cacheable(value = "userCache", key = "#userId")   public User getUserById(String userId) {       return userRepository.findById(userId).orElseThrow();   }  
  1. Результаты сложных вычислений
    Если вы выполняете ресурсоёмкие вычисления (например, обработки больших объёмов данных), кэширование может стать спасением:

@Cacheable(value = "calculationCache", key = "#input")   public CalculationResult calculateHeavyTask(InputData input) {       // Затратная операция   }  
  1. Взаимодействие с внешними API
    Помимо FeignClient, кэширование отлично подходит для любого взаимодействия с внешними сервисами, чтобы минимизировать сетевую нагрузку.

  2. Часто используемые настройки или метаданные
    Если ваш сервис часто запрашивает статические данные (например, конфигурации, справочники или метаданные), кэширование ускоряет доступ:

@Cacheable(value = "configCache")   public Config getConfig() {       return loadConfigFromFileOrDatabase();   }  
  1. Работа с асинхронными потоками
    Для асинхронных операций кэширование также может быть настроено через асинхронный бин Caffeine.

Вывод

Кэширование — это универсальный инструмент, который работает везде, где требуется оптимизация доступа к данным. Главное — правильно определить, какие данные действительно стоит кэшировать, и выбрать подходящую стратегию.


ссылка на оригинал статьи https://habr.com/ru/articles/860120/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *