Как очистить кэш spring

Обновлено: 06.07.2024

Узнайте, как аннулировать кэши с помощью Spring Boot.

1. Обзор

В этом коротком уроке мы узнаем, как мы можем выполнить удаление кэша с помощью Spring. Мы создадим небольшой пример, чтобы продемонстрировать это.

Прежде чем продолжить, ознакомьтесь с нашей статьей – Руководством по кэшированию весной, – чтобы ознакомиться с тем, как работает весеннее кэширование.

2. Как удалить кэш?

Spring предоставляет два способа удаления кэша: либо с помощью аннотации @CacheEvict для метода, либо путем автоматического подключения CacheManger и очистки его путем вызова метода clear () .

Вот как эти два механизма удаления кэша могут быть реализованы в коде.

2.1. Использование @CacheEvict

Spring перехватит все методы, аннотированные @CacheEvict , и очистит все значения на основе флага все записи |. Можно удалить значения на основе определенного ключа.

Для этого все, что нам нужно сделать, это передать ключ кэша в качестве аргумента аннотации вместо флага все записи :

Поскольку значение атрибута key является динамическим, мы можем использовать язык выражений Spring или пользовательский генератор ключей, реализуя KeyGenerator для выбора интересующих аргументов или вложенных свойств.

2.2. Использование CacheManager

Далее давайте посмотрим, как мы можем удалить кэш с помощью CacheManager , предоставленного модулем Spring Cache. Во-первых, мы должны автоматически подключить реализованный Cache Manager bean.

И тогда мы сможем очистить с его помощью тайники в зависимости от наших потребностей:

Как мы видим в коде, метод clear() очистит все записи кэша, а метод exelt() очистит значения на основе ключа .

3. Как удалить все тайники?

Spring не предоставляет готовых функций для очистки всех кэшей. Но мы можем легко достичь этого, используя метод getCacheNames() диспетчера кэша.

3.1. Выселение по требованию

Давайте теперь посмотрим, как мы можем очистить все тайники по требованию. Чтобы создать триггерную точку, мы должны сначала предоставить конечную точку:

В Службе Кэширования мы можем затем очистить все кэши , выполнив итерацию по именам кэша, полученным из диспетчера кэша :

3.2. Автоматическое Выселение

Существуют определенные случаи использования, когда удаление кэша должно выполняться автоматически через определенные промежутки времени. В этом случае мы можем использовать планировщик задач Spring :

4. Заключение

Мы научились выселять тайники разными способами. Одна из вещей, которую стоит отметить об этих механизмах, заключается в том, что он будет работать со всеми различными реализациями кэша, такими как eh-cache, infini-span, apache-ignite и т. Д.

Как всегда, все примеры, упомянутые в этом руководстве, можно найти на Github .

Мы звоним в службу федерации удостоверений, чтобы очень часто получать токены пользователей, и почти выполняем нагрузочное тестирование в службе удостоверений.

Потенциальное решение состоит в том, чтобы кэшировать пользовательские токены в существующем приложении, однако с помощью встроенного Spring-кэша, мы можем истечь отдельными записями в кэше?

В приведенном ниже примере мне удалось очистить кеш, удалив все записи, однако я пытаюсь истечь отдельные записи.

Spring-boot класс приложения выглядит так:

2 ответа

Вы можете удалить одну запись в кэше, используя @CacheEvict в методе, который принимает ваш ключ кэша. Кроме того, благодаря использованию кеша Spring и @Cacheable , нет необходимости в коде HashMap (поскольку на самом деле это просто вторичный кеш).

  1. getUserToken("Joe") -> no cache, calls API
  2. getUserToken("Alice") -> no cache, calls API
  3. getUserToken("Joe") -> cached
  4. evictUserToken("Joe") -> evicts cache for user "Joe"
  5. getUserToken("Joe") -> no cache, calls API
  6. getUserToken("Alice") -> cached (as it has not been evicted)
  7. evictAll() -> evicts all cache
  8. getUserToken("Joe") -> no cache, calls API
  9. getUserToken("Alice") -> no cache, calls API

