Spring Cache: от входа до настоящего аромата

Java

прогулочная доска с полостью отходов

西湖

Это когда два дня назад я пошел играть на Западное озеро, и это было время, когда солнце садилось. Итак, мы вдвоем сидели на берегу Западного озера, тихо наблюдая за закатом, ожидая, пока небо потемнеет, и ожидая, когда загорятся огни.

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

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

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

Сегодняшняя статья немного больше «учебная». Но я также проанализировал исходный код от мелкого к более глубокому и представил некоторые общие проблемы и решения при использовании Spring Cache.Он определенно более глубокий, чем простой вводный документ.Я думаю, что после его прочтения будут некоторые награда.

Зачем использовать кеш

Несколько дней назад в статье «Как я оптимизировал 15-минутную программу до 10 секунд» я упомянул некоторые методы оптимизации производительности на уровне кода. Первым из них является использование кешей.

Использование кеша — это очень «рентабельная» оптимизация производительности, особенно для программ с большим количеством повторяющихся запросов. Вообще говоря, в серверных веб-приложениях есть два места, которые занимают много времени: одно — проверка базы данных, а другое — вызов API других служб (поскольку другим службам в конечном итоге потребуется проверить базу данных). и др.) работают).

Есть также два типа повторных запросов. Один из них заключается в том, что мы плохо пишем код в приложении, пишемforЦикл, может использовать повторяющиеся параметры для запроса каждого цикла. В этом случае более умные программисты будут рефакторить этот код, и использовать Map для временного хранения найденных вещей в памяти.Прежде чем потом запрашивать, проверьте, есть ли что-то в Map, а затем проверьте базу данных. идеи кэширования.

Другой тип повторяющегося запроса — это результат большого количества идентичных или похожих запросов. Например, список статей на главной странице информационных сайтов, список товаров на главной странице сайтов электронной коммерции и статьи в социальных сетях, таких как Weibo и т. д., когда большое количество пользователей запрашивают один и тот же интерфейс и одни и те же данные, если они каждый раз проверяют базу данных, то это невыносимая нагрузка на базу данных. Поэтому мы обычно кэшируем высокочастотные запросы, которые мы называем «горячими точками».

Зачем использовать Spring Cache

Как упоминалось ранее, у кэширования есть много преимуществ, поэтому все готовятся добавить функции кэширования в свои приложения. Однако поиск в Интернете обнаружил, что существует слишком много фреймворков кэширования, каждый со своими преимуществами, например Redis, Memcached, Guava, Caffeine и так далее.

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

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

подумай об этомAOPПрименимый сценарий , разве это не то, что АОП должен делать по своей природе?

Что касается Spring AOP, вы можете обратиться к моей предыдущей статье «Десять вопросов о душе Spring AOP».

Да, Spring Cache является одним из таких фреймворков. Он использует АОП для реализации функции кэширования на основе аннотаций и имеет разумную абстракцию.Бизнес-коду не нужно заботиться о том, какая структура кэширования используется на нижнем уровне.Для реализации функции кэширования достаточно просто добавить аннотацию. И Spring Cache также предоставляет множество настроек по умолчанию, пользователи могут использовать очень хорошую функцию кэширования за 3 секунды.

Раз есть такие хорошие колеса, то почему бы их не использовать?

Как использовать Spring кэш

Вышеуказанные 3 секунды точно не преувеличение. Использование SpringCache разделено на три простых шага: добавление зависимостей, включение кэширования и добавление аннотаций кэша.

Пример кода в этой статье использует официальный пример кода, адрес git:GitHub.com/spring — Положения…

1 плюс зависимость

градиент:

implementation 'org.springframework.boot:spring-boot-starter-cache'

maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2 Включить кеш

В классе запуска добавьте@EnableCachingАннотировать, чтобы включить использование кэширования.

@SpringBootApplication
@EnableCaching
public class CachingApplication {

	public static void main(String[] args) {
		SpringApplication.run(CachingApplication.class, args);
	}

}

3 Добавьте аннотации кэша

Добавьте выше метод для кэширования@CacheableАннотация, вы можете кэшировать возвращаемое значение этого метода.

@Override
@Cacheable("books")
public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
}

