#spring #spring-boot #caching
#spring #spring-boot #кэширование
Вопрос:
Я использую Spring Cache @CacheEvict
amp; @Cacheable
В настоящее время я запускаю планировщик каждый час, чтобы очистить кеш, и в следующий раз, когда вызывается fetchUser(), он извлекает данные из внешнего API и добавляет в кеш.
@Scheduled(cron = "0 0 * * * *}")
@CacheEvict(value = "some-unique-value", allEntries = true)
public void clearUserCache() {
log.info("Cache cleared");
}
@Cacheable(value = "some-unique-value", unless = "#result.isFailure()")
@Override
public Result<UserResponse> fetchUser() {
try {
UserResponse userResponse = api.fetchUserDetail();
return Result.success(userResponse);
} catch (Exception e) {
return Result.failure(INTERNAL_SERVER_ERROR);
}
}
Теперь нам нужно очищать кеш только при успешном вызове пользовательского API. Есть ли способ сделать это.
Поскольку теперь кэш очищается по расписанию, и предположим, что внешний вызов API завершается с ошибкой. Основной API вернет ответ с ошибкой. В этом случае я должен иметь возможность использовать сам существующий кеш.
Комментарии:
1. При удалении кэша вы не выполняете никаких вызовов API, чтобы явно проверить успех или неудачу вызова API. Как бы вы зафиксировали его статус? Используя метод вызова API,
@Cacheable
это означает, что он будет считываться из кэша, он не будет выполнять фактический вызов Rest. Я прав?2. Планировщик не является триггерной точкой для вызова пользовательского API. Он будет вызван из службы с использованием userAPI и только тогда, когда эта служба запускается внешней системой.
Ответ №1:
Если я правильно понял, почему бы вам не вызвать его как обычный метод после проверки правильности вызова API у родительского элемента этого метода?
В вашем коде что-то вроде
// we just leave scheduled here as you need it.
@Scheduled(cron = "0 0 * * * *}")
@CacheEvict(value = "some-unique-value", allEntries = true)
public void clearUserCache() {
log.info("Cache cleared");
}
@Cacheable(value = "some-unique-value", unless = "#result.isFailure()")
@Override
public Result<UserResponse> fetchUser() {
try {
UserResponse userResponse = api.fetchUserDetail();
return Result.success(userResponse);
} catch (Exception e) {
return Result.failure(INTERNAL_SERVER_ERROR);
}
}
public void parentMethod() {
Result<UserResponse> userResult = this.fetchUser();
if(userResult.isFailure()) {
this.clearUserCache();
}
}
Таким образом, если какой-либо из них Exception
будет выдан, он вернется со failure
статусом, и вы сможете его проверить. Таким образом, кеш будет очищаться либо каждый час, либо когда он не работал.
Поэтому в следующий раз, поскольку это был сбой и кэша нет, он повторит попытку.
Комментарии:
1. Просто небольшое отклонение. Единственная загвоздка в том, что вы никогда не достигнете «this.clearUserCache();», если кеш уже установлен. Spring вернет прокси-объект вместо вызова фактического метода.
2. Также требуется планировщик, поскольку данные должны обновляться после каждого Hr любым последующим вызовом API.
3. Я исправил свой ответ. Родительский сервер вызовет,
this.fetchUser()
и если предыдущая итерация не сработала или прошел час, он снова вызовет метод. Или вам нужно сверить его с текущей итерацией?4. 2 отклонения 1. Если кэш очищается при каждом Hr и UserAPI не работает, общий ответ не будет выполнен. Что противоречит цели. 2. parentMethod() не должен быть недействительным и проверять, является ли if(!userResult.isFailure()) { я думаю, вы пропустили «not». Но в целом это соответствует тому, что я реализовал. скоро будет публикация. Спасибо.
Ответ №2:
Я не нашел никакой прямой реализации, но с обходным путем я смог это сделать.
Пример использования
- Ответ пользовательского API должен обновляться только при запуске следующего вызова службы, который использует пользовательский API. Он не должен обновляться планировщиком. Поскольку нам нужно передавать информацию заголовка, поступающую из внешней системы, также в пользовательский API.
- Кэш должен быть очищен только при успешном ответе пользовательского API.
Шаги:
- Добавлена переменная в планировщик и включение ее
ON
по расписанию иOFF
при обновлении кеша. - Этот флаг используется в
UserService
классе для проверки, был ли запущен планировщик или нет. - Если нет, используйте кеш. Если
true
, запустите вызов пользовательского API. Проверьте наличие ответа, если он успешный. ЗапуститеCacheEvict
метод и обновите кеш.
Пример кода:
SchedulerConfig
private boolean updateUserCache;
@Scheduled(cron = "${0 0 * * * *}") // runs every Hr
public void userScheduler() {
updateUserCache = true;
log.info("Scheduler triggered for User");
}
@CacheEvict(value = "USER_CACHE", allEntries = true)
public void clearUserCache() {
updateUserCache = false;
log.info("User cache cleared");
}
public boolean isUserCacheUpdateRequired() {
return updateUserCache;
}
Пользовательский сервис
UserResponse userResponse = null;
if (schedulerConfig.isUserCacheUpdateRequired()) {
userResponse = userCache.fetchUserDetail();
if (userResponse != null) {
// clear's cache and userResponse is stored in cache automatically when getUserDetail is called below
schedulerConfig.clearUserCache();
}
}
return userCache.getUserDetail(userResponse);
Кэш пользователя
@Cacheable(value = "USER_CACHE", key = "#root.targetClass", unless = "#result.isFailure()")
public Result<User> getUserDetail(UserResponse userResponse) {
try {
if (userResponse == null) { // handle first time trigger when cache is not available
userResponse = fetchUserDetail(); // actual API call
}
return Result.success(mapToUser(userResponse));
} catch (Exception e) {
return Result.failure("Error Response");
}
}
Примечание:
- Результатом является пользовательская оболочка, предположим, что это объект с атрибутами успеха или неудачи
- Мне пришлось добавить
@Cacheable
часть как отдельный компонент, потому что кэширование работает только с прокси-объектами. Если я сохраняю егоgetUserDetail
внутриUserService
и вызываю напрямую, он не был перехвачен, поскольку прокси-сервер и логика кэша не работают, вызов API запускается каждый раз. - Самое главное: это не лучшее решение, и его можно улучшить.
Комментарии:
1. выглядит немного сложно.. вы сталкивались с каким-либо другим решением?