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