// Don't do this at home
private void simulateSlowService() {
    try {
        long time = 3000L;
        Thread.sleep(time);
    } catch (InterruptedException e) {
        throw new IllegalStateException(e);
    }
}

контрольная работа

@Override
public void run(String... args) {
    logger.info(".... Fetching books");
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
    logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
}

Протестируйте и узнайте. Первый и второй (второй параметр отличается от первого) вызовgetByIsbnметод, он будет ждать 3 секунды, а следующие четыре вызова вернутся немедленно.

Общие примечания

Spring Cache имеет несколько общих аннотаций, а именно@Cacheable,@CachePut,@CacheEvict,@Caching,@CacheConfig. За исключением последнего CacheConfig, остальные четыре можно использовать на уровне класса или метода. Если они используются в классе, они повлияют на все общедоступные методы класса. Эти аннотации описаны ниже.

@Cacheable

@CachebleВ аннотации указано, что данный метод имеет функцию кеширования, а возвращаемое значение метода будет закэшировано.Перед вызовом метода в следующий раз он проверит, есть ли уже значение в кеше.Если есть, то вернет напрямую без вызова метода. Если нет, вызовите метод и кэшируйте результат. эта аннотацияОбычно используется в методах запроса.

@CachePut

добавлять@CachePutАннотированный метод поместит возвращаемое значение метода в кеш и кеширует его для использования в других местах. ЭтоОбычно используется для добавления методов.

@CacheEvict

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

@Caching

Механизм аннотаций Java определяет, что только одна из одинаковых аннотаций может воздействовать на метод. Затем иногда метод может работать с несколькими кэшами (это чаще встречается в операциях удаления кэша, реже в операциях добавления).

Разумеется, Spring Cache также учитывает эту ситуацию,@CachingАннотации используются для решения такого рода ситуаций, и каждый может понять это, взглянув на его исходный код.

public @interface Caching {
	Cacheable[] cacheable() default {};
	CachePut[] put() default {};
	CacheEvict[] evict() default {};
}

@CacheConfig

Четыре аннотации, упомянутые выше, являются широко используемыми аннотациями в Spring Cache. Каждая аннотация имеет множество настраиваемых свойств, которые мы подробно объясним в следующем разделе. Но эти аннотации обычно используются в методах, и некоторые конфигурации могут быть общими для класса.В этом случае вы можете использовать@CacheConfigТеперь это аннотация на уровне класса, которая может настраивать cacheNames, keyGenerator, cacheManager, cacheResolver и т. д. на уровне класса.

Конфигурация общих аннотаций

Для этой части нам лучше бытьОбъединить исходный кодЧтобы лучше понять, как работают эти конфигурации.

Исходный код: Когда анализировать аннотации

Этот раздел в основном посвящен анализу исходного кода, который немного неясен.Студенты, не интересующиеся исходным кодом, могут его пропустить. Но если вы хотите разобраться, как работает Spring Cache, рекомендуется взглянуть.

Вышеупомянутые аннотации @Cacheable, @CachePut, @CacheEvict и @CacheConfig имеют некоторые настраиваемые свойства. Эти свойства конфигурации доступны в абстрактном классеCacheOperationи его подклассы. Они примерно такие:

CacheOperation UML

Видя это, я должен восхищаться этим, это наследство полезно, прекрасно.

Код для разбора каждой аннотации находится вSpringCacheAnnotationParserЕго можно найти в классе, например, в методе parseEvictAnnotation, который имеет следующее предложение:

builder.setCacheWide(cacheEvict.allEntries());

В аннотации он явно называется allEntries, ноCacheEvictOperationЭто называется cacheWide? После прочтения автора, есть несколько авторов, но первый автор - приятель по имени Костин Лео, Я все еще немного смущен этим именем. . . Кажется, у больших парней тоже есть проблема с несоответствиями имен при написании кода.

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

调用链

В интерфейсе отладки вы можете видеть, что цепочка вызовов очень длинная.Фронт — это процесс регистрации bean-компонента IOC, с которым мы знакомы, пока не увидим вызов с именемAbstractAutowireCapableBeanFactoryBeanFactory, и тогда этот класс узнает, есть ли Советник при создании Бина. Бывает, что такой советник определен в исходном коде Spring Cache:BeanFactoryCacheOperationSourceAdvisor.

PointCut, возвращаемый этим советником, являетсяCacheOperationSourcePointcut, этот PointCut перезаписывает метод match и получаетCacheOperationSource, вызовите его метод getCacheOperations. этоCacheOperationSourceЯвляется интерфейсом, основным классом реализации являетсяAnnotationCacheOperationSource. В методе findCacheOperations мы будем вызывать то, что сказали в началеSpringCacheAnnotationParser.

На этом анализ на основе аннотаций завершен.

Запись: перехватчик на базе АОП

Итак, когда мы на самом деле вызываем метод, как мы с ним справляемся? Мы знаем, что bean-компонент, использующий АОП, сгенерирует прокси-объект, и когда он действительно будет вызван, он выполнит сериюInterceptor. Spring Cache используетCacheInterceptorперехватчик. Если мы добавим соответствующую аннотацию кеша, мы перейдем к этому перехватчику. Этот перехватчик наследуетCacheAspectSupportclass, выполнит метод execute этого класса, этот метод является основным методом, который мы хотим проанализировать.

@Кэшируемая синхронизация

Давайте продолжим рассмотрение упомянутого ранее метода execute, который сначала определяет, является ли он синхронным. Конфигурация синхронизации здесь заключается в использовании атрибута синхронизации @Cacheable, который по умолчанию имеет значение false. Если синхронизация настроена,Когда несколько потоков пытаются использовать один и тот же ключ для кэширования данных, это будет синхронная операция..

源码

Давайте посмотрим на исходный код синхронной операции. Если определено, что в данный момент требуется синхронная операция (1), то сначала оценивается, соответствует ли текущее условие условию (2). Условие здесь также является конфигурацией, определенной в @Cacheable, которое является выражением EL.Например, мы можем использовать это для кэширования книги с идентификатором больше 1:

@Override
@Cacheable(cacheNames = "books", condition = "#id > 1", sync = true)
public Book getById(Long id) {
    return new Book(String.valueOf(id), "some book");
}

Если он не соответствует условиям, не используйте кеш, не кладите результат в кеш и переходите сразу к 5. В противном случае попробуйте получить ключ (3). При получении ключа он сначала определит, определил ли пользователь ключ, который также является выражением EL. Если нет, используйте keyGenerator для генерации ключа:

@Nullable
protected Object generateKey(@Nullable Object result) {
    if (StringUtils.hasText(this.metadata.operation.getKey())) {
        EvaluationContext evaluationContext = createEvaluationContext(result);
        return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
    }
    return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}

Таким образом, мы можем вручную указать генерацию на основе идентификатора.book-1,book-2Такой ключ:

@Override
@Cacheable(cacheNames = "books",  sync = true, key = "'book-' + #id")
public Book getById(Long id) {
    return new Book(String.valueOf(id), "some book");
}

Ключ здесь — объект Object.Если мы не укажем ключ над аннотацией, будет использоваться ключ, сгенерированный генератором ключей. Генератор ключей по умолчаниюSimpleKeyGenerator, который генерируетSimpleKeyObject, метод тоже очень простой.Если нет входного параметра, возвращается ПУСТОЙ объект.Если есть входной параметр, а входной параметр только один, и он не пустой и не массив, используйте этот параметр ( обратите внимание, что здесь используется сам параметр, а неSimpleKeyобъект. В противном случае используйте все входные параметры для упаковки одногоSimpleKey.

Исходный код:

@Override
public Object generate(Object target, Method method, Object... params) {
    return generateKey(params);
}

/**
	 * Generate a key based on the specified parameters.
	 */
public static Object generateKey(Object... params) {
    if (params.length == 0) {
        return SimpleKey.EMPTY;
    }
    if (params.length == 1) {
        Object param = params[0];
        if (param != null && !param.getClass().isArray()) {
            return param;
        }
    }
    return new SimpleKey(params);
}

Увидев это, у вас должен возникнуть вопрос,Здесь используются только входные параметры, нет никакой разницы между именем класса и именем метода, то если входные параметры двух методов одинаковы, не конфликтует ли ключ?

Вы чувствуете себя хорошо, вы можете попробовать эти два метода:

// 定义两个参数都是String的方法
@Override
@Cacheable(cacheNames = "books", sync = true)
public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
}

@Override
@Cacheable(cacheNames = "books", sync = true)
public String test(String test) {
    return test;
}

// 调用这两个方法,用相同的参数"test"
logger.info("test getByIsbn -->" + bookRepository.getByIsbn("test"));
logger.info("test test -->" + bookRepository.test("test"));

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

Caused by: java.lang.ClassCastException: class com.example.caching.Book cannot be cast to class java.lang.String (com.example.caching.Book is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
	at com.sun.proxy.$Proxy33.test(Unknown Source) ~[na:na]
	at com.example.caching.AppRunner.run(AppRunner.java:23) ~[main/:na]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:795) ~[spring-boot-2.3.2.RELEASE.jar:2.3.2.RELEASE]
	... 5 common frames omitted

Книга не может быть приведена к String, потому что когда мы вызываем метод getByIsbn в первый раз, сгенерированный ключ является проверочным, а затем он заменяется возвращаемым значением объекта Book в кэш. При вызове тестового метода сгенерированный ключ по-прежнему является тестовым, и Книга будет извлечена, но возвращаемое значение тестового метода — String, поэтому он попытается принудительно преобразовать в String, но обнаруживается, что принудительное преобразование не удается.

Мы можем настроить keyGenerator для решения этой проблемы:

@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return target.getClass().getName() + method.getName() + 
                Stream.of(params).map(Object::toString).collect(Collectors.joining(","));
    }
}

Затем вы можете использовать этот кастом в конфигурацииMyKeyGeneratorТеперь снова запустите программу, вышеуказанная проблема не возникнет.

@Override
@Cacheable(cacheNames = "books", sync = true, keyGenerator = "myKeyGenerator")
public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
}

@Override
@Cacheable(cacheNames = "books", sync = true, keyGenerator = "myKeyGenerator")
public String test(String test) {
    return test;
}

Затем посмотрите вниз, и вы увидите, что у нас есть Тайник. Этот кеш является новым, когда мы вызываем метод выполнения CacheAspectSupport.CacheOperationContext. В методе построения этого контекста cacheResolver будет использоваться для разрешения Cache в аннотации и создания объекта Cache.

По умолчанию cacheResolverSimpleCacheResolver, он получает настроенные cacheNames из CacheOperation, а затем использует cacheManager для получения Cache. Здесь cacheManager — это контейнер, используемый для управления кешем.ConcurrentMapCacheManager. Слушая название, вы понимаете, что он основан на ConcurrentMap, а нижний слой — ConcurrentHashMap.

Так при чем здесь Кэш? Кэш — это абстракция «контейнера кеша», включая get, put, evict, putIfAbsent и другие методы, которые будет использовать кеш.

Различные cacheNames будут соответствовать разным объектам Cache.Например, мы можем определить два cacheNames в одном методе, хотя мы также можем использоватьvalue, который является псевдонимом для cacheNames, но при наличии нескольких конфигураций рекомендуется использовать cacheNames, поскольку он более удобочитаем.

@Override
@Cacheable(cacheNames = {"book", "test"})
public Book getByIsbn(String isbn) {
    simulateSlowService();
    return new Book(isbn, "Some book");
}

Кэш по умолчанию — ConcurrentMapCache, который также основан на ConcurrentHashMap.

Но здесь есть проблема: мы возвращаемся к коду метода execute выше и обнаруживаем, что если для sync установлено значение true, он берет первый Cache и игнорирует остальные Cache. Поэтому, если вы настроите синхронизацию на true, будет поддерживаться только один cacheNames. Если вы настроите более одного, будет сообщено об ошибке:

@Cacheable(sync=true) only allows a single cache on...

Продолжайте смотреть вниз и обнаружите, что вызывается метод get(Object, Callcable) Cache. Этот метод сначала попытается использовать ключ для получения значения в кеше, если нет, вызовет вызываемую функцию, а затем добавит ее в кеш. Spring Cache также ожидает, что класс реализации Cache реализует функцию «синхронизации» внутри этого метода.

Итак, вернемся и посмотримCacheableВ комментарии над атрибутом sync говорится: При использовании sync как true существуют следующие ограничения:

  1. Не поддерживает, если, как вы можете видеть из кода, поддерживает только условие, а не если; я не знаю, почему. . . Но именно так написан код Interceptor.
  2. Кэш может быть только один, потому что код записывается насмерть в одном. Я предполагаю, что это для лучшей поддержки синхронизации, она помещает синхронизацию в кэш для достижения.
  3. Никакие другие Cache операции не поддерживаются, код мертво написан, поддерживается только Cachable, думаю это тоже для поддержки синхронизации.

Прочие операции

Что делать, если синхронизация неверна?

Продолжайте смотреть на код выполнения и, возможно, выполните следующие шаги:

  1. Попробуйте удалить кеш перед вызовом метода.BeforeInvocation, настроенный в @CacheEvict, по умолчанию имеет значение false (если это правда, кеш будет удален на этом шаге);
  2. попытаться получить кеш;
  3. Если вы не можете получить его на шаге 2, попробуйте получить аннотацию Cachable и сгенерировать соответствующий CachePutRequest;
  4. Если оно получено на шаге 2 и аннотация CachPut отсутствует, значение берется напрямую из кеша. В противном случае вызовите целевой метод;
  5. Разберите аннотацию CachePut, а также сгенерируйте соответствующий CachePutRequest;
  6. Выполнить все CachePutRequest;
  7. Попробуйте удалить кеш после вызова метода, если параметр beforeInvocation, настроенный @CacheEvict, имеет значение false, кеш будет удален

До сих пор мы объясняли синхронизацию всех конфигураций вместе с исходным кодом.

Используйте другие фреймворки кэширования

Что делать, если я хочу использовать другую структуру кэширования?

Благодаря приведенному выше анализу исходного кода мы знаем, что если мы хотим использовать другие фреймворки кэширования, нам нужно только переопределитьCacheManagerиCacheResolverЭти две фасоли подойдут.

Фактически, Spring автоматически определит, внедрили ли мы соответствующую структуру кэширования.Если мы введем spring-data-redis, Spring автоматически будет использовать RedisCacheManager, RedisCache, предоставленный spring-data-redis.

Если мы хотим использовать фреймворк Caffeine. Пока представлен Caffeine, Spring Cache по умолчанию будет использовать CaffeineCacheManager и CaffeineCache.

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'

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

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

private final Caffeine caffeine = new Caffeine();

private final Couchbase couchbase = new Couchbase();

private final EhCache ehcache = new EhCache();

private final Infinispan infinispan = new Infinispan();

private final JCache jcache = new JCache();

private final Redis redis = new Redis();

Проблемы с использованием кеша

Непоследовательная двойная запись

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

Среди них мы в основном говорим о проблеме несогласованности двойной записи, которая является относительно распространенной проблемой.Одно из распространенных решений — сначала удалить кеш, а затем обновить базу данных при обновлении. Таким образом, @CacheEvict Spring Cache будет иметь конфигурацию beforeInvocation.

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

Занять дополнительную память

Это неизбежно. Потому что всегда есть место, куда положить кеш. Будь то ConcurrentHashMap, Redis или Caffeine, он всегда будет занимать дополнительные ресурсы памяти для кэширования. Но идея кэширования заключается в обмене места на время, и иногда использование этого дополнительного пространства очень полезно для оптимизации времени.

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

Смоделируем эксперимент:

@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        // 每次都生成不同的key
        return UUID.randomUUID().toString();
    }
}

//调它个100w次
for (int i = 0; i < 1000000; i++) {
    bookRepository.test("test");
}

Затем установите максимальную память на 20M:-Xmx20M.

Давайте сначала протестируем кеш на основе ConcurrentHashMap по умолчанию и обнаружим, что он скоро сообщит об OOM.

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "RMI TCP Connection(idle)"

Используем Caffeine и настраиваем его максимальную мощность:

spring:
  cache:
    caffeine:
      spec: maximumSize=100

Запустите программу еще раз и обнаружите, что она работает нормально и не сообщает об ошибке.

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

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

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

Об авторе

Я Ясин, публичный аккаунт WeChat:запрограммировал программу

Персональный сайт:yasinshaw.com

Подписывайтесь на мой официальный аккаунт и развивайтесь вместе со мной~

Существуют также учебные ресурсы, группы технического обмена и рекламные акции на крупных заводах.