Если вы хотите, чтобы ваши токены кэшировались в течение определенного времени, вам понадобится еще один CacheManager , кроме родного Spring. Существует множество вариантов кэширования, которые работают с Spring @Cacheable . Я приведу пример с использованием Caffeine, высокопроизводительной библиотеки кэширования для Java 8. Например, если вы знаете, что хотите кэшировать токен в течение 30 минут, вы, вероятно, захотите пойти по этому пути.

Сначала добавьте следующие зависимости к своему build.gradle (или, если используете Maven, переведите следующее и поместите его в свой pom.xml ). Обратите внимание, что вы захотите использовать последние версии или те, которые соответствуют вашей текущей версии Spring Boot.

После того как вы добавили эти две зависимости, все, что вам нужно сделать, это настроить спецификацию caffeine в вашем файле application.properties :

Измените expireAfterWrite=30m на любое значение, для которого вы хотите использовать токены. Например, если вы хотите 400 секунд, вы можете изменить его на expireAfterWrite=400s .

Spring Cache Abstraction - это абстракция, а не реализация, поэтому она вообще не поддерживает явную настройку TTL, поскольку это особенность реализации. Например, если ваш кеш поддерживается ConcurrentHashMap , он не может поддерживать TTL "из коробки".

В вашем случае у вас есть 2 варианта. Если вам нужен локальный кеш (т.е. каждый экземпляр микросервиса управляет своим собственным кешем), вы можете заменить Spring Cache Abstraction на Кофеин, который является официальной зависимостью и управляется Spring Boot. Просто заявить нужно без указания версии.

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

Опять же, это локальный кеш, что означает, что каждый микросервис будет управлять своим собственным набором токенов. Таким образом, если у вас есть 5 запущенных экземпляров одного и того же микросервиса, у одного и того же пользователя может быть 5 токенов, лежащих во всех 5 кешах, в зависимости от того, какие экземпляры обрабатывали его запросы.

С другой стороны, если вам нужен распределенный кеш (т. Е. Несколько экземпляров микросервиса используют один и тот же централизованный кеш), вам нужно взглянуть на EHCache или Hazelcast. В этом случае вы можете продолжать использовать Spring Cache Abstraction и выбрать одну из этих библиотек в качестве своей реализации, объявив CacheManager из этих библиотек (например, HazelcastCacheManager ).

Затем вы можете просмотреть соответствующую документацию для дальнейшей настройки выбранного вами CacheManager с TTL для конкретных кэшей (например, вашего tokenCache ). Я привел простую конфигурацию для Hazelcast ниже в качестве примера.

Раньше я боялся кэширования. Очень не хотелось лезть и выяснять, что это такое, сразу представлялись какие-то подкапотные люто-энтерпрайзные штуки, в которых может разобраться только победитель олимпиады по математике. Оказалось, что это не так. Кэширование оказалось очень простым, понятным и невероятно лёгким во внедрении в любой проект.


В данном посте я постараюсь объяснить о кэшировании так же просто, как это сейчас понимаю я. Вы узнаете о том, как внедрить кэширование за 1 минуту, как кэшировать по ключу, устанавливать время жизни кэша, и многие прочие штуки, которые необходимо знать, если Вам поручили закэшировать что-то в вашем рабочем проекте, и Вы не хотите ударить в грязь лицом.

Почему я говорю «поручили»? Потому что кэширование, как правило, есть смысл применять в больших, высоконагруженных проектах, с десятками тысяч запросов в минуту. В таких проектах, чтобы не перегружать базу, как правило, кэшируют обращения к репозиторию. Особенно если известно, что данные из какой-нибудь мастер-системы обновляются с некоторой периодичностью. Сами мы такие проекты не пишем, мы на них работаем. Если же проект маленький и перегрузки ему не грозят, тогда, конечно, лучше ничего не кэшировать — всегда свежие данные всегда лучше периодически обновляемых.

