A Spring Boot integra o cache da mola e possui várias implementações de cache, como redis, cafeína, jcache, ehcache etc., mas se você usar apenas um cache, ele terá um grande consumo de rede (como o REDIS) ou terá muito uso de memória (como o cache de memória de aplicação da cafeína). Em muitos cenários, os caches de primeiro e segundo nível podem ser combinados para obter melhorias em larga escala na eficiência do processamento da aplicação.
Descrição do conteúdo:
Para um entendimento simples, o cache é ler dados de mídia de leitura mais lenta e colocá-los em meio com leitura mais rápida, como disco-> memória. Normalmente, armazenamos dados no disco, como: banco de dados. Se você o ler a partir do banco de dados sempre, o próprio disco afetará a velocidade de leitura; portanto, haverá um cache de memória como o Redis. Você pode ler os dados e colocá -los na memória, para que, quando precisar obter os dados, possa obter diretamente os dados da memória e retorná -los, o que pode melhorar bastante a velocidade. No entanto, geralmente o Redis é implantado separadamente em um cluster; portanto, haverá consumo no IO da rede. Embora existam ferramentas de agrupamento de conexões para vincular o cluster Redis, ainda haverá algum consumo na transmissão de dados. Portanto, há um cache no aplicativo, como: cafeína. Quando há dados que atendem aos critérios no cache do aplicativo, eles podem ser usados diretamente sem precisar obtê-los através da rede para redis, formando assim um cache de dois níveis. O cache no aplicativo é chamado de cache de primeiro nível, e o cache remoto (como redis) é chamado de cache de segundo nível
cache da primavera
Ao usar o cache, o processo a seguir é geralmente o seguinte:
Pode ser visto no fluxograma que, para usar o cache, muitas operações de cache foram adicionadas com base no processamento de negócios original. Se estes forem acoplados ao código comercial, haverá muito trabalho repetitivo ao desenvolver e não for propício para entender os negócios com base no código.
O cache do Spring é um componente de cache fornecido no pacote Spring-Context com base na anotação. Ele define algumas interfaces padrão. Ao implementar essas interfaces, o cache pode ser alcançado adicionando anotações ao método. Isso evitará o problema do código do cache ser acoplado ao processamento de negócios. A implementação do cache da primavera é uma extensão da interface do método (MethodInterceptor) encapsulada na Spring AOP. Obviamente, a Spring AOP também é implementada com base no aspecto.
Existem duas interfaces principais de cache de mola: cache e cachemanager
Interface de cache
Forneça operações específicas de cache, como colocar, leitura e limpeza. As implementações fornecidas pelo Spring Framework são:
Exceto pelo Rediscache, que está no pacote Spring-Data-Redis, os outros estão basicamente no pacote de suporte de contexto de primavera.
#Cache.javapackage org.springframework.cache; import java.util.concurrent.callable; cache de interface pública {// cachename, o nome do cache. Na implementação padrão, o Cachemanager passa por Cachename ao criar o feijão de cache. String getName (); // Obtenha o cache real, como: redistemplate, com.github.benmanes.caffeine.cache.cache <objeto, objeto>. Ainda não encontrei o uso real. Posso apenas fornecer feijão que obtém cache nativo para que algumas operações ou estatísticas de cache precisem ser estendidas. Objeto getNativeCache (); // Obtenha o valor do cache através da chave, observe que o valor retornado é ValueWrapper. Para ser compatível com os valores nulos, o valor de retorno é embrulhado em uma camada e o valor real é obtido através do Method ValueWrapper GET GET (chave do objeto); // Obtenha o valor do cache através da chave, que retorna o valor real, ou seja, o tipo de valor de retorno do método <T> t Get (chave do objeto, classe <t> tipo); // Obtenha o valor do cache através da chave, você pode usar valueloader.call () para chamar o método usando a anotação @cacheable. Use este método quando o atributo SYNC da anotação @cacheable estiver configurado para true. Portanto, a sincronização da fonte de retorno ao banco de dados precisa ser garantida dentro do método. Evite grande quantidade de solicitações para retornar à fonte ao banco de dados quando o cache falhar. <t> t Get (chave do objeto, chamável <t> valueloader); // Coloque os dados retornados pelo método de anotação @cacheable no cache void put (chave do objeto, valor do objeto); // Coloque o cache apenas quando não houver chave no cache. O valor de retorno é os dados originais quando a chave existe valuewrapper putifabsent (chave do objeto, valor do objeto); // Excluir cache void despet (chave do objeto); // Exclua todos os dados no cache. Deve -se notar que, na implementação específica, apenas todos os dados em cache usando a anotação @cacheable são excluídos e não afetam outros caches no aplicativo void clear (); // envolve o valor da interface do valor do cache ValueWrapper {// retorna o objeto em cache real get (); } // Quando uma exceção é lançada por {@link #get (objeto, callable)}, ela será embrulhada como essa exceção lançada por @suppresswarnings ("serial") classe ValueReRevELexception estende a run timeException {private final Object Key; public valueReRevEalException (chave do objeto, CHAMADA EXELHADOR, LOLHERENT, LOWLE EX) {Super (String.Format ("Valor para Key '%s' não pôde ser carregado usando '%s'", chave, carregador), ex); this.Key = key; } public object getKey () {return this.key; }}}Interface Cachemanager
Ele fornece principalmente a criação de feijões de implementação de cache. Cada aplicativo pode isolar o cache através do Cachename, e cada Cachename corresponde a uma implementação de cache. A implementação fornecida pelo Spring Framework e a implementação do cache aparecem em pares, e a estrutura do pacote também está na figura acima.
#Cachemanager.javapackage org.springframework.cache; importar java.util.collection; interface pública Cachemanager {// Crie o gabinete de implementação do cache através do cachename. A implementação específica precisa armazenar os grãos de implementação de cache criados para evitar a criação repetida e também evitar a situação em que o conteúdo original do cache é perdido após o objeto de cache de memória (como cafeína) é recriado em cache getcache (nome da string); // Retornar toda a coleção Cachename <String> getCachenames ();}Anotações comuns
@Cacheable: Aplicado principalmente ao método de consultar dados
pacote org.springframework.cache.annotation; importar java.lang.annotation.documented; importar java.lang.annotation.ElementType; importar java.lang.annotation.iRettation; import java.lang.annotation.retion; importação; importação.anngation.innotation; importação; java.lang.annotation.Target;import java.util.concurrent.Callable;import org.springframework.core.annotation.AliasFor;@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Cacheable { // Cachenames, o Cachemanager cria o feijão de implementação de cache correspondente através desse nome @Aliasfor ("Cachenames") string [] value () padrão {}; @Aliasfor ("value") string [] cachenames () padrão {}; // Chave de cache, suporta expressões de spel. O padrão é um objeto embrulhado por todos os parâmetros e seu hashcode (simples) string key () padrão ""; // gerador de teclas de cache, a implementação padrão é o SimpleKeyGenerator String keyGenerator () padrão ""; // Especifique qual Cachemanager usar o String Cachemanager () padrão ""; // cache analiser string cacheResolver () padrão ""; // Condição do cache, suporta dados de expressão do SPEL e cache somente quando as condições satisfatórias são atendidas. String condition () padrão "" será julgado antes e depois de chamar o método; // O cache não é atualizado quando a condição é atendida, a expressão do SPEL é suportada e a string, a menos que () padrão "" é julgada somente após chamar o método; // Ao retornar à origem ao método real para obter dados, se deve manter sincronizado? Se false, o método cache.get (key) é chamado; Se verdadeiro, o método cache.get (chave, chamável) é chamado boolean sync () padrão false;} @Cacheevict: cache claro, aplicado principalmente ao método de exclusão de dados. Existem mais duas propriedades do que em cache
pacote org.springframework.cache.annotation; importar java.lang.annotation.documented; importar java.lang.annotation.ElementType; importar java.lang.annotation.iRettation; import java.lang.annotation.retion; importação; importação.anngation.innotation; importação; java.lang.annotation.target; importar org.springframework.core.annotation.aliasfor; @target ({elementType.method, elementType.type})@retention (retentionPolicy.Runtime)@herded@attrfublic @rinterface @Cacheable // Se você deve limpar todos os dados em cache, o método cache.evict (key) é chamado quando false; Quando verdadeiro, o método cache.clear () é chamado boolean allentries () padrão false; // Limpe o cache antes ou depois de chamar o método boolean antes davocation () padrão false;}O cache da primavera foi integrado na bota da primavera e fornece uma variedade de configurações de cache. Ao usá -lo, você só precisa configurar qual cache (enum cachetype) para usar.
Há uma coisa de extensão adicional adicionada à Spring Boot, que é a interface CachemanageCustomizer. Você pode personalizar esta interface e fazer algumas configurações para o Cachemanager, como:
package com.itopener.demo.cache.redis.config;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;import org.springframework.data.redis.cache.RedisCacheManager;public class RediscachemanageCustomizer implementa o CachemanageCustomizer <Rediscachemanager> {@Override public void Personalize (Rediscachemanager Cachemanager) {// Tempo de expiração padrão, unidade de segundos Cachemanager.setdefexpiration (1000); cachemanager.setUsePrefix (false); Mapa <string, long> expire = new ConcurrentHashmap <string, long> (); expires.put ("userIdcache", 2000l); Cachemanager.Setexpires (expira); }}Carregue este feijão:
package com.itopener.demo.cache.redis.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author fuwei.deng * @date December 22, 2017 at 10:24:54 am * @version 1.0.0 */@Configurationpublic classe Cacheredisconfiguration {@Bean Public RediscachemanageCustomizer RediscachemanageCustomizer () {return New RediscachemanageRerCustomizer (); }}O cache comumente usado é redis. Redis implementa a interface do cache da mola no pacote Spring-Data-Redis.
Aqui estão algumas das deficiências da implementação do Rediscache, eu acho:
1. No momento em que o cache falha, se um thread obtiver os dados do cache, ele poderá retornar nulo. O motivo é que as etapas a seguir estão na implementação do Rediscache:
Portanto, quando o cache falha depois de julgar que a chave existe e, em seguida, a obtenção do cache não possui dados, ele retorna nulo.
2. O atributo (CachenullValues) que pode armazenar valores nulos no Rediscachemanager é falso por padrão, ou seja, não é permitido armazenar valores nulos, o que arriscará a penetração do cache. O defeito é que essa propriedade é do tipo final e o objeto só pode ser criado quando o objeto é criado através do método do construtor. Portanto, para evitar a penetração do cache, você só pode declarar o Bean Rediscachemanager no aplicativo.
3. As propriedades no Rediscachemanager não podem ser configuradas diretamente através de arquivos de configuração. Eles só podem ser definidos na interface CachemanageCustomizer. Pessoalmente, acho que não é conveniente.
A cafeína é um cache de memória de alto desempenho com base no conceito de design de goiaba de código aberto do Google. Ele é desenvolvido usando o Java 8. Após o introdução da bota da primavera, a integração de goiaba foi gradualmente abandonada. Código fonte de cafeína e endereço de introdução: cafeína
A cafeína fornece uma variedade de estratégias de preenchimento de cache e estratégias de reciclagem de valor, e também inclui estatísticas como hits de cache, que podem fornecer grande ajuda na otimização do cache.
Para a introdução da cafeína, consulte: http://www.vevb.com/article/134242.htm
Aqui falamos brevemente sobre os seguintes tipos de estratégias de reciclagem baseadas no tempo de cafeína:
Mencionei no início que, mesmo que o Redis Cache seja usado, haverá um certo grau de consumo na transmissão da rede. Em aplicativos reais, haverá alguns dados com alterações muito baixas, que podem ser armazenadas em cache diretamente dentro do aplicativo. Para alguns dados com menos requisitos em tempo real, eles também podem ser armazenados em cache no pedido por um certo período de tempo para reduzir o acesso ao redis e melhorar a velocidade de resposta
Como o Redis tem algumas deficiências na implementação do cache da primavera na estrutura da mola-dados-redis, alguns problemas podem surgir ao usá-lo; portanto, não o estenderemos com base na implementação original. Vamos nos referir diretamente ao método de implementação para implementar as interfaces de cache e cachemanager
Deve-se notar também que os aplicativos geralmente implantam vários nós, e o cache de primeiro nível é um cache dentro do aplicativo; portanto, quando os dados são atualizados e limpos, todos os nós precisam ser notificados para limpar o cache. Existem muitas maneiras de alcançar esse efeito, como: Zookeeper, MQ, etc., mas como o Redis Cache é usado, o próprio Redis suporta funções de assinatura/publicação, por isso não depende de outros componentes. Ele usa diretamente o Channel Redis para notificar outros nós para limpar as operações de cache.
A seguir, são apresentados as etapas de encapsulamento de partida e o código-fonte para a inicialização da mola + o cache da mola para implementar o cache de dois níveis (Redis + Caffeine)
Defina a classe de propriedade de configuração de propriedades
pacote com.iTopener.cache.redis.caffeine.spring.boot.autoconfigure; importar java.util.hashmap; importar java.util.hashset; import java.util.map; importenToTaxxxt.Pring.PringPring.util.map; @Author Fuwei.Deng * @Date 29 de janeiro de 2018 às 11:32:15 * @Version 1.0.0 */ @ConfigurationProperties (prefix = "spring.cache.multi") classe pública CacheredCaffeineProperties {private set <tring> cachenames = newhset <>; / ** Se deve armazenar valores nulos, padrão true, impedir a penetração do cache*/ private boolean cachenullValues = true; / ** Se deve criar a implementação do cache dinamicamente com base no cachene, padrão true*/ private boolean dinâmico = true; / ** prefixo da tecla de cache*/ private String CachePrefix; private redis redis = new redis (); cafeína privada cafeína = new cafeína (); classe pública Redis { / ** Tempo de expiração global, unidade milissegundos, expiração padrão* / private longo defaultExpiration = 0; / ** Tempo de validade, milissegundos de unidade, prioridade é maior que defaultExpiration*/ mapa privado <string, long> expira = new hashmap <> (); / ** Notifique outros nós dos nomes de tópicos quando as atualizações do cache*/ private String tópico = "cache: redis: cafeína: tópico"; public Long getDefaultExpiration () {return defaultExpiration; } public void setDefaultExpiration (long defaultExpiration) {this.DefaultExpiration = defaultExpiration; } mapa público <string, long> getExpires () {return expira; } public void se setExpires (map <string, long> expira) {this.expires = expira; } public string getTopic () {retornar tópico; } public void Settópico (tópico da string) {this.topic = tópico; }} classe pública Caffeine { / ** Tempo de validade após o acesso, em milissegundos* / private Long ExpireFterAccess; / ** Tempo de validade após escrever, unidade milissegundos*/ private Long ExpireafterWrite; / ** Atualizar tempo após escrever, unidade milissegundos*/ Private Long Refreshafterwrite; / ** Tamanho da inicialização*/ Private int InitialCapacity; / ** Número máximo de objetos de cache, o cache colocado antes de exceder esse número será inválido*/ privado de maximumsize longo; /** Como o peso precisa ser fornecido por objetos de cache, ele não é muito adequado para cenários como o uso do cache de primavera, portanto a configuração ainda não é suportada*/// public Long getExpireAxtAccess () {return expreefterAccess; } public void se setExpireabterAccess (long expireafterAccess) {this.expireabterAccess = ExpireFterAccess; } public long getExpireAfordWrite () {return exireafterWrite; } public void setexpireAfordWrite (long expireFterWrite) {this.expireAfordWrite = ExireAdFerWrite; } 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 (Long Maximumsize) {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 dinâmico; } public void setDynamic (dinâmico booleano) {this.dynamic = dinâmico; } 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 cafeína getCaffeine () {return cafeína; } public void setCaffeine (cafeína cafeína) {this.caffeine = cafeína; }} Existe uma classe abstrata abstrataValueAptingCache que implementa a interface do cache no cache da mola, que contém a embalagem de valores vazios e a embalagem dos valores de cache, portanto, não há necessidade de implementar a interface do cache e implementar diretamente a classe abstrata de abstratoValueAdttingCache.
pacote com.iTopener.cache.redis.caffeine.spring.boot.autoconfigure.support; importar java.lang.reflect.constructor; importar java.util.map; import java.util.ets; import java.util.Curntent.Calable; Import.mport; java.util.concurrent.locks.ReentrantLock;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.support.AbstractValueAdaptingCache;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.stringUtils; importar com.github.benmanes.caffeine.cache.cache; importar com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacheredisceineproperties;/** *author 17:24:11 PM * @Version 1.0.0 */classe pública Rediscaffeinecache estende abstractValuEadaptingCache {private final Logger Logger = LoggerFactory.getLogger (Rediscaffeinecache.class); nome de string privado; Private Redistemplate <Object, Object> Redistemplate; cache privado <objeto, objeto> Caffeinecache; Cacheprefix de String Private; private longo defaultExpiration = 0; mapa privado <string, long> expira; String privada tópico = "cache: redis: cafeína: tópico"; Rediscaffeinecache protegido (boolean alownullValues) {super (allowlullValues); } public Rediscaffeinecache (Nome da String, Redistemplate <Object, Object> Redistemplate, Cache <Object, Object> Caffeinecache, CacheredCaffeineProperties CacherediscaffeineProperties) {Super (CacheredCaffeineProperties.iscachenullValues ()); this.name = nome; this.redistemplate = redistemplate; this.caffeinecache = Caffeinecache; this.cacherefix = 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 ("desmarcado") @Override public <T> t Get (chave do objeto, chamável <T> valueloader) {Valor do objeto = Lookup (chave); if (value! = null) {return (t) value; } Reentrantlock Lock = new Reentrantlock (); tente {Lock.lock (); valor = lookup (chave); if (value! = null) {return (t) value; } value = valueloader.call (); Objeto StoreValue = TostoreValue (valueloader.call ()); put (chave, storeValue); Retorno (t) valor; } catch (Exceção e) {try {class <?> c = class.ForName ("org.springframework.cache.cache $ valueRetrievalexception"); Construtor <?> Construtor = c.getConstructor (object.class, callable.class, throwable.class); RUNTimeException Exception = (RUNTimeException) construtor.NewInstance (Key, Valueloader, E.GetCausa ()); Exceção de lançar; } catch (Exceção E1) {THROW NOVA ILGLAGALSTATEEXCECTION (E1); }} finalmente {Lock.unlock (); }} @Override public void put (chave do objeto, valor do objeto) {if (! Super.isallowlullValues () && value == null) {this.evict (key); retornar; } long expire = getExpire (); if (expire> 0) {redistemplate.opsforValue (). set (getKey (chave), tostoreValue (valor), expire, timeUnit.millisEconds); } else {redistemplate.opsforValue (). set (getKey (chave), tostorevalue (valor)); } push (novo cachemessage (this.name, chave)); Caffeinecache.put (chave, valor); } @Override public valueWrapper putifabsent (chave do objeto, valor do objeto) {objeto cacheKey = getKey (chave); Objeto prevvalue = null; // Considere o uso de bloqueios distribuídos ou altere o Redis 'SetIfabsent para a operação atômica sincronizada (key) {prevValue = redistemplate.opsforValue (). Get (cachekey); if (prevvalue == null) {long expire = getExpire (); if (expire> 0) {redistemplate.opsforValue (). set (getKey (chave), tostoreValue (valor), expire, timeUnit.millisEconds); } else {redistemplate.opsforValue (). set (getKey (chave), tostorevalue (valor)); } push (novo cachemessage (this.name, chave)); Caffeinecache.put (chave, tostorevalue (valor)); }} retornar ToValueWrapper (PrevValue); } @Override public void despet (chave do objeto) {// primeiro limpe os dados em cache no redis e depois limpe o cache na cafeína, para evitar o cache em um curto período de tempo se o cache do cache for limpo primeiro e outras solicitações serão carregadas de redis para cafeine redistemplate.delete (getkey); push (novo Cachemessage (this.name, chave)); Caffeinecache.invalidate (chave); } @Override public void clear () {// Limpe os dados em cache em redis e depois limpe o cache na cafeína, para evitar o cache em um curto período de tempo se o cache for limpo primeiro, depois outras solicitações) serão carregadas de redis para cafeína de Redis Set <Cejed Techa> = redistemplate.kyys (this.n); para (chave do objeto: chaves) {redistemplate.delete (chave); } push (novo Cachemessage (this.name, nulo)); Caffeinecache.invalidateall (); } @Override Protected Object Lookup (chave do objeto) {objeto cacheKey = getKey (chave); Valor do objeto = Caffeinecache.getifpresent (key); if (value! = null) {logger.debug ("Obtenha cache da cafeína, a chave é: {}", cachekey); valor de retorno; } value = redistemplate.opsforValue (). get (cachekey); if (value! = null) {logger.debug ("Obtenha cache do redis e coloque cafeína, a chave é: {}", cachekey); cacheinecache.put (chave, valor); } retornar valor; } objeto privado getKey (chave do objeto) {return this.name.concat (":"). concat (stringutils.isEmpty (cacheprefix)? key.toString (): cacheprefix.concat (":"). concat (key.tostring ())); } private long getExpire () {long expire = defaultExpiration; Long cacheNameExpire = expira.get (this.name); Retornar CachenameExpire == NULL? Expire: CachenameExpire.longValue (); } / ** * @Description Notifique outros nós para limpar o cache local quando o cache mudar * @Author Fuwei.deng * @Date 31 de janeiro de 2018 às 15:20:28 * @Version 1.0.0 * @param mensagem * / private void push) } / ** * @Description Limpe o cache local * @Author Fuwei.deng * @Date 31 de janeiro de 2018 às 15:15:39 * @version 1.0.0 * @param key * / public void clearlocal (chave do objeto) {logger.debug ("cache local, a chave é: {{} if (key == null) {Caffeinecache.inValidateAll (); } else {Caffeinecache.invalidate (key); }}} Implementar a interface Cachemanager
pacote com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; importar java.util.collection; importar java.util.set; import java.util.concurrent.concurrenthmap; importação java.util.concurnt.concurrenthmap; importar java.util.concurnt.concurrenthmap; java.util.concurrent.TimeUnit; importar org.slf4j.logger; importar org.slf4j.loggerFactory; importar org.springframework.cache.cache; importar org.springframework.cache.cachemanager; importar.emplate; com.github.benmanes.caffeine.cache.caffeine; importar com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineProperties;/** ** * @author fuwei.deng * @date 26, 2018 às 5 de janeiro. RediscaffeineCachemanager implementa Cachemanager {private final Logger Logger = LoggerFactory.getLogger (RediscaffeineCachemanager.class); private ConcurrentMap <String, cache> Cachemap = novo ConcurrentHashMap <String, cache> (); CacherediscaffeineProperties privadas CacherediscaffeineProperties; Private Redistemplate <Object, Object> Redistemplate; dinâmico booleano privado = true; Conjunto privado <String> Cachenames; public RedisCaffeineCeffeineManager (CacheredCaffeineProperties CacherediscaffeineProperties, Redistemplate <Object, objeto> Redistemplate) {super (); this.cacherediscaffeineProperties = cacherediscaffeineProperties; this.redistemplate = redistemplate; this.dynamic = cacherediscaffeineProperties.isdynamic (); this.cachenames = cacherediscaffeineProperties.getCachenames (); } @Override public cache getCache (nome da string) {cache cache = Cachemap.get (nome); if (cache! = null) {return cache; } if (! Dynamic &&! Cachenames.contains (nome)) {return cache; } cache = novo Rediscaffeinecache (nome, redistemplate, cache (), cacherediscaffeineproperties); Cache OldCache = Cachemap.putifabsent (nome, cache); Logger.debug ("Criar instância do cache, o nome do cache é: {}", nome); Retornar OldCache == NULL? Cache: OldCache; } public com.github.benmanes.caffeine.cache.cache <Object, object> Caffeinecache () {Caffeine <Object, object> Cachebuilder = Caffeine.newbuilder (); if (cacherediscaffeineProperties.getcaffeine (). getExpireAxtAccess ()> 0) {cachebuilder.expireabterAccess (cacherediscaffeineProperties.getcaffeine (). getExpireAccess (), timeUnit.millisEconds); } if (cacherediscaffeineProperties.getcaffeine (). getExpireAthterWrite ()> 0) {cachebuilder.expireAxtwrite (cacherediscaffeineProperties.getcaffeine (). getExpireArWrite (), timeUnit.milliseconds); } if (cacherediscaffeineProperties.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 (). getRefShafterWrite (), timeunit.millisConds); } retornar cachebuilder.build (); } @Override Public Collection <String> getCachenames () {return this.cachenames; } public void clearlocal (string cachename, chave de objeto) {cache cache = cachemap.get (cachename); if (cache == null) {return; } Rediscaffeinecache Rediscaffeinecache = (Rediscaffeinecache) cache; rediscaffeinecache.clearlocal (key); }} Redis Message Publicar/assinar, classe de mensagem para transmissão
pacote com.iTopener.cache.redis.caffeine.spring.boot.autoconfigure.support; importar java.io.serializable;/** * @author fuwei.deng * @date 29, 2018 1:31:17 pm * @version 1.0 * * */Public. final serialversionUid final = 5987219310442078193L; Cachename de string privado; chave de objeto privado; public Cachemessage (Cachename de String, chave do objeto) {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 (chave do objeto) {this.key = key; }} Ouvir as mensagens Redis requer a interface Messagelistener
pacote com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; importar org.slf4j.logger; import org.slf4j.loggerfactory; importação org.springframework.data.redis.connection.message; org.springframework.data.redis.connection.MessageListener;import org.springframework.data.redis.core.RedisTemplate;/** * @author fuwei.deng * @date January 30, 2018 at 5:22:33 pm * @version 1.0.0 */public class CacheMessageListener implements MessageListener { private Final Logger Logger = LoggerFactory.getLogger (CachemessageListener.class); Private Redistemplate <Object, Object> Redistemplate; RediscaffeineCachemanager privado RediscaffeineCachemanager; public CachemessageListener (Redistemplate <Object, Object> Redistemplate, RediscaffeineCachemanager RediscaffeineCachemanager) {super (); this.redistemplate = redistemplate; this.rediscaffeineCachemanager = RediscaffeineCachemanager; } @Override public void onMessage (mensagem da mensagem, byte [] padrão) {Cachemessage Cachemessage = (Cachemessage) redistemplate.getValueRializer (). Deserialize (message.getBody ()); Logger.debug ("Recebe uma mensagem de tópico Redis, cache local limpo, o cachename é {}, a chave é {}", cachemessage.getcachename (), cachemessage.getKey ()); RediscaffeineCachemanager.clearlocal (Cachemessage.getCachename (), cachemessage.getKey ()); }} Adicionar classe de configuração de inicialização da mola
pacote com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; importar org.springframework.beans.factory.annotation.autowired; importar org.springframework.boot.autoconfigure.autoconfiguratur; org.springframework.boot.autoconfigure.condition.conditionalonbean; importar org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration; importrg.springframework.Boot.CoToTeries.Properties.EnLabrties.Enllabrings; 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 (nome); 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
Estendido
个人认为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
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.