Spring Boot integra el caché de Spring y tiene múltiples implementaciones de caché, como Redis, Caffeine, JCache, Ehcache, etc. Pero si solo usa un caché, tendrá un gran consumo de red (como Redis), o tendrá demasiado uso de memoria (como el caché de memoria de la aplicación de la cafeína). En muchos escenarios, los cachés de primer y segundo nivel se pueden combinar para lograr una mejora a gran escala en la eficiencia de procesamiento de la aplicación.
Descripción del contenido:
Para una comprensión simple, el caché es leer datos de medios de lectura más lentos y ponerlos en medio con lectura más rápida, como el disco-> memoria. Por lo general, almacenamos datos en el disco, como: base de datos. Si lo lee desde la base de datos cada vez, el disco en sí afectará la velocidad de lectura, por lo que habrá un caché de memoria como Redis. Puede leer los datos y ponerlos en la memoria, de modo que cuando necesite obtener los datos, pueda obtener directamente los datos de la memoria y devolverlos, lo que puede mejorar enormemente la velocidad. Sin embargo, generalmente Redis se implementa por separado en un clúster, por lo que habrá consumo en la red IO. Aunque existen herramientas de agrupación de conexión para vincular al clúster Redis, todavía habrá algún consumo en la transmisión de datos. Entonces hay un caché en la aplicación, como: cafeína. Cuando hay datos que cumplen con los criterios en el caché de la aplicación, se puede usar directamente sin tener que obtenerlo a través de la red para redis, formando así un caché de dos niveles. El caché en la aplicación se llama caché de primer nivel, y el caché remoto (como Redis) se llama caché de segundo nivel
caché de primavera
Al usar caché, el siguiente proceso es generalmente el siguiente:
Se puede ver en el diagrama de flujo que para usar caché, se han agregado muchas operaciones de caché en función del procesamiento comercial original. Si estos se acoplan al código de negocio, habrá mucho trabajo repetitivo al desarrollar, y no es propicio para comprender el negocio en función del código.
Spring Cache es un componente de caché proporcionado en el paquete Spring-Context basado en la anotación. Define algunas interfaces estándar. Al implementar estas interfaces, el caché se puede lograr agregando anotaciones al método. Esto evitará el problema del código de caché que se combina con el procesamiento de negocios. La implementación de Spring Cache es una extensión de la encapsulación de la interfaz de método (MethodInterceptor) en Spring AOP. Por supuesto, Spring AOP también se implementa en función del aspecto.
Hay dos interfaces núcleo de caché de primavera: caché y cachemanager
Interfaz de caché
Proporcione operaciones específicas de caché, como poner, leer y limpiar cachés. Las implementaciones proporcionadas por Spring Framework son:
A excepción de Rediscache, que se encuentra en el paquete Spring-Data-Redis, los otros están básicamente en el paquete Spring-Context-Support.
#Cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interfaz caché {// cachename, el nombre del caché. En la implementación predeterminada, Cachemanager pasa Cachename al crear el Bean de caché. Cadena getName (); // Obtenga el caché real, como: Redistemplate, com.github.benmanes.caffeine.cache.cache <objeto, objeto>. Todavía no he encontrado el uso real. Puedo proporcionar frijoles que obtienen caché nativo para que algunas operaciones o estadísticas de caché deban extenderse. Objeto getNateVecache (); // Obtener el valor de caché a través de la clave, tenga en cuenta que el valor devuelto es ValueWrapper. Para ser compatible con valores nulos, el valor de retorno se envuelve en una capa, y el valor real se obtiene a través del método Get ValueWrapper Get (clave de objeto); // Obtenga el valor de caché a través de la clave, que devuelve el valor real, es decir, el tipo de valor de retorno del método <T> t get (clave de objeto, clase <t> type); // Obtener el valor de caché a través de la clave, puede usar valueloader.call () para llamar al método usando @cachable anotation. Use este método cuando el atributo de sincronización de la anotación @Cachable se configure en True. Por lo tanto, la sincronización de la fuente de retorno a la base de datos debe garantizarse dentro del método. Evite una gran cantidad de solicitudes para regresar a la fuente a la base de datos cuando falla el caché. <T> t get (clave de objeto, llamable <t> valueloader); // Ponga los datos devueltos por el método de anotación @Cachable en el Cache Void Put (clave de objeto, valor de objeto); // Coloque el caché solo cuando no hay clave en el caché. El valor de retorno son los datos originales cuando la clave existe ValueWrapper Putifabsent (clave de objeto, valor de objeto); // Eliminar el desalojo de cache void (clave de objeto); // Eliminar todos los datos en el caché. Cabe señalar que en la implementación específica, solo todos los datos almacenados en caché con la anotación @cacheable se eliminan y no afectan a otros cachés en la aplicación nula clare (); // envuelve la interfaz de valor de retorno de caché ValueWrapper {// Devuelve el objeto del objeto en caché real get (); } // Cuando se lanza una excepción por {@link #get (object, llamable)}, se envolverá como esta excepción lanzada por @SupessWarnings ("Serial") ValueTrievalException extiende RuntimeException {clave de objeto final privado; public ValuerTieValException (clave de objeto, llamable <?> cargador, showable ex) {super (string.format ("valor para la clave '%s' no se pudo cargar usando '%s'", clave, cargador), ex); this.key = key; } Public Object getKey () {return this.key; }}}Interfaz de caché
Proporciona principalmente la creación de frijoles de implementación de caché. Cada aplicación puede aislar el caché a través de Cachename, y cada Cachename corresponde a una implementación de caché. La implementación proporcionada por el marco de Spring y la implementación de caché aparecen en pares, y la estructura del paquete también está en la figura anterior.
#Cachemanager.javapackage org.springframework.cache; import java.util.collection; public interfaz Cachemanager {// Cree la implementación de caché bean a través de Cachename. La implementación específica debe almacenar los frijoles de implementación de caché creados para evitar la creación repetida, y también evitar la situación en la que el contenido de caché original se pierde después de que el objeto de caché de memoria (como la cafeína) se recree en caché getcache (nombre de cadena); // Devuelve toda la colección Cachename <String> getCachenames ();}Anotaciones comunes
@Cachable: se aplica principalmente al método de consulta de datos
paquete org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; import java.lang.annotation.inherited; import java.lang.annotation.retention; import java.lang.annotation java.lang.annotation.target; import java.util.concurrent.callable; import org.springframework.core.annotation.aliasfor; @Target ({elementType.method, elementType.type})@retención (retención retención.runtime) a Cachenames, Cachemanager crea el bean de implementación de caché correspondiente a través de este nombre @aliasfor ("Cachenames") String [] valor () predeterminado {}; @Aliasfor ("valor") string [] cachenames () predeterminado {}; // Cache Key, admite expresiones de Spel. El valor predeterminado es un objeto envuelto por todos los parámetros y su tecla de cadena hashcode (SimpleKey) String () predeterminada ""; // Generador de teclas de caché, la implementación predeterminada es SimpleKeyGenerator String KeyGenerator () predeterminado ""; // Especifique qué Cachemanager use String Cachemanager () predeterminado ""; // Cache Parser String Cacheresolver () predeterminado ""; // condición de caché, admite la expresión de Spel y los datos de caché solo cuando se cumplen las condiciones satisfactorias. Condición de cadena () predeterminada "" se juzgará antes y después de llamar al método; // El caché no se actualiza cuando se cumple la condición, se admite la expresión de Spel y la cadena a menos que () predeterminado "" se juzga solo después de llamar al método; // Al regresar a la fuente al método real para obtener datos, si se mantiene sincronizado? If false, se llama al método cache.get (clave); Si es verdadero, el método Cache.get (Key, Callable) se llama Boolean Sync () predeterminado Falso;} @CacheEvict: Clare Cache, aplicado principalmente al método para eliminar los datos. Hay dos propiedades más que en caché
paquete org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; import java.lang.annotation.inherited; import java.lang.annotation.retention; import java.lang.annotation java.lang.annotation.target; import org.springframework.core.annotation.aliasfor; @Target ({elementType.method, elementtype.type})@retención (retenciónPolicy.runtime)@heredado@documentedpublic @interface cacheevicte {// ... para el mismo atributo descripción, por favor en las instrucciones de @cetedpublic @cachedpaceface cacheeVicto {// ... para el mismo atributo descripción, por favor, a los instrucciones de @cetedpublic @cachedpaceface @Cachedited // Si para borrar todos los datos en caché, el método Cache.Evict (Key) se llama cuando False; Cuando es verdadero, el método Cache.Clear () se llama boolean allentries () predeterminado falso; // borrar la memoria caché antes o después de llamar al método boolean antes de la invocación () predeterminada falsa;}Spring Cache se ha integrado en Spring Boot y proporciona una variedad de configuraciones de caché. Al usarlo, solo necesita configurar qué caché (enum Cachetype) usar.
Hay una extensión adicional agregada a Spring Boot, que es la interfaz CachemanagerCustomizer. Puede personalizar esta interfaz y luego hacer algunas configuraciones para Cachemanager, como:
paquete 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.cacheishiser; RediscachemanAgercustomizer implementa CachemanaGerCustomizer <Rediscachemanager> {@Override public void personalizar (Rediscachemanager Cachemanager) {// Tiempo de vencimiento predeterminado, Unidad Seconds Cachemanager.SetDefaultExpiration (1000); Cachemanager.SetUseprefix (falso); Map <string, long> expires = new concurrenthashmap <string, long> (); expires.put ("userIdcache", 2000l); Cachemanager.setExires (expiras); }}Cargue este frijol:
paquete com.itopener.demo.cache.redis.config; import org.springframework.context.annotation.bean; importar org.springframework.context.annotation.configuration;/** * @author fuwei.Deng * @date 22 de diciembre de 2017 en 10:24:54 am * @version CacheredisConfiguration {@Bean public RediscachemanagerCustomizer RediscachemanagerCustomizer () {return New RediscachemanagerCustomizer (); }}El caché comúnmente utilizado es Redis. Redis implementa la interfaz Spring Cache en el paquete Spring-Data-Redis.
Estas son algunas de las deficiencias en la implementación de Rediscache, creo:
1. En el momento en que el caché falla, si un hilo obtiene los datos de caché, puede devolver nulo. La razón es que los siguientes pasos están en la implementación de Rediscache:
Por lo tanto, cuando el caché falla después de juzgar que la clave existe, y luego obtener el caché no tiene datos, devuelve nulo.
2. El atributo (CachenullValues) que puede almacenar valores nulos en Rediscachemanager es falso de forma predeterminada, es decir, no está permitido almacenar valores nulos, lo que arriesgará la penetración de caché. El defecto es que esta propiedad es de tipo final, y el objeto solo se puede crear cuando el objeto se crea a través del método del constructor. Por lo tanto, para evitar la penetración de caché, solo puede declarar el Rediscachemanager Bean en la aplicación.
3. Las propiedades en Rediscachemanager no se pueden configurar directamente a través de archivos de configuración. Solo se pueden configurar en la interfaz CachemanagerCustomizer. Personalmente creo que no es conveniente.
La cafeína es un caché de memoria de alto rendimiento basado en el concepto de diseño de guayaba de código abierto de Google. Se desarrolla con Java 8. Después de la bota de primavera introducida con cafeína, la integración de la guayaba se ha abandonado gradualmente. Código fuente de cafeína y dirección de introducción: cafeína
La cafeína proporciona una variedad de estrategias de llenado de caché y estrategias de reciclaje de valores, y también incluye estadísticas como golpes de caché, que pueden proporcionar una gran ayuda en la optimización de caché.
Para la introducción de la cafeína, consulte: http://www.vevb.com/article/134242.htm
Aquí hablamos brevemente sobre los siguientes tipos de estrategias de reciclaje del tiempo de cafeína:
Al principio mencioné que incluso si se usa el caché redis, habrá un cierto grado de consumo en la transmisión de red. En aplicaciones reales, habrá algunos datos con cambios muy bajos, que se pueden almacenar en caché directamente dentro de la aplicación. Para algunos datos con menos requisitos en tiempo real, también se puede almacenar en caché dentro de la aplicación durante un cierto período de tiempo para reducir el acceso a Redis y mejorar la velocidad de respuesta
Dado que Redis tiene algunas deficiencias en la implementación de Spring Cache en el marco Spring-Data-Redis, pueden surgir algunos problemas al usarlo, por lo que no lo extenderemos en función de la implementación original. Nos referiremos directamente al método de implementación para implementar las interfaces Cache y Cachemanager
También se debe tener en cuenta que generalmente las aplicaciones implementan múltiples nodos, y el caché de primer nivel es un caché dentro de la aplicación, por lo que cuando los datos se actualizan y se borran, todos los nodos deben ser notificados para limpiar el caché. Hay muchas maneras de lograr este efecto, como: Zookeeper, MQ, etc., pero dado que se usa el caché redis, Redis en sí admite funciones de suscripción/publicación, por lo que no depende de otros componentes. Utiliza directamente el canal Redis para notificar a otros nodos para limpiar las operaciones de caché.
Los siguientes son los pasos de encapsulación de inicio y el código fuente para el arranque de primavera + caché de primavera para implementar la memoria caché de dos niveles (redis + cafeína)
Definir la clase de propiedad de configuración de propiedades
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import java.util.hashmap; import java.util.hashset; import java.util.map; import java.util.set; import og.springframework.boot.context.contexties.configatoration; @author fuwei.deng * @Date 29 de enero de 2018 a las 11:32:15 am * @version 1.0.0 */ @configurationProperties (prefix = "spring.cache.multi") clase pública cacherediscoffeineproperties {set private <tring> cachenames = new Hashset <> (););); / ** Si almacenar valores nulos, predeterminado verdadero, evitar la penetración de caché*/ private boolean CachenullValues = true; / ** Si se debe crear implementación de caché dinámicamente basada en Cachename, verdadero verdadero*/ private boolean Dynamic = true; / ** Prefijo de la tecla Cache*/ private String Cacheprefix; privado redis redis = new Redis (); cafeína privada cafeína = nueva cafeína (); clase pública Redis { / ** Tiempo de vencimiento global, Unidad MilliseConds, expiración predeterminada* / Private Long DefaultExpiration = 0; / ** Tiempo de caducidad, Unidad Millisegundos, Prioridad es mayor que la EXPORTA EXPERIA*/ MAP Private <String, Long> expires = new HashMap <> (); / ** Notifique a otros nodos de los nombres de los temas cuando las actualizaciones de caché*/ private String Topic = "Cache: Redis: Caffeine: Topic"; public Long getDefaultExpiration () {return DefaultExpiration; } public void setDefaultExpiration (Long DefaultExpiration) {this.defaultExpiration = defaultExpiration; } mapa público <string, long> getExpires () {return expire; } public void setExpires (map <string, long> expires) {this.expires = expires; } public String getTopic () {Tema de retorno; } public void settópico (tema de cadena) {this.topic = topic; }} Cafeína de clase pública { / ** Tiempo de vencimiento después del acceso, en milisegundos* / Private Long expirante de los expiras; / ** Tiempo de vencimiento Después de escribir, Millisegundos de la unidad*/ Privado Long Expirantewterswrite; / ** Actualizar tiempo después de escribir, Unidad Millisegundos*/ Private Long RefreshAfterWrite; / ** Tamaño de inicialización*/ private int InitialCapacity; / ** Número máximo de objetos de caché, el caché colocado antes excede este número no será válido*/ Private Long MaximumSize; /** Dado que el peso debe ser proporcionado por los objetos de caché, no es muy adecuado para escenarios como el uso de Spring Cache, por lo que la configuración aún no es compatible*/// Private Long Maximum Weight; public Long GetExpireAftterAccess () {return expiranteCcess; } public void setExpeAfTterAccess (Long ExpirAfTterAccess) {this.ExpeAfTterAccess = expirante enterrainCess; } public Long GetExpeAftterWrite () {return expirantewterwrite; } public void setExpeAfTterWrite (Long expirantewterwrite) {this.ExpeAfTterWrite = expirante enterrawrite; } public Long GetRefreshafterWrite () {return refreshAfterWrite; } public void setRefreshafterWrite (Long RefreshAfterWrite) {this.RefreshAfterWrite = refreshAfterWrite; } public int getInitialCapacity () {return inicialCapacity; } public void setInitialCapacity (int InitialCapacity) {this.initialCapacity = inicialCapacity; } public Long Long getMaximumSize () {return MaximumSize; } public void setmaximumSize (largo máximo de máxima) {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 (Dynamic boolean) {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 cafeine getCaffeine () {return cafeine; } public void setCaffeine (cafeína con cafeína) {this.caffeine = caffeine; }} Hay una clase abstracta AbstractValueAdaptingCache que implementa la interfaz de caché en Spring Cache, que contiene el embalaje de valores vacíos y el empaque de los valores de caché, por lo que no es necesario implementar la interfaz de caché e implementar directamente la clase abstracta abstractueAptAptingCache.
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.lang.reflect.constructor; import java.util.map; import java.util.set; import java.util.concurrent.callable; java.util.concurrent.locks.reentrantlock; import org.slf4j.logger; import org.slf4j.loggerFactory; import org.springframework.cache.support.abstractValueadaptingCache; import org.springframework.util.stringutils; import com.github.benmanes.caffeine.cache.cache; import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscoffeinepreperties;/** * @author fuwei.deng * @Date enero de 2018, 2018 a 5:2:20 a 5:2. * @version 1.0.0 */public class RedisCaffeInecache extiende AbstractValueAptingCache {private final logger logger = loggerFactory.getLogger (rediscafafeInecache.class); nombre de cadena privada; privado redistemplate <objeto, objeto> redistemplate; Cache privado <objeto, objeto> CaffeInecache; cadena privada CachePrefix; Private Long DefaultExpiration = 0; mapa privado <string, long> expiras; String private String Topic = "Cache: Redis: Caffeine: Topic"; RediscisfaffeInecache protegido (Valuos Allowlulles booleanos) {Super (AllownLullValues); } public redisCAffeInEcache (nombre de cadena, redistemplate <objeto, objeto> redistemplate, caché <objeto, objeto> caffeInecache, cacherediscaffeineproperties cacherediscaffeineproperties) {super (cacherediscaffeineproperties.iscacheachenullvalues ()); this.name = name; this.redistemplate = redistemplate; this.CaffeInecache = CaffeInecache; this.CachePrefix = CacheredisCaffeineProperties.getCacheprefix (); this.defaultExpiration = CacheredisCaffeineProperties.getedis (). getDefaultExpiration (); this.expires = CacheredisCaffeineProperties.getredis (). getExpires (); this.topic = CacheredisCaffeineProperties.getredis (). getTopic (); } @Override public String getName () {return this.name; } @Override Public Object getNativecache () {return esto; } @Suppleswarnings ("sin verificar") @Override public <t> t get (clave de objeto, llamable <t> valueloader) {objeto valor = lookup (clave); if (value! = null) {return (t) valor; } ReentRantLock LOCK = new ReentRantLock (); intente {Lock.Lock (); valor = búsqueda (clave); if (value! = null) {return (t) valor; } valor = valueloader.call (); Object storeValue = Tostorevalue (valueloader.call ()); poner (clave, almacenamiento de almacén); Valor de retorno (t); } catch (Exception e) {try {class <?> c = class.forname ("org.springframework.cache.cache $ valueTrievalException"); Constructor <?> constructor = c.getConstrucor (object.class, llamado.class, switebable.class); RuntimeException excepción = (runtimeException) constructor.newinstance (Key, Valieloader, E.GetCause ()); Tirar excepción; } Catch (Exception e1) {Throw New IlegalStateException (E1); }} finalmente {Lock.unlock (); }} @Override public void put (clave de objeto, valor de objeto) {if (! Super.IsiLhoWnullValues () && valor == null) {this.evict (clave); devolver; } long expire = getEptire (); if (expire> 0) {redistemplate.opsforvalue (). set (getKey (key), tostorevalue (valor), expire, timeUnit.milliseConds); } else {redistemplate.opsforValue (). set (getKey (key), tostorevalue (valor)); } push (new Cachemessage (this.name, key)); caffeInecache.put (clave, valor); } @Override public ValueWrapper Putifabsent (clave de objeto, valor de objeto) {object cachekey = getKey (clave); Object Prevvalue = null; // Considere usar bloqueos distribuidos, o cambie Redis 'Setifabsent a Operation Atomic Synchronized (Key) {PrevSalue = redistemplate.opsforValue (). Get (CacheKey); if (PrevValue == null) {long expire = getEppire (); if (expire> 0) {redistemplate.opsforvalue (). set (getKey (key), tostorevalue (valor), expire, timeUnit.milliseConds); } else {redistemplate.opsforValue (). set (getKey (key), tostorevalue (valor)); } push (new Cachemessage (this.name, key)); caffeInecache.put (clave, tostorevalue (valor)); }} return toValueWrapper (Prevalue); } @Override public void Evict (clave de objeto) {// Primero borre los datos en caché en Redis, luego borre el caché en la cafeína, para evitar el caché en un corto período de tiempo si el caché se borra primero, y otras solicitudes se cargarán de Redis a Caffeine Redistemplate. push (nuevo Cachemessage (this.name, key)); caffeInecache.Invalidate (clave); } @Override public void Clear () {// Borrar los datos almacenados en caché en Redis, luego borre el caché en la cafeína, para evitar el caché en un corto período de tiempo si el caché de caché se borra primero, entonces otras solicitudes se cargarán de desde la cafeína a la cafeína de Redis Set <ject> Keys = redistemplate.Keys (this.name.concat ((":" "));)););););););););););););) for (clave de objeto: tecla) {redistemplate.delete (clave); } push (new Cachemessage (this.name, null)); CaffeInecache.InvalidateAll (); } @Override Lookup de objeto protegido (tecla de objeto) {Object cachekey = getKey (clave); Valor objeto = caffeInecache.getifPresent (clave); if (value! = null) {logger.debug ("Obtener caché de la cafeína, la clave es: {}", cachekey); valor de retorno; } valor = redistemplate.opsforValue (). get (cachekey); if (value! = null) {logger.debug ("Obtener caché de Redis y poner en cafeína, la clave es: {}", cachekey); CacheInecache.put (clave, valor); } valor de retorno; } objeto privado getKey (clave de objeto) {return this.name.concat (":"). concat (stringUtils.isEmpty (cacheprefix)? key.ToString (): cacheprefix.concat (":"). concat (key.ToString ())); } Private Long GetEppire () {long expire = defaultExpiration; Long CachenameExpire = expires.get (this.name); return cachenameExpire == nulo? expirar: CachenameExpire.longValue (); } / ** * @Description notifique a otros nodos para limpiar el caché local cuando cambia el caché * @author fuwei.deng * @Date el 31 de enero de 2018 a las 3:20:28 pm * @version 1.0.0 * @param Mensaje * / private Void Push (Mensaje de caché) {redistoplate.conververandsend (tema, mensaje);; } / ** * @Description Limpie el caché local * @author fuwei.deng * @Date el 31 de enero de 2018 a las 3:15:39 pm * @version 1.0.0 * @param key * / public void clareLocal (clave de objeto) {logger.debug ("Clear Cheache, la clave es: {}", Key); if (key == null) {caffeInecache.InvalidateAll (); } else {caffeInecache.Invalidate (clave); }}} Implementar la interfaz Cachemanager
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.util.collection; import java.util.set; import java.util.concurrent.concurrenthashmap; import java.util.concurrent.concurrentmap; import java.util.concurrent.timeunit; import org.slf4j.logger; import org.slf4j.loggerFactory; import org.springframework.cache.cache; import org.springframework.cache.cachemanger; import org.springframework.data.redis.redistemplate; com.github.benmanes.caffeine.cache.caffeine; import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineproperties;/** * @author fuwei.Deng * @Date el 26 de enero de 2018 en 5:24:52 pm * @version 1.0.0.0.0.0.0.0.0.0.0.0.0 */Público Classil RediscaffeInecachemanager implementa Cachemanager {private final logger logger = loggerFactory.getLogger (rediscaffeInecachemanager.class); Private ConcurrentMap <String, Cache> Cachemap = new concurrenthashmap <string, cache> (); CacheredisCaffeineProperties privadas CacherediscaffeineProperties; privado redistemplate <objeto, objeto> redistemplate; Private Boolean Dynamic = True; Conjunto privado <String> Cachenames; Public RedisCAffeInEcaffeInemanager (CacheredisCaffeineProperties CacheredisCaffeineProperties, redistemplate <objeto, objeto> redistemplate) {super (); this.CacheredisCaffeineProperties = CacheredisCaffeineProperties; this.redistemplate = redistemplate; this.dynamic = CacheredisCaffeineProperties.IsDyNamic (); this.cachenames = CacheredisCaffeineProperties.getCachenames (); } @Override public cache getCache (nombre de cadena) {caché cache = cachemap.get (nombre); if (cache! = null) {return cache; } if (! Dynamic &&! Cachenames.contains (name)) {return caché; } cache = new RedisCAffeInEcache (nombre, redistemplate, cache (), cacherediscaffeineproperties); Cache OldCache = Cachemap.putifabsent (nombre, caché); logger.debug ("Crear instancia de caché, el nombre de caché es: {}", nombre); devolver OldCache == NULL? Cache: OldCache; } public com.github.benmanes.caffeine.cache.cache <objeto, objeto> caffeInecache () {Caffeine <object, object> cacheBuilder = caffeine.newbuilder (); if (cacherediscaffeineproperties.getCaffeine (). getExpeAftterAccess ()> 0) {cacheBuilder.expirafterAccess (cacherediscaffeineproperties.getCaffeine (). GetExpeAfteraccess (), tiempo de tiempo. } if (CacheredisCaffeineProperties.getCaffeine (). } if (cacheredisCaffeineProperties.getCaffeine (). getInitialCapacity ()> 0) {cacheBuilder.initialCapacity (cacherediscaffeineproperties.getCaffeine (). getInitialCapacity ()); } if (cacherDisCaffeineProperties.getCaffeine (). getMaximumSize ()> 0) {cacheBuilder.maximumSize (cacherediscaffeineproperties.getCaffeine (). GetMaximumSize ()); } if (cacherediscaffeineproperties.getCaffeine (). getRefreshafterwrite ()> 0) {cacheBuilder.refresheshafterwrite (cacherediscaffeineproperties.getCaffeine (). getReshafterWrite (), timeUnit.milliseconds); } return cacheBuilder.Build (); } @Override Public Collection <String> getCachenames () {return this.cachenames; } public void ClearLocal (String Cachename, Key de objeto) {Cache Cache = Cachemap.get (Cachename); if (cache == null) {return; } RedisCaffeInecache redisCaffeInecache = (rediscaffeInecache) caché; rediscaffeInecache.ClearLocal (clave); }} Redis Mensaje Publicar/Suscríbete, clase de mensaje para la transmisión
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.io.serializable;/** * @author fuwei.deng * @Date 29 de enero de 2018 1:31:17 PM * @version 1.0.0 */public Class CapeMessage Cophemesge SerialVersionUid = 59872193104442078193l; cadena privada Cachename; clave de objeto privado; public Cachemessage (String Cachename, Key de 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 (clave de objeto) {this.key = key; }} Escuchar los mensajes Redis requiere la interfaz MessageListener
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import org.slf4j.logger; import org.slf4j.loggerFactory; import org.springframework.data.redis.connection.message; import; org.springframework.data.redis.connection.messageListener; import org.springframework.data.redis.core.redistemplate;/** * @author fuwei.deng * @Date 30 de enero de 2018 a las 5:22:33 pm * @version 1.0.0 */public class Cachemessisterenerener logger = loggerFactory.getLogger (CachemessageListener.class); privado redistemplate <objeto, objeto> redistemplate; Rediscaffeinecachemanager privado RediscisfaffeInecachemanager; Public CachemessageListener (redistemplate <objeto, objeto> redistemplate, rediscaffeinecachemanager rediscaffeinecachemanager) {super (); this.redistemplate = redistemplate; this.RedisCAffeInecachemanager = rediscaffeInecachemanager; } @Override public void onMessage (mensaje de mensaje, byte [] patrón) {Cachemessage Cachemessage = (Cachemessage) redistemplate.getValueSerializer (). Deserialize (Message.getBody ()); logger.debug ("Recibvice A Redis Topic Mensaje, Clare Local Cache, Cachename es {}, la clave es {}", Cachemessage.getCachename (), Cachemessage.getKey ()); rediscaffeInecachemanager.ClearLocal (Cachemessage.getCachename (), Cachemessage.getKey ()); }} Agregar clase de configuración de arranque de primavera
paquete com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import org.springframework.beans.factory.annotation.auTowired; import o org. 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 (nombre); 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
Extendido
个人认为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
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.