Обычно в обучающих постах докладчик сначала лезет под капот, начинает копаться в кишочках технологии, чем немало утомляет читателя, а уж потом, когда тот пролистал без дела добрую половину статьи и ничего не понял, повествует, как это работает. У нас всё будет иначе. Сначала мы делаем так, чтобы заработало, и желательно, с приложением наименьших усилий, а уж потом, если интересно, Вы сможете заглянуть под капот кэширования, посмотреть изнутри сам бин и тонко настроить кэширование. Но даже если Вы этого не сделаете (а это начинается с 6 пункта), Ваше кэширование будет работать и так.

Мы создадим проект, в котором разберём все те аспекты кэширования, которые я обещал. В конце, как обычно, будет ссылка на сам проект.

0. Создание проекта

Мы создадим очень простой проект, в котором мы сможем брать сущность из базы данных. Я добавил в проект Lombok, Spring Cache, Spring Data JPA и H2. Хотя, вполне можно обойтись только Spring Cache.


У нас будет только одна сущность, назовём её User.


Добавим репозиторий и сервис:


Когда мы заходим в сервисный метод get(), мы пишем об этом в лог.

Подключим к проекту Spring Cache.

1. Кэширование возвращаемого результата

Что делает Spring Cache? Spring Cache просто кэширует возвращаемый результат для определённых входных параметров. Давайте это проверим. Мы поставим аннотацию @Cacheable над сервисным методом get(), чтобы кэшировать возвращаемые данные. Дадим этой аннотации название «users» (далее мы разберём, зачем это делается, отдельно).


Для того, чтобы проверить, как это работает, напишем простой тест.

Небольшое отступление, почему я обычно пишу AbstractTest и наследую от него все тесты.

Если над классом стоит своя аннотация @SpringBootTest, для такого класса каждый раз заново поднимается контекст. Поскольку контекст может подниматься 5 секунд, а может 40 секунд, это в любом случае очень сильно тормозит процесс тестирования. При этом, разницы в контексте обычно нет никакой, и при запуске каждой группы тестов в пределах одного класса нет необходимости заново запускать контекст. Если же мы ставим только одну аннотацию, скажем, над абстрактным классом, как в нашем случае, это позволяет поднимать контекст только один раз.

Поэтому я предпочитаю сокращать количество поднимаемых контекстов при тестировании/сборке, если это возможно.

Что делает наш тест? Он создаёт двоих юзеров и потом по 2 раза вытаскивает их из базы. Как мы помним, мы поместили аннотацию @Cacheable, которая будет кэшировать возвращаемые значения. После получения объекта из метода get() мы выводим объект в лог. Также, мы выводим в лог информацию о каждом посещении приложением метода get().

Запустим тест. Вот что мы получаем в консоль.


Как мы видим, первые два раза мы действительно сходили в метод get() и реально получили юзера из базы. Во всех остальных случаях, реального захода в метод не было, приложение брало закэшированные данные по ключу (в данном случае, это id).

2. Объявление ключа для кэширования

Бывают ситуации, когда в кэшируемый метод приходит несколько параметров. В таком случае, бывает нужно определить параметр, по которому будет происходить кэширование. Добавим в пример метод, который будет сохранять в базу сущность, собранную по параметрам, но если сущность с таким именем уже есть, мы не будем её сохранять. Для этого мы определим параметр name как ключ для кэширования. Выглядеть это будет так:


Напишем соответствующий тест:


Мы попытаемся создать троих пользователей, для двоих из которых будет совпадать имя


и для двоих из которых будет совпадать email


В методе создания мы логируем каждый факт обращения к методу, а также, мы будем логировать все сущности, которые этот метод нам вернул. Результат будет таким:


Мы видим, что фактически приложение вызывало метод 3 раза, а заходило в него только два раза. Один раз для метода совпадал ключ, и он просто возвращал закэшированное значение.

3. Принудительное кэширование. @CachePut

Бывают ситуации, когда мы хотим кэшировать возвращаемое значение для какой-то сущности, но в то же время, нам нужно обновить кэш. Для таких нужд существует аннотация @CachePut. Оно пропускает приложение в метод, при этом, обновляя кэш для возвращаемого значения, даже если оно уже закэшировано.

Добавим пару методов, в которых мы будем сохранять юзера. Один из них мы пометим обычной аннотацией @Cacheable, второй — @CachePut.


По той логике, которая уже описывалась, при первом сохранении пользователя с именем «Vasya» через метод createOrReturnCached() далее мы будем получать кэшированную сущность, при этом, в сам метод приложение заходить не будет. Если же мы вызовем метод createAndRefreshCache(), кэшированная сущность для ключа с именем «Vasya» перезапишется в кэше. Выполним тест и посмотрим, что будет выведено в консоль.


Мы видим, что user1 благополучно записался в базу и кэш. При повторной попытке записать юзера с таким же именем мы получаем закэшированный результат выполнения первого обращения (user2, для которого id такой же, как у user1, что говорит нам о том, что юзер не был записан, и это просто кэш). Далее, мы пишем третьего пользователя через второй метод, который даже при имеющемся закэшированном результате всё равно вызвал метод и записал в кэш новый результат. Это user3. Как мы видим, у него уже новый id. После чего, мы вызываем первый метод, который берёт новый кэш, добавленный user3.

4. Удаление из кэша. @CacheEvict

Иногда возникает необходимость жёстко обновить какие-то данные в кэше. Например, сущность уже удалена из базы, но она по-прежнему доступна из кэша. Для сохранения консистентности данных, нам необходимо хотя бы не хранить в кэше удалённые данные.

Добавим в сервис ещё пару методов.


Первый будет просто удалять пользователя, второй тоже будет его удалять, но мы пометим его аннотацией @CacheEvict. Добавим тест, который будет создавать двух юзеров, после чего, одного будет удалять через простой метод, а второго — через аннотируемый метод. После чего, мы достанем этих юзеров через метод get().


Логично, что раз наш юзер уже закэширован, удаление не помешает нам его как бы получить — ведь он закэширован. Посмотрим логи.


Мы видим, что приложение благополучно сходило оба раза в метод get() и Spring закэшировал эти сущности. Далее, мы удалили их через разные методы. Первый мы удалили обычным путём, и закэшированное значение осталось, поэтому когда мы попытались получить юзера под id 1, нам это удалось. Когда же мы попытались получить юзера 2, метод вернул нам EntityNotFoundException — такого юзера в кэше не оказалось.

5. Группировка настроек. @Caching

Иногда один метод требует нескольких настроек кэширования. Для этих целей используется аннотация @Caching. Выглядеть это может приблизительно так:


Это единственный способ группировать аннотации. Если Вы попытаетесь нагородить что-то вроде


то IDEA сообщит Вам, что так нельзя.

6. Гибкая настройка. CacheManager

Наконец-то мы разобрались с кэшем, и он перестал быть для нас чем-то непонятным и страшным. Теперь давайте заглянем под капот и посмотрим, как мы можем настроить кэширование в целом.

Для таких задач существует CacheManager. Он существует везде, где есть Spring Cache. Когда мы добавили аннотацию @EnableCache, такой кэш менеджер автоматически будет создан Spring. Мы можем убедиться в этом, если заавтовайрим ApplicationContext и вскроем его на брейкпоинте. Среди прочих бинов, будет и бин «cacheManager».


Я остановил приложение на этапе, когда уже два юзера были созданы и помещены в кэш. Если мы вызовем нужный нам бин через Evaluate Expression, то мы увидим, что такой бин действительно есть, в нём есть ConcurentMapCache с ключом «users» и значением ConcurrentHashMap, в которой уже лежат закэшированные юзеры.


Мы, в свою очередь, можем создать свой кэш-менеджер, с хабром и программистами, после чего, тонко настроить его на наш вкус.


Осталось только выбрать, какой именно кэш-менеджер мы будем использовать, потому что их предостаточно. Я не буду перечислять все кэш-менеджеры, достаточно будет знать, что есть такие:

  • SimpleCacheManager — самый простой кэш-менеджер, удобный для изучения и тестирования.
  • ConcurrentMapCacheManager — лениво инициализирует возвращаемые экземпляры для каждого запроса. Также рекомендуется для тестирования и изучения работы с кэшем, а также, для каких-то простых действий, вроде наших. Для серьёзной работы с кэшем рекомендуются имплементации ниже.
  • JCacheCacheManager, EhCacheCacheManager, CaffeineCacheManager — серьёзные кэш-менеджеры «от партнёров», гибко настраиваемые и выполняющие задачи очень широкого спектра действия.

Итак, досоздадим наш кэш-менеджер.


Наш кэш-менеджер готов.

7. Настройка кэша. Время жизни, максимальный размер и проч.

Для этого нам потребуется довольно популярная библиотека Google Guava. Я взял последнюю.


При создании кэш-менеджера переопределим метод createConcurrentMapCache, в котором вызовем CacheBuilder от Guava. В процессе нам будет предложено настроить кэш-менеджер при помощи инициализации следующих методов:

  • maximumSize — максимальный размер значений, которые может содержать кэш. При помощи этого параметра можно найти попытаться найти компромисс между нагрузкой на базу данных и на оперативную память JVM.
  • refreshAfterWrite — время после записи значения в кэш, после которого оно автоматически обновится.
  • expireAfterAccess — время жизни значения после последнего обращения к нему.
  • expireAfterWrite — время жизни значения после записи в кэш. Именно этот параметр мы определим.

Определим в менеджере время жизни записи. Чтобы долго не ждать, выставим 1 секунду.


Напишем соответствующий такому случаю тест.


Мы сохраняем несколько значений в базу данных, причём, если данные закэшированы, мы ничего не сохраняем. Сначала мы сохраняем два значения, потом ожидаем 1 секунду, пока кэш не протухнет, после чего, сохраняем ещё одно значение.


Логи показывают, что сначала мы создали юзера, потом попытались ещё одного, но поскольку данные были закэшированы, мы получили их из кэша (в обоих случаях — при сохранении и при получении из базы). Потом протух кэш, о чём сообщает нам запись о фактическом сохранении и фактическом получении юзера.

8. Подведу итог

Рано или поздно, разработчик сталкивается с необходимостью реализации кэширования в проекте. Я надеюсь, что эта статья поможет Вам разобраться в предмете и смотреть на вопросы кэширования смелее.

В этом уроке освещается процесс включения кеширования Spring бина.

Что вы создадите

Вы создадите приложение, которое позволяет кешировать простой репозиторий книг.

Что вам потребуется

  • Примерно 15 минут свободного времени
  • Любимый текстовый редактор или IDE и выше
  • Gradle 1.11+ или Maven 3.0+
  • Вы также можете импортировать код этого урока, а также просматривать web-страницы прямо из Spring Tool Suite (STS), собственно как и работать дальше из него.

Как проходить этот урок

Как и большинство уроков по Spring, вы можете начать с нуля и выполнять каждый шаг, либо пропустить базовые шаги, которые вам уже знакомы. В любом случае, вы в конечном итоге получите рабочий код.

Чтобы начать с нуля, перейдите в Настройка проекта.

Когда вы закончите, можете сравнить получившийся результат с образцом в gs-caching/complete .

Настройка проекта

Для начала вам необходимо настроить базовый скрипт сборки. Вы можете использовать любую систему сборки, которая вам нравится для сборки проетов Spring, но в этом уроке рассмотрим код для работы с Gradle и Maven. Если вы не знакомы ни с одним из них, ознакомьтесь с соответсвующими уроками Сборка Java-проекта с использованием Gradle или Сборка Java-проекта с использованием Maven.

Создание структуры каталогов

В выбранном вами каталоге проекта создайте следующую структуру каталогов; к примеру, командой mkdir -p src/main/java/hello для *nix систем:

Создание файла сборки Gradle

Ниже представлен начальный файл сборки Gradle. Файл pom.xml находится здесь. Если вы используете Spring Tool Suite (STS), то можете импортировать урок прямо из него.

Если вы посмотрите на pom.xml , вы найдете, что указана версия для maven-compiler-plugin. В общем, это не рекомендуется делать. В данном случае он предназначен для решения проблем с нашей CI системы, которая по умолчанию имеет старую(до Java 5) версию этого плагина.

Spring Boot gradle plugin предоставляет множество удобных возможностей:

  • Он собирает все jar'ы в classpath и собирает единое, исполняемое "über-jar", что делает более удобным выполнение и доставку вашего сервиса
  • Он ищет public static void main() метод, как признак исполняемого класса
  • Он предоставляет встроенное разрешение зависимостей, с определенными номерами версий для соответсвующих Spring Boot зависимостей. Вы можете переопределить на любые версии, какие захотите, но он будет по умолчанию для Boot выбранным набором версий

Создание репозитория

Для начала давайте создадим очень простую модель для вашей книги.

И репозиторий для этой модели:

Вы могли бы использовать Spring Data для предоставления реализации к вашему репозиторию для широкого выбора SQL или NoSQL хранилищ, но для наглядности данного урока вы будете использовать простейшую реализацию, которая симулирует некоторую задержку.

Метод simulateSlowService специально вставлен для задержки в пять секунд в каждом вызове getByIsbn .

Использование репозитория

Инициализируйте репозиторий и используйте его для доступа к некоторым книгам:

Если вы попытаетесь запустить приложение на данном этапе, то вы заметите, как оно работает достаточно медленно, даже с учетом того, что вы получаете одну и ту же книгу.

Как видно по времени, каждая книга возвращается через пять секунд, не смотря на то, что вы её уже получали.

Включение кеширования

Давайте включим кеширование в вашем SimpleBookRepository так, чтобы книги были закешированы в books кэше.

Теперь вам необходимо включить обработку аннотаций кеширования:

Аннотация @EnableCaching запускает post processor, который проверяет каждый Spring бин на наличие аннотаций кеширования для public методов. Если такая аннотация найдена, то автоматически созданный прокси перехватает вызов метода и обрабатывает в соответствии споведением кеширования.

Cacheable , CachePut и CacheEvict являются аннотациями, которыми управляет вышеописанный post processor. Более подробную информацию смотрите в документации.

В основе своей, аннотации требуется CacheManager как поставщика соответствующего кэша. В данном примере вы используете реализацию, которая возвращает ConcurrentHashMap . Интерфейс CachingConfigurer предоставляет более расширенные варианты настройки.

Сборка исполняемого JAR

Вы можете собрать единый исполняемый JAR-файл, который содержит все необходимые зависимости, классы и ресурсы. Это делает его легким в загрузке, версионировании и развертывании сервиса как приложения на протяжении всего периода разработки, на различных средах и так далее.

Затем вы можете запустить JAR-файл:

Если вы используете Maven, вы можете запустить приложение, используя mvn spring-boot:run , либо вы можете собрать приложение с mvn clean package и запустить JAR примерно так:

Процедура, описанная выше, создает исполняемый JAR. Вы также можете вместо него собрать классический WAR-файл.

Если вы используете Gradle, вы можете запустить ваш сервис из командной строки:

Если вы используете Maven, то можете запустить ваш сервис таким образом: mvn clean package && java -jar target/gs-caching-0.1.0.jar .

Как вариант, вы можете запустить ваш сервис напрямую из Gradle примерно так:

Тестирование приложения

Теперь, когда кеширование включено, вы можете снова запустить приложение и увидеть разницу с дополнительными вызовами или без с теми же isbn. Разница должна быть огромной:

Эти данные из консоли показывают, что первый запрос книги по каждому из isbn занимает пять секунд, а последующие вызовы выполняются почти мгновенно.

Читайте также: