Spring Boot интегрирует Spring Cache и имеет несколько реализаций кэша, таких как Redis, Caffeine, Jcache, Ehcache и т. Д. Но если вы используете только один кэш, либо он будет иметь большое потребление сети (например, Redis), либо будет слишком много использования памяти (например, кеш памяти приложения кофеина). Во многих сценариях кэши первого и второго уровня могут быть объединены для достижения крупномасштабного улучшения в эффективности обработки применения.
Описание контента:
К простому пониманию, кэш заключается в чтении данных с более медленного числа средств для чтения и размещению их на среде с более быстрым чтением, таким как диск-> память. Обычно мы храним данные на диске, такие как: база данных. Если вы читаете его из базы данных каждый раз, сам диск повлияет на скорость чтения, поэтому будет кеш памяти, такой как Redis. Вы можете прочитать данные и поместить их в память, чтобы, когда вам необходимо получить данные, вы можете напрямую получить данные из памяти и вернуть их, что может значительно улучшить скорость. Однако, как правило, Redis развертывается отдельно в кластер, поэтому в сети в IO будет потребление. Несмотря на то, что есть инструменты объединения соединений для ссылки на кластер Redis, в передаче данных все еще будет некоторое потребление. Таким образом, есть кеш в приложении, такой как: кофеин. Когда есть данные, которые соответствуют критериям в кэше приложения, их можно использовать непосредственно без необходимости получить их через сеть в REDIS, создавая двухуровневый кэш. Кэш в приложении называется кэшем первого уровня, а удаленный кеш (например, Redis) называется кэшем второго уровня
Весенний кеш
При использовании кэша следующий процесс обычно является следующим:
Из блок -схемы можно увидеть, что для использования кэша было добавлено множество операций кэша на основе исходной бизнес -обработки. Если они связаны с бизнес -кодом, при разработке будет много повторяющихся работ, и это не способствует пониманию бизнеса на основе кода.
Spring Cache-это кэш-компонент, предоставляемый в пакете Spring-Context на основе аннотации. Это определяет некоторые стандартные интерфейсы. Реализуя эти интерфейсы, кэш может быть достигнут путем добавления аннотаций в метод. Это позволит избежать проблемы того, как код кэша в сочетании с бизнес -обработкой. Реализация пружинного кеша является расширением инкапсуляции интерфейса метода (MethodInterceptor) в пружине AOP. Конечно, Spring AOP также реализуется на основе аспекта.
Существует два основных интерфейса пружинного кеша: кэш и кэхханагер
Кэш Интерфейс
Предоставьте конкретные операции в кешах, такие как введение, чтение и очистка кэши. Реализации, предоставленные Spring Framework:
За исключением Reviscache, который находится в пакете Spring-Data-Redis, другие в основном находятся в пакете Spring-Context-поддержки.
#Cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interface cache {// cachename, имя кэша. В реализации по умолчанию Cachemanager проходит Cachename при создании бобов кеша. String getName (); // Получить фактический кеш, например: redistemplate, com.github.benmanes.caffeine.cache.cache <объект, объект>. Я еще не нашел фактического использования. Я могу просто предоставить бобы, которые получают собственный кеш, чтобы необходимо расширить некоторые операции или статистику кэша. Object getNativeCache (); // Получить значение кэша через ключ, обратите внимание, что возвращаемое значение является valuewrapper. Чтобы быть совместимым с нулевыми значениями, возвращаемое значение обернуто в слое, а фактическое значение получается с помощью метода get valuewrapper get (ключ объекта); // Получить значение кэша через ключ, который возвращает фактическое значение, то есть тип возвращаемого значения метода <T> t get (ключ объекта, класс <t> type); // Получить значение кэша через ключ, вы можете использовать valueloader.call (), чтобы вызвать метод, используя @cachable Annotation. Используйте этот метод, когда атрибут синхронизации аннотации @Cachable настроен на TRUE. Следовательно, синхронизация источника возврата в базу данных должна быть обеспечена в методе. Избегайте большого количества запросов, чтобы вернуться к источнику в базу данных при сбое кэша. <t> t get (объект клавиша, Callible <t> Valuelauder); // Поместите данные, возвращаемые методом аннотации @Cachable в кэш void put (ключ объекта, значение объекта); // Поместите кэш только тогда, когда нет ключа в кэше. Возвращаемое значение - это исходные данные, когда ключ существует valuewrapper putifabsent (ключ объекта, значение объекта); // Удалить кеш void eailt (ключ объекта); // Удалить все данные в кэше. Следует отметить, что в конкретной реализации только все данные, кэшированные с использованием @cachable аннотации, удаляются и не влияют на другие кэши в приложении void clear (); // Обворачивания кэша возвращаемого значения значения valueWrapper {// возвращать фактический объект кэшированного объекта get (); } // Когда исключение брошено {@link #get (object, callable)}, оно будет завершено как это исключение, которое брошено @suppresswarnings ("serial") class valueretrevalexception extends runtimeexception {private final Fool Object; public valuerErieValexception (ключ объекта, Callable <?> Loader, Throwable ex) {super (string.format («значение для клавиши»%s 'не может быть загружен с использованием «%s'», key, loader), ex); this.key = key; } public Object getKey () {return this.key; }}}Cachemanager Interface
В основном он обеспечивает создание бобов реализации кэша. Каждое приложение может изолировать кэш через Cachename, и каждое кахеновое имя соответствует реализации кэша. Реализация, предоставленная платформой Spring и реализацией кэша, появляется в парах, а структура пакета также находится на рисунке выше.
#Cachemanager.javapackage org.springframework.cache; import java.util.collection; открытый интерфейс cachemanager {// Создание бобов реализации кэша через кахенам. Конкретная реализация должна хранить созданные бобы реализации кэша, чтобы избежать повторного создания, а также избежать ситуации, когда исходное содержание кэша теряется после того, как объект кеша памяти (например, кофеин) воссоздан Cache GetCache (String name); // вернуть все коллекции Cachename <string> getCachenames ();}Общие аннотации
@Cachable: в основном применяется к методу запроса данных
Пакет org.springframework.cache.annotation; импорт java.lang.annotation.documented; импорт java.lang.annotation.elementtype; импорт java.lang.annotation.inherited; импорт java.lang.annotation.retention; импорт java.lang.nantation.retation java.lang.annotation.target; import java.util.concurrent.callable; import org.springframework.core.annotation.aliasfor; @Target ({elementType.method, letryType.type})@rewation (neampolicy.runtime)@inhertype.type})@rewation (repentionpolicy.runtime)@inhorepeablepail Cachenames, Cachemanager создает соответствующую программу реализации кэша через это имя @aliasfor ("cachenames") string [] value () default {}; @Aliasfor ("value") string [] cachenames () default {}; // Кэш -ключ, поддерживает выражения SPEL. По умолчанию - это объект, обернутый всеми параметрами, и их хэшкод (SimpleKey) String Key () по умолчанию ""; // генератор ключей кэша, реализация по умолчанию - простой кейпенратор string keygenerator () по умолчанию ""; // Укажите, какой CachEmanager использовать String cachEmanager () по умолчанию ""; // кэш -анализатор строки cacheresolver () default ""; // Состояние кэша, поддерживает экспрессию SPE, и данные кэша только тогда, когда удовлетворяют удовлетворительные условия. String Condity () по умолчанию "" будет оцениваться до и после вызова метода; // Кэш не обновляется при выполнении условия, выражение SPEL поддерживается, а строка, если () по умолчанию «» «осуждается только после вызова метода; // При возврате к источнику к фактическому методу для получения данных, будь то синхронизировать? Если false, метод cache.get (key) называется; Если true, метод cache.get (key, callable) называется Boolean sync () по умолчанию false;} @Cacheevict: очистить кэш, в основном применяемый к методу удаления данных. Есть два больше свойств, чем кэшируемые
Пакет org.springframework.cache.annotation; импорт java.lang.annotation.documented; импорт java.lang.annotation.elementtype; импорт java.lang.annotation.inherited; импорт java.lang.annotation.retention; импорт java.lang.nantation.retation java.lang.annotation.target; import org.springframework.core.annotation.aliasfor; @Target ({elementType.method, elementtype.type})@retented (retentionpolicy.runtime)@unheret@incordyedpublic @Interface cache @Cachable // Стоит ли очистить все кэшированные данные, метод cache.evict (key) вызывается, когда false; Когда true, метод cache.clear () называется Boolean allentries () по умолчанию false; // Очистить кэш до или после вызова метода BooleaneNvocation () по умолчанию false;}Spring Cache был интегрирован в Spring Boot и обеспечивает различные конфигурации кэша. При его использовании вам нужно только настроить, какой кэш (enum cachetype) использовать.
В Spring Boot добавлена дополнительная удлинительная вещь, которая является интерфейсом CachemanagerCustomizer. Вы можете настроить этот интерфейс, а затем сделать несколько настроек для Cachemanager, например:
пакет com.itopener.demo.cache.redis.config; import java.util.map; import java.util.concurrent.concurrenthashmap; импорт org.springframework.boot.autoconfigure.cache.cachemanagercustomizer; imporm.springframework.data.cree.cachemanagustomizer; import.springframeWor Rediscachemanagercustomizer реализует Cachemanagercustomizer <rediscachemanager> {@override public void raild (rediscachemanager cachemanager) {// время истечения срока по умолчанию, единица секунды Cachemanager.setDefaultexpiration (1000); cachemanager.setuseprefix (false); Map <string, long> истекает = new concurrenthashmap <string, long> (); истекать. cachemanager.setexpires (истекает); }}Загрузите этот боб:
пакет com.itopener.demo.cache.redis.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration;/** * @author fuwei.deng * @date 22 декабря 2017 г. Cacheredisconfiguration {@bean public rediscachemanagercustomizer resiscachemanagercustomizer () {return new rediscachemanagercustomizer (); }}Обычно используемый кэш - Redis. Redis реализует интерфейс Spring Cache в пакете Spring-Data-Redis.
Вот некоторые из недостатков в реализации Rediscache, я думаю:
1. В тот момент, когда кэш не удается, если поток получает данные кэша, он может вернуть NULL. Причина в том, что следующие шаги представлены в реализации RediScache:
Следовательно, когда кэш не удается после суждения, что ключ существует, а затем получение кэша не имеет данных, он возвращает NULL.
2. Атрибут (CachenullValues), которому разрешено хранить нулевые значения в rediscachemanager, является ложным по умолчанию, то есть не разрешено хранить нулевые значения, которые будут рисковать проникновением кэша. Дефект заключается в том, что это свойство имеет конечный тип, и объект может быть создан только тогда, когда объект создается с помощью метода конструктора. Поэтому, чтобы избежать проникновения в кеш, вы можете объявить только Rediscachemanager Bean в приложении.
3. Они могут быть установлены только на интерфейсе CachemanagerCustomizer. Я лично думаю, что это не удобно.
Кофеин-это высокопроизводительный кэш памяти, основанный на концепции дизайна Google Google Guava. Он разработан с использованием Java 8. После весеннего загрузки введена кофеин, интеграция гуавы постепенно заброшена. Код кофеина и адрес введения: кофеин
Кофеин предоставляет различные стратегии заполнения кеша и стратегии переработки стоимости, а также включает в себя статистику, такие как кеш -хиты, которые могут оказать большую помощь в оптимизации кэша.
Для введения кофеина, пожалуйста, см.
Здесь мы кратко говорим о следующих типах стратегий переработки кофеина:
Вначале я упоминал, что даже если используется кэш Redis, при передаче сети будет определенная степень потребления. В реальных приложениях будут некоторые данные с очень низкими изменениями, которые можно кэшировать непосредственно в приложении. Для некоторых данных с меньшими требованиями в режиме реального времени их также можно кэшировать в пределах приложения в течение определенного периода времени, чтобы уменьшить доступ к REDIS и улучшить скорость отклика
Поскольку Redis имеет некоторые недостатки в реализации Spring Cache в рамках Spring-Data-Redis, при его использовании могут возникнуть некоторые проблемы, поэтому мы не будем расширять ее на основе исходной реализации. Мы будем напрямую ссылаться на метод реализации для реализации интерфейсов Cache и Cachemanager
Следует также отметить, что, как правило, приложения развертывают несколько узлов, а кэш первого уровня является кэшем в приложении, поэтому, когда данные обновляются и очищаются, все узлы должны быть уведомлены для очистки кэша. Существует много способов достичь этого эффекта, таких как: Zookeeper, MQ и т. Д., Но, поскольку используется кэш Redis, сам Redis поддерживает функции подписки/публикации, поэтому он не полагается на другие компоненты. Он напрямую использует канал Redis, чтобы уведомить другие узлы для очистки операций кэша.
Ниже приведены шаги стартовой инкапсуляции и исходный код для пружинного кеша пружины для реализации двухуровневого кэша (Redis + Caffeine)
Определите свойства свойств свойств
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; импорт java.util.hashmap; import java.util.hashset; import java.util.map; import java.til.set; импорт org.spramework.boot.properties fuwei.deng * @date 29 января 2018 года в 11:32:15 * @version 1.0.0 */ @configurationproperties (prefix = "spring.cache.multi") открытый класс CacherediscaffeineProperties {private set <string> cachenames = new Hashset <> (); / ** хранить нулевые значения, по умолчанию истину, предотвратить проникновение кэша*/ private boolean cachenullvalues = true; / ** Создание реализации кэша динамически на основе Cachename, по умолчанию true*/ private boolean dynamic = true; / ** Префикс ключа кеша*/ private String Cacheprefix; Private Redis Redis = new Redis (); частный кофеин кофеин = новый кофеин (); открытый класс Redis { / ** Глобальное время истечения, единица Milliseconds, истечение срока по умолчанию* / private long defaultexpiration = 0; / ** Время истечения, единица миллисекундов, приоритет выше, чем Defaultexpiration*/ Private Map <String, long> истекать = new hashmap <> (); / ** Уведомление о других узлах имен тем, когда обновления кэша*/ приватная строка topic = "cache: redis: caffeine: topic"; public long getDefaultexpiration () {return defaultexpiration; } public void setDefaultexpiration (long defaultexpiration) {this.defaultexpiration = defaultexpiration; } public map <string, long> getExpires () {return истекает; } public void setExpires (map <string, long> истекает) {this.expires = expires; } public String getTopic () {return Topic; } public void SetTopic (String Topic) {this.Topic = topic; }} открытый класс кофеин { / ** время истечения срока действия после доступа, в миллисекундах* / private long ocirefiterAccess; / ** время истечения после написания, единица миллисекундов*/ частный длинный срок действия; / ** Обновление времени после написания, единица миллисекундов*/ private long forreshafterwrite; / ** Размер инициализации*/ private int initialCapacity; / ** Максимальное количество объектов кэша, кэш, размещенный до превышения этого числа, будет недействительным*/ private long maximumsize; /** Поскольку вес должен быть обеспечен кэш -объектами, он не очень подходит для сценариев, таких как использование пружинного кеша, поэтому конфигурация еще не поддерживается*/// Приватный длинный максимальный вес; public long getExpireAfterAccess () {return oxriefTerAccess; } public void setExpireFaterAccess (long ocirefaterAccess) {this.expireafterAccess = exriefterAccess; } public long getExpireAfterWrite () {return oxriRefterWrite; } public void setExpirEfaterWrite (Long ExpireFterWrite) {this.ExpirefaterWrite = expireFterWrite; } public long getRefreshafterWrite () {return RefreshafterWrite; } public void setRefreshafterWrite (Long RefreshafterWrite) {this.refreshafterWrite = RefreshafterWrite; } public int getInitialCapacity () {return initialCapacity; } public void setInitialCapacity (int initialCapacity) {this.initialCapacity = initialCapacity; } public long getMaximumSize () {return maximumSize; } public void setMaximumSize (длинный максимум) {this.MaximumSize = maximumSize; }} public set <string> getCachenames () {return cachenames; } public void setCachEnames (set <string> cachenames) {this.cachenames = cachenames; } public boolean iscachenullvalues () {return cachenullvalues; } public void setCachenUllValues (Boolean cachenullvalues) {this.cachenullvalues = cachenullvalues; } public boolean isdynamic () {return dynamic; } public void setDynamic (boolean dynamic) {this.dynamic = dynamic; } public String getCachEpRefix () {return cacheprefix; } public void setCachePrefix (String cacheprefix) {this.cacheprefix = cacheprefix; } public redis getRedis () {return redis; } public void setredis (redis redis) {this.redis = redis; } public caffeine getCaffeine () {return caffeine; } public void setCaffeine (кофеин кофеин) {this.caffeine = caffeine; }} Существует абстрактный класс AbstractValueAdaptingCache, который реализует интерфейс кэша в пружинном кэше, который содержит упаковку пустых значений и упаковку значений кэша, поэтому нет необходимости реализовать интерфейс кэша и напрямую реализовать абстрактный класс AbstractValueAdaptingCache.
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; импорт java.lang.reflect.constructor; импорт java.util.map; импорт java.util.set; импорт java.util.concurrent.callable; импорт. java.util.concurrent.locks.reentrantlock; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.support.abstractvalueadaptingCache; импорт org.spramework.data.redis.core.redatettatetate org.springframework.util.stringutils; import com.github.benmanes.caffeine.cache.cache; import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacheredIscaffeineProperties;/** * @author fuwei.deng * @daite athraiteproperties;/** * @author fuwei. * @version 1.0.0 */public Class rescaffeinecache extrans AbstractValueAdaptingCache {private final logger logger = loggerfactory.getLogger (resiscaffeinecache.class); Приватное название строки; Частный Redistemplate <Object, Object> Redistemplate; Private Cache <Object, Object> CaffeineCache; Private String CachePrefix; частное длинное Defaultexpiration = 0; частная карта <строка, long> истекает; Приватная строка topic = "cache: Redis: Caffeine: Topic"; Защищенный resiscaffeinecache (Boolean allownullvalues) {super (allownullvalues); } public rescaffeinecache (имя строки, redistemplate <объект, объект> redistemplate, cache <объект, объект> Caffeinecache, CacherediscaffeineProperties cacherediscaffeineProperties) {super (cacherediscaffeineproperties.iscachenullesalues ()); this.name = name; this.redistemplate = redistemplate; this.caffeinecache = caffeinecache; this.cacheprefix = cacherediscaffeineproperties.getCachePrefix (); this.defaultexpiration = cacherediscaffeineproperties.getredis (). getDefaultexpiration (); this.expires = cacherediscaffeineproperties.getredis (). getExpires (); this.topic = cacherediscaffeineproperties.getredis (). getTopic (); } @Override public String getName () {return this.name; } @Override public Object getNativeCache () {return this; } @Suppresswarnings ("unchecked") @override public <t> t get (объект клавиша, Callible <t> valueloader) {object value = lookup (key); if (value! = null) {return (t) значение; } Reentrantlock lock = new Reentrantlock (); try {lock.lock (); значение = поиск (ключ); if (value! = null) {return (t) значение; } value = valueloader.call (); Object ShoreValue = tostoreValue (valueloader.call ()); положить (Key, StoreValue); return (t) значение; } catch (Exception e) {try {class <?> c = class.forname ("org.springframework.cache.cache $ valueretReivalexception"); Конструктор <?> Constructor = c.getConstructor (object.class, callable.class, throwable.class); Runtimeexception exception = (runtimeexception) constructor.newinstance (key, valueloader, e.getcause ()); бросить исключение; } catch (Exception e1) {бросить новое allodalstateException (e1); }} наконец {lock.unlock (); }} @Override public void put (ключ объекта, значение объекта) {if (! Super.isallownullvalues () && value == null) {this.evict (key); возвращаться; } long истекает = getExpire (); if (истекает> 0) {redistemplate.opforvalue (). set (getkey (key), tostorevalue (значение), истечение, timeUnit.milliseconds); } else {redistemplate.opforvalue (). set (getKey (key), tostorevalue (value)); } push (new cachemessage (this.name, key)); caffeinecache.put (ключ, значение); } @Override public valueWrapper putifabsent (объект клавиша, значение объекта) {Object cachekey = getKey (key); Object prevvalue = null; // Рассмотрим использование распределенных замков или изменить Setifabsent на атомную операцию Synchronized (key) {prevvalue = redistemplate.opforvalue (). Get (cachekey); if (prevvalue == null) {long oxpire = getExpire (); if (истекает> 0) {redistemplate.opforvalue (). set (getkey (key), tostorevalue (значение), истечение, timeUnit.milliseconds); } else {redistemplate.opforvalue (). set (getKey (key), tostorevalue (value)); } push (new cachemessage (this.name, key)); caffeinecache.put (key, tostorevalue (значение)); }} вернуть tovalueWrapper (prevvalue); } @Override public void evict (объект ключа) {// сначала очистить кэшированные данные в Redis, затем очистить кэш в кофеине, чтобы избежать кэша в течение короткого периода времени, если кэш -кэш будет очищен сначала, а другие запросы будут загружены из Redis в Caffeine Redistemptame.delete (getKey (ключ)); push (new Cachemessage (this.name, key)); caffeinecache.invalidate (key); } @Override public void clear () {// очистить кэшированные данные в Redis, затем очистить кэш в кофеине, чтобы избежать кеша в течение короткого периода времени, если кэш -кэш будет очищен сначала, тогда другие запросы будут загружены из Redis в кофеин из Redis set <object> keys = redistemplate.keys (this.name.concat ("); for (объект клавиша: keys) {redistemplate.delete (key); } push (new cachemessage (this.name, null)); caffeinecache.invalidateall (); } @Override защищенный объект Lookup (Object Key) {Object CacheKey = getKey (Key); Значение объекта = caffeinecache.getifpresent (key); if (value! = null) {logger.debug ("получить кеш из кофеина, ключ: {}", cachekey); возвращаемое значение; } value = redistemplate.opforvalue (). get (cachekey); if (value! = null) {logger.debug ("Получить кэш из Redis и положить в кофеин, ключ: {}", cachekey); cacheinecache.put (ключ, значение); } return value; } private Object getKey (объект ключа) {return this.name.concat (":"). concat (stringUtils.isempty (cacheprefix)? } private long getExpire () {long oxpire = defaultexpiration; Long cachenameexpire = expires.get (this.name); вернуть cachenameexpire == null? истекает: cachenameexpire.longvalue (); } / ** * @Description уведомление о других узлах для очистки локального кэша, когда изменяется кэш * @author fuwei.deng * @date 31 января 2018 года в 15:20:28 * @version 1.0.0 * @param Сообщение * / private void push (cachemessage Сообщение) {redistemplate.convertandsend (тема, сообщение); } / ** * @description Очистить локальный кэш * @author fuwei.deng * @date 31 января 2018 года в 15:15:39 * @version 1.0.0 * @param key * / public void clearlocal (ключ объекта) {logger.debug ("Clear Local Cache, ключ: {}", ключ); if (key == null) {caffeinecache.invalidateall (); } else {caffeinecache.invalidate (key); }}} Реализуйте интерфейс CacheManager
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.util.collection; импорт java.util.set; import java.util.concurrent.concurrenthashmap; импорт java.util.concurrent.concurrentmap; java.util.concurrent.timeUnit; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.cache; импорт org.springframework.cache.cachemanager; импорт org.spramework.data.rreadis.core.corestate. com.github.benmanes.caffeine.cache.caffeine; импорт com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineProperties;/** * @author fuwei.deng * @date 26 января 2018 года в 5:24:52. Resiscaffeinecachemanager реализует Cachemanager {private final logger logger = loggerfactory.getlogger (resiscaffeinecachemanager.class); private concurrentmap <string, cache> cachemap = new concurrenthashmap <string, cache> (); частные CacherediscaffeineProperties cacherediscaffeineProperties; Частный Redistemplate <Object, Object> Redistemplate; частная логическая динамика = true; частный набор <string> cachenames; public rescaffeinecaffeinemanager (CacherediscaffeineProperties CacherediscaffeineProperties, Redistemplate <Object, Object> Redistemplate) {super (); this.cacherediscaffeineProperties = cacherediscaffeineProperties; this.redistemplate = redistemplate; this.dynamic = cacherediscaffeineproperties.isdynamic (); this.cachenames = cacherediscaffeineproperties.getCachenames (); } @Override public cache getCache (String name) {cache cache = cachemap.get (name); if (cache! = null) {return cache; } if (! Dynamic &&! cachenames.contains (name)) {return cache; } cache = new Rediscaffeinecache (имя, Redistemplate, cache (), CacherediscaffeineProperties); Cache oldcache = cachemap.putifabsent (name, cache); logger.debug ("Создать экземпляр кэша, имя кэша: {}", name); вернуть OldCache == NULL? Кэш: OldCache; } public com.github.benmanes.caffeine.cache.cache <объект, объект> caffeinecache () {caffeine <объект, объект> cachebuilder = caffeine.newbuilder (); if (cacherediscaffeineproperties.getCaffeine (). getExpirefitterAccess ()> 0) {cachebuilder.expireafterateraccess (cacherediscaffeineProperties.getCaffeine (). getExpireAfteraterAccess (), timeUnit.milliseconds); } if (cacherediscaffeineProperties.getCaffeine (). getExpireAfterWrite ()> 0) {cacheBuilder.expireafterWrite (cacherediscaffeineProperties.getCaffeine (). getExpireAfterWrite (), timeUnit.milliseconds); } if (cachediscaffeineProperties.getCaffeine (). getInitialCapacity ()> 0) {cacheBuilder.initialCapacity (cacherediscaffeineProperties.getCaffeine (). getInitialCapacity ()); } if (cacherediscaffeineProperties.getCaffeine (). getMaximumSize ()> 0) {cacheBuilder.maximumSize (cacherediscaffeineProperties.getCaffeine (). getMaximumSize ()); } if (cacherediscaffeineproperties.getCaffeine (). getRefreshafterWrite ()> 0) {cacheBuilder.refreshafterWrite (cacherediscaffeineProperties.getCaffeine (). getRefreshafterWrite (), timeUnit.milliseconds); } return cachebuilder.build (); } @Override public Collection <String> getCachEnames () {return this.cachenames; } public void clearlocal (String Cachename, Key Key) {cache cache = cachemap.get (cachename); if (cache == null) {return; } Resiscaffeinecache resiscaffeinecache = (resiscaffeinecache) кэш; resiscaffeinecache.clearlocal (ключ); }} Сообщение Redis Publish/Pride, класс сообщений для передачи
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.io.serializable;/** * @author fuwei.deng * @date 29 января 2018 г. 1:31:17 ** SerialVersionuid = 5987219310442078193L; частная строковая кахенам; Частный объект ключ; public cachemessage (string cachename, object key) {super (); this.cachename = cachename; this.key = key; } public String getCachename () {return cachename; } public void setCachename (String cachename) {this.cachename = cachename; } public Object getKey () {return Key; } public void setKey (объект ключа) {this.key = key; }} Прослушивание сообщений Redis требует интерфейса Messagelistener
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import org.slf4j.logger; импорт org.slf4j.loggerfactory; импорт org.springframework.data.redis.cessection.message; org.springframework.data.redis.connection.messagelistener; import org.springframework.data.redis.core.redistemplate;/** * @author fuwei.deng * @date 30 января 2018 г. Logger logger = loggerFactory.getLogger (cachemessageListener.class); Частный Redistemplate <Object, Object> Redistemplate; Частный resiscaffeinecachemanager rescaffaffeinecachemanager; Public Cachemessagelistener (Redistemplate <Object, Object> Redistemplate, resiscaffeinecachemanager rescaffeinecachemanager) {super (); this.redistemplate = redistemplate; this.rediscaffeinecachemanager = rediscaffeinecachemanager; } @Override public void onMessage (сообщение сообщения, Byte [] pattern) {cachemessage cachemessage = (cachemessage) redistemplate.getValueSerializer (). Deserialize (message.getBody ()); logger.debug ("recevice a redis -тематическое сообщение, очистить локальный кэш, cachename is {}, ключ {}", cachemessage.getCachename (), cachemessage.getkey ()); resiscaffeinecachemanager.clearlocal (cachemessage.getcachename (), cachemessage.getkey ()); }} Добавить класс конфигурации Spring Boot
пакет com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import org.springframework.beans.factory.annotation.autowired; импорт org.springframework.boot.autoconfigure.autoconfiguration; импорт. org.springframework.boot.autoconfigure.condition.conditionalonbean; импорт org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration; импорт org.springframework.boot.context.EnbroperTies.EnableConfigurationSperpertiespromeSperperties org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.listener.ChannelTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.CacheMessageListener;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.RedisCaffeineCacheManager;/** * @author fuwei.deng * @date January 26, 2018 at 5:23:03 pm * @version 1.0.0 */@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)@EnableConfigurationProperties(CacheRedisCaffeineProperties.class)public class CacheRedisCaffeineAutoConfiguration { @Autowired private CacheRedisCaffeineProperties cacheRedisCaffeineProperties; @Bean @ConditionalOnBean(RedisTemplate.class) public RedisCaffeineCaffeine cacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { return new RedisCaffeineCacheManager(cacheRedisCaffeineProperties, redisTemplate); } @Bean public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate<Object, Object> redisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory()); CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCaffeineCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(cacheRedisCaffeineProperties.getRedis().getTopic())); return redisMessageListenerContainer; }}在resources/META-INF/spring.factories文件中增加spring boot配置扫描
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=/com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineAutoConfiguration
接下来就可以使用maven引入使用了
<dependency> <groupId>com.itopener</groupId> <artifactId>cache-redis-caffeine-spring-boot-starter</artifactId> <version>1.0.0-SNAPSHOT</version> <type>pom</type></dependency>
在启动类上增加@EnableCaching注解,在需要缓存的方法上增加@Cacheable注解
package com.itopener.demo.cache.redis.caffeine.service;import java.util.Random;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import com.itopener.demo.cache.redis.caffeine.vo.UserVO;import com.itopener.utils.TimestampUtil;@Servicepublic class CacheRedisCaffeineService { private final Logger logger = LoggerFactory.getLogger(CacheRedisCaffeineService.class); @Cacheable(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public UserVO get(long id) { logger.info("get by id from db"); UserVO user = new UserVO(); user.setId(id); user.setName("name" + id); user.setCreateTime(TimestampUtil.current()); return user; } @Cacheable(key = "'cache_user_name_' + #name", value = "userNameCache", cacheManager = "cacheManager") public UserVO get(String name) { logger.info("get by name from db"); UserVO user = new UserVO(); user.setId(new Random().nextLong()); user.setname (имя); user.setCreateTime(TimestampUtil.current()); return user; } @CachePut(key = "'cache_user_id_' + #userVO.id", value = "userIdCache", cacheManager = "cacheManager") public UserVO update(UserVO userVO) { logger.info("update to db"); userVO.setCreateTime(TimestampUtil.current()); return userVO; } @CacheEvict(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public void delete(long id) { logger.info("delete from db"); }} properties文件中redis的配置跟使用redis是一样的,可以增加两级缓存的配置
#两级缓存的配置spring.cache.multi.caffeine.expireAfterAccess=5000spring.cache.multi.redis.defaultExpiration=60000#spring cache配置spring.cache.cache-names=userIdCache,userNameCache#redis配置#spring.redis.timeout=10000#spring.redis.password=redispwd#redis pool#spring.redis.pool.maxIdle=10#spring.redis.pool.minIdle=2#spring.redis.pool.maxActive=10#spring.redis.pool.maxWait=3000#redis clusterspring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006spring.redis.cluster.maxRedirects=3
Расширенный
个人认为redisson的封装更方便一些
后续可以增加对于缓存命中率的统计endpoint,这样就可以更好的监控各个缓存的命中情况,以便对缓存配置进行优化
源码下载
starter目录:springboot / itopener-parent / spring-boot-starters-parent / cache-redis-caffeine-spring-boot-starter-parent
示例代码目录: springboot / itopener-parent / demo-parent / demo-cache-redis-caffeine